source: src/fsuipc.py@ 3:ca35ec2b9cc4

Last change on this file since 3:ca35ec2b9cc4 was 3:ca35ec2b9cc4, checked in by István Váradi <ivaradi@…>, 12 years ago

The FSUIPC data collection framework works

File size: 8.6 KB
Line 
1# Module handling the connection to FSUIPC
2
3import threading
4import os
5import fs
6import time
7
8if os.name == "nt":
9 import pyuipc
10else:
11 import pyuipc_emu as pyuipc
12
13class Handler(threading.Thread):
14 """The thread to handle the FSUIPC requests."""
15 class Request(object):
16 """A simple, one-shot request."""
17 def __init__(self, forWrite, data, callback, extra):
18 """Construct the request."""
19 self._forWrite = forWrite
20 self._data = data
21 self._callback = callback
22 self._extra = extra
23
24 def process(self, time):
25 """Process the request."""
26 if self._forWrite:
27 pyuipc.write(self._data)
28 self._callback(self._extra)
29 else:
30 values = pyuipc.read(self._data)
31 self._callback(values, self._extra)
32
33 return True
34
35 class PeriodicRequest(object):
36 """A periodic request."""
37 def __init__(self, id, period, data, callback, extra):
38 """Construct the periodic request."""
39 self._id = id
40 self._period = period
41 self._nextFire = time.time() + period
42 self._data = data
43 self._preparedData = None
44 self._callback = callback
45 self._extra = extra
46
47 @property
48 def id(self):
49 """Get the ID of this periodic request."""
50 return self._id
51
52 @property
53 def nextFire(self):
54 """Get the next firing time."""
55 return self._nextFire
56
57 def process(self, time):
58 """Check if this request should be executed, and if so, do so.
59
60 Return a boolean indicating if the request was executed."""
61 if time < self._nextFire:
62 return False
63
64 if self._preparedData is None:
65 self._preparedData = pyuipc.prepare_data(self._data)
66 self._data = None
67
68 values = pyuipc.read(self._preparedData)
69
70 self._callback(values, self._extra)
71
72 while self._nextFire <= time:
73 self._nextFire += self._period
74
75 return True
76
77 def __cmp__(self, other):
78 """Compare two periodic requests. They are ordered by their next
79 firing times."""
80 return cmp(self._nextFire, other._nextFire)
81
82 def __init__(self, connectionListener):
83 """Construct the handler with the given connection listener."""
84 threading.Thread.__init__(self)
85
86 self._connectionListener = connectionListener
87
88 self._requestCondition = threading.Condition()
89 self._connectionRequested = False
90
91 self._requests = []
92 self._nextPeriodicID = 1
93 self._periodicRequests = []
94
95 self.daemon = True
96
97 def requestRead(self, data, callback, extra = None):
98 """Request the reading of some data.
99
100 data is a list of tuples of the following items:
101 - the offset of the data as an integer
102 - the type letter of the data as a string
103
104 callback is a function that receives two pieces of data:
105 - the values retrieved
106 - the extra parameter
107
108 It will be called in the handler's thread!
109 """
110 with self._requestCondition:
111 self._requests.append(Handler.Request(False, data, callback, extra))
112 self._requestCondition.notify()
113
114 def requestWrite(self, data, callback, extra = None):
115 """Request the writing of some data.
116
117 data is a list of tuples of the following items:
118 - the offset of the data as an integer
119 - the type letter of the data as a string
120 - the data to write
121
122 callback is a function that receives the extra data when writing was
123 succesful. It will called in the handler's thread!
124 """
125 with self._requestCondition:
126 self._requests.append(Handler.Request(True, data, callback, extra))
127 self._requestCondition.notify()
128
129 def requestPeriodicRead(self, period, data, callback, extra = None):
130 """Request a periodic read of data.
131
132 period is a floating point number with the period in seconds.
133
134 This function returns an identifier which can be used to cancel the
135 request."""
136 with self._requestCondition:
137 id = self._nextPeriodicID
138 self._nextPeriodicID += 1
139 request = Handler.PeriodicRequest(id, period, data, callback, extra)
140 self._periodicRequests.append(request)
141 self._requestCondition.notify()
142 return id
143
144 def clearPeriodic(self, id):
145 """Clear the periodic request with the given ID."""
146 with self._requestCondition:
147 for i in range(0, len(self._periodicRequests)):
148 if self._periodicRequests[i].id==id:
149 del self._periodicRequests[i]
150 return True
151 return False
152
153 def connect(self):
154 """Initiate the connection to the flight simulator."""
155 with self._requestCondition:
156 if not self._connectionRequested:
157 self._connectionRequested = True
158 self._requestCondition.notify()
159
160 def disconnect(self):
161 """Disconnect from the flight simulator."""
162 with self._requestCondition:
163 if self._connectionRequested:
164 self._connectionRequested = False
165 self._requestCondition.notify()
166
167 def run(self):
168 """Perform the operation of the thread."""
169 while True:
170 self._waitConnectionRequest()
171
172 if self._connect():
173 self._handleConnection()
174
175 self._disconnect()
176
177 def _waitConnectionRequest(self):
178 """Wait for a connection request to arrive."""
179 with self._requestCondition:
180 while not self._connectionRequested:
181 self._requestCondition.wait()
182
183 def _connect(self):
184 """Try to connect to the flight simulator via FSUIPC"""
185 while self._connectionRequested:
186 try:
187 pyuipc.open(pyuipc.SIM_FS2K4)
188 description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
189 (pyuipc.fsuipc_version, pyuipc.lib_version,
190 pyuipc.fs_version)
191 self._connectionListener.connected(fs.TYPE_FS2K4, description)
192 return True
193 except Exception, e:
194 print "fsuipc.Handler._connect: connection failed: " + str(e)
195
196 return False
197
198 def _handleConnection(self):
199 """Handle a living connection."""
200 with self._requestCondition:
201 while self._connectionRequested:
202 if not self._processRequests():
203 return
204 timeout = None
205 if self._periodicRequests:
206 self._periodicRequests.sort()
207 timeout = self._periodicRequests[0].nextFire - time.time()
208 if timeout is None or timeout > 0.0:
209 self._requestCondition.wait(timeout)
210
211 def _disconnect(self):
212 """Disconnect from the flight simulator."""
213 pyuipc.close()
214 self._connectionListener.disconnected()
215
216 def _processRequest(self, request, time):
217 """Process the given request.
218
219 If an exception occurs, we try to reconnect.
220
221 Returns what the request's process() function returned or None if
222 reconnection failed."""
223
224 self._requestCondition.release()
225
226 try:
227 return request.process(time)
228 except Exception as e:
229 print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
230 str(e) + ") reconnecting."
231 self._disconnect()
232 if not self._connect(): return None
233 else: return True
234 finally:
235 self._requestCondition.acquire()
236
237 def _processRequests(self):
238 """Process any pending requests.
239
240 Will be called with the request lock held."""
241 while self._connectionRequested and self._periodicRequests:
242 self._periodicRequests.sort()
243 request = self._periodicRequests[0]
244 result = self._processRequest(request, time.time())
245 if result is None: return False
246 elif not result: break
247
248 while self._connectionRequested and self._requests:
249 request = self._requests[0]
250 del self._requests[0]
251
252 if self._processRequest(request, None) is None:
253 return False
254
255 return self._connectionRequested
Note: See TracBrowser for help on using the repository browser.