1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """
21 Driver for Reefnet Sensus Ultra dive logger.
22
23 It uses libdivecomputer library from
24
25 http://divesoftware.org/libdc/
26
27 Tested with libdivecomputer version: 781f0db71f7a06591e819f7960dd5b504b4f16a0.
28 """
29
30 import ctypes as ct
31 from datetime import datetime
32 from struct import unpack
33 from collections import namedtuple
34 from functools import partial
35 from queue import Queue, Full, Empty
36 from concurrent.futures import ThreadPoolExecutor
37 import time
38 import operator
39
40 import logging
41 log = logging.getLogger('kenozooid.driver.su')
42
43 import kenozooid.uddf as ku
44 import kenozooid.data as kd
45 import kenozooid.component as kc
46 from kenozooid.driver import DeviceDriver, DataParser, DeviceError
47 from kenozooid.units import C2K
48
49 SIZE_MEM_USER = 16384
50 SIZE_MEM_DATA = 2080768
51 SIZE_MEM_HANDSHAKE = 24
52 SIZE_MEM_SENSE = 6
53
54 START_HANDSHAKE = 0
55 END_HANDSHAKE = START_USER = SIZE_MEM_HANDSHAKE
56 END_USER = START_DATA = START_USER + SIZE_MEM_USER
57 END_DATA = START_DATA + SIZE_MEM_DATA
58
59
60
61 HandshakeDump = namedtuple('HandshakeDump', 'ver1 ver2 serial time')
62 FMT_HANDSHAKE = '<bbHL'
63
64 DiveHeader = namedtuple('DiveHeader', 'time interval threshold'
65 ' endcount averaging')
66
67 FMT_DIVE_HEADER = '<4xL4H'
68
69
70
71
72
73
74 -class DCBuffer(ct.Structure):
75 _fields_ = [
76 ('data', ct.POINTER(ct.c_char)),
77 ('capacity', ct.c_size_t),
78 ('offset', ct.c_size_t),
79 ('size', ct.c_size_t),
80 ]
81
82
83 SampleType = namedtuple('SampleType', 'time depth pressure temperature' \
84 ' event rbt heartbeat bearing vendor')._make(range(9))
88 _fields_ = [
89 ('tank', ct.c_uint),
90 ('value', ct.c_double),
91 ]
92
93
94 -class Event(ct.Structure):
95 _fields_ = [
96 ('type', ct.c_uint),
97 ('time', ct.c_uint),
98 ('flags', ct.c_uint),
99 ('value', ct.c_uint),
100 ]
101
102
103 -class Vendor(ct.Structure):
104 _fields_ = [
105 ('type', ct.c_uint),
106 ('size', ct.c_uint),
107
108 ]
109
112 _fields_ = [
113 ('time', ct.c_uint),
114 ('depth', ct.c_double),
115 ('pressure', Pressure),
116 ('temperature', ct.c_double),
117
118
119
120
121
122
123 ]
124
125
126
127 FuncDive = ct.CFUNCTYPE(ct.c_uint, ct.POINTER(ct.c_char), ct.c_uint,
128 ct.POINTER(ct.c_char), ct.c_uint, ct.c_void_p)
129 FuncSample = ct.CFUNCTYPE(None, ct.c_int, SampleValue, ct.c_void_p)
130
131
132 @kc.inject(DeviceDriver, id='su', name='Sensus Ultra Driver',
133 models=('Sensus Ultra',))
135 """
136 Sensus Ultra dive logger driver.
137 """
139 self.dev = dev
140 self.lib = lib
141
142
143 @staticmethod
144 - def scan(port=None):
145 """
146 Look for Reefnet Sensus Ultra dive logger connected to one of USB
147 ports.
148
149 Library `libdivecomputer` is used, therefore no scanning and port
150 shall be specified.
151 """
152 lib = ct.CDLL('libdivecomputer.so.0')
153
154 dev = ct.c_void_p()
155 rc = 0
156 if port is not None:
157 rc = lib.reefnet_sensusultra_device_open(ct.byref(dev),
158 port.encode())
159 if rc == 0:
160 drv = SensusUltraDriver(dev, lib)
161 log.debug('found Reefnet Sensus Ultra driver using' \
162 ' libdivecomputer library on port {}'.format(port))
163 yield drv
164 else:
165 log.debug('libdc error: {}'.format(rc))
166
167
169 """
170 Read Reefnet Sensus Ultra version and serial number.
171 """
172
173 sd = ct.create_string_buffer(SIZE_MEM_SENSE)
174 rc = self.lib.reefnet_sensusultra_device_sense(self.dev, sd, SIZE_MEM_SENSE)
175 if rc != 0:
176 raise DeviceError('Device communication error')
177
178 hd = ct.create_string_buffer(SIZE_MEM_HANDSHAKE)
179 rc = self.lib.reefnet_sensusultra_device_get_handshake(self.dev, hd, SIZE_MEM_HANDSHAKE)
180 if rc != 0:
181 raise DeviceError('Device communication error')
182
183
184 dump = _handshake(hd.raw)
185 return 'Sensus Ultra %d.%d' % (dump.ver2, dump.ver1)
186
187
188
189 @kc.inject(DataParser, id='su', data=())
191 """
192 Reefnet Sensus Ultra dive logger data parser.
193 """
194
196 """
197 Download Sensus Ultra
198
199 - handshake packet
200 - user data
201 - data of all dive profiles
202 """
203 dev = self.driver.dev
204 lib = self.driver.lib
205
206 hd = ct.create_string_buffer(SIZE_MEM_HANDSHAKE)
207 ud = ct.create_string_buffer(SIZE_MEM_USER)
208 dd = ct.cast(lib.dc_buffer_new(SIZE_MEM_DATA), ct.POINTER(DCBuffer))
209
210 log.debug('loading user data')
211 rc = lib.reefnet_sensusultra_device_read_user(dev, ud, SIZE_MEM_USER)
212 if rc != 0:
213 log.debug('read error rc = {}'.format(rc))
214 raise DeviceError('Device communication error')
215
216 log.debug('loading handshake data')
217 rc = lib.reefnet_sensusultra_device_get_handshake(dev, hd, SIZE_MEM_HANDSHAKE)
218 if rc != 0:
219 log.debug('read error rc = {}'.format(rc))
220 raise DeviceError('Device communication error')
221
222 log.debug('loading dive data')
223 rc = lib.device_dump(dev, dd)
224 if rc != 0:
225 log.debug('read error rc = {}'.format(rc))
226 raise DeviceError('Device communication error')
227
228 return hd.raw + ud.raw + dd.contents.data[:dd.contents.size]
229
230
232 """
233 Convert Reefnet Sensus Ultra dive data into UDDF format.
234 """
235
236
237 lib = ct.CDLL('libdivecomputer.so.0')
238
239 parser = ct.c_void_p()
240 rc = lib.reefnet_sensusultra_parser_create(ct.byref(parser))
241 if rc != 0:
242 raise DeviceError('Cannot create data parser')
243
244 hd = dump.data[:END_HANDSHAKE]
245 assert len(hd) == SIZE_MEM_HANDSHAKE, len(hd)
246 hdp = _handshake(hd)
247
248 ud = dump.data[START_USER : END_USER]
249 assert len(ud) == SIZE_MEM_USER, len(ud)
250
251 dd = ct.create_string_buffer(SIZE_MEM_DATA)
252 dd.raw = dump.data[START_DATA:]
253 assert len(dd.raw) == SIZE_MEM_DATA, len(dd.raw)
254
255
256 btime = time.mktime(dump.datetime.timetuple()) - hdp.time
257
258 dq = Queue(10)
259 parse_dive = partial(self.parse_dive,
260 parser=parser, boot_time=btime, dives=dq)
261 f = FuncDive(parse_dive)
262 extract_dives = partial(lib.reefnet_sensusultra_extract_dives,
263 None, dd, SIZE_MEM_DATA, f, None)
264
265 return _iterate(dq, extract_dives)
266
267
269 """
270 Return empty tuple - no gas information stored by Sensus Ultra.
271
272 :Parameters:
273 data
274 Sensus Ultra data.
275 """
276 return ()
277
278
280 """
281 Get Sensus Ultra version from raw data.
282
283 :Parameters:
284 data
285 Raw dive computer data.
286 """
287 status = _handshake(data)
288 return 'Sensus Ultra %d.%d' % (status.ver2, status.ver1)
289
290
291 - def parse_dive(self, buffer, size, fingerprint, fsize, pdata, parser,
292 boot_time, dives):
293 """
294 Callback used by libdivecomputer's library function to extract
295 dives from a device and put it into dives queue.
296
297 :Parameters:
298 buffer
299 Buffer with binary dive data.
300 size
301 Size of buffer dive data.
302 fingerprint
303 Fingerprint buffer.
304 fsize
305 Size of fingerprint buffer.
306 pdata
307 Parser user data (nothing at the moment).
308 parser
309 libdivecomputer parser instance.
310 boot_time
311 Sensus Ultra boot time.
312 dives
313 Queue of dives to be consumed by caller.
314 """
315 lib = ct.CDLL('libdivecomputer.so.0')
316 lib.parser_set_data(parser, buffer, size)
317
318 header = _dive_header(buffer)
319 log.debug('parsing dive: {0}'.format(header))
320
321
322
323 st = datetime.fromtimestamp(boot_time - header.interval + header.time)
324 log.debug('got dive time: {0}'.format(st))
325
326 samples = []
327 parse_sample = partial(self.parse_sample,
328 dive_header=header,
329 sdata={},
330 sq=samples)
331
332 f = FuncSample(parse_sample)
333 lib.parser_samples_foreach(parser, f, None)
334
335 log.debug('removing {} endcount samples'.format(header.endcount))
336 del samples[-header.endcount:]
337
338
339 max_depth = max(samples, key=operator.attrgetter('depth')).depth
340 min_temp = min(samples, key=operator.attrgetter('temp')).temp
341 duration = samples[-1].time + header.interval
342
343
344
345 samples.insert(0, kd.Sample(depth=0.0, time=0))
346
347
348
349 samples.append(kd.Sample(depth=0.0, time=duration))
350
351
352 dive = kd.Dive(datetime=st, depth=max_depth, duration=duration,
353 temp=min_temp, profile=samples)
354
355 try:
356 dives.put(dive, timeout=30)
357 except Full:
358 log.error('could not parse dives due to internal queue timeout')
359 return 0
360
361 return 1
362
363
364 - def parse_sample(self, st, sample, pdata, dive_header, sdata, sq):
365 """
366 Convert dive samples data generated with libdivecomputer library
367 into UDDF waypoint structure.
368
369 :Parameters:
370 st
371 Sample type as specified in parser.h.
372 sample
373 Sample data.
374 pdata
375 Parser user data (nothing at the moment).
376 dive_header
377 Dive header of parsed dive.
378 sdata
379 Temporary sample data.
380 sq
381 List of samples.
382 """
383
384
385 if st == SampleType.time:
386 sdata['time'] = sample.time
387 elif st == SampleType.temperature:
388 sdata['temp'] = round(C2K(sample.temperature), 1)
389 elif st == SampleType.depth:
390
391 sdata['depth'] = round(max(sample.depth, 0), 1)
392
393 s = kd.Sample(**sdata)
394 log.debug('got sample {}'.format(sdata))
395
396 sq.append(s)
397 sdata.clear()
398 else:
399 log.warn('unknown sample type', st)
400 return 1
401
404 """
405 Convert binary data into HandshakeDump structure.
406
407 :Parameters:
408 data
409 Binary data.
410
411 """
412 return HandshakeDump._make(unpack(FMT_HANDSHAKE, data[:8]))
413
416 """
417 Convert binary data into DiveHeader structure.
418
419 :Parameters:
420 data
421 Dive binary data.
422 """
423 return DiveHeader._make(unpack(FMT_DIVE_HEADER, data[:16]))
424
427 """
428 Create iterator for stateful function.
429
430 Stateful function pushes items into to the queue, while this function
431 pull items from the queue and returns them one by one.
432
433 :Parameters:
434 queue
435 Queue holding stateful function items.
436 f
437 Stateful function.
438 """
439 with ThreadPoolExecutor(max_workers=1) as e:
440 fn = e.submit(f)
441
442 while not (fn.done() and queue.empty()):
443 try:
444 n = queue.get(timeout=1)
445 yield n
446 except Empty:
447 if not fn.done():
448 log.warn('su driver possible queue miss')
449
450 if fn.result() == 0:
451 raise StopIteration()
452 else:
453 raise DeviceError('Failed to extract data properly')
454
455
456