-
Notifications
You must be signed in to change notification settings - Fork 2
/
airmar.py
550 lines (478 loc) · 18.8 KB
/
airmar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
#!/usr/bin/env python
from __future__ import with_statement
import serial
import syslog
import time
import weewx.drivers
DRIVER_NAME = 'Airmar'
DRIVER_VERSION = '0.26'
INHG_PER_BAR = 29.5333727
METER_PER_FOOT = 0.3048
MILE_PER_KM = 0.621371
DEBUG_SERIAL = 0
def loader(config_dict, _):
return Airmar(**config_dict[DRIVER_NAME])
def confeditor_loader():
return AirmarConfEditor()
DEFAULT_PORT = '/dev/ttyS0'
def logmsg(level, msg):
syslog.syslog(level, 'airmar: %s' % msg)
def logdbg(msg):
logmsg(syslog.LOG_DEBUG, msg)
def loginf(msg):
logmsg(syslog.LOG_INFO, msg)
def logerr(msg):
logmsg(syslog.LOG_ERR, msg)
class Airmar(weewx.drivers.AbstractDevice):
"""weewx driver that communicates with an Airmar Weather Station
model: station model, e.g., 'Airmar 150WX'
[Optional. Default is 'Airmar']
port - serial port
[Required. Default is /dev/ttyS0]
max_tries - how often to retry serial communication before giving up
[Optional. Default is 10]
"""
def __init__(self, **stn_dict):
self.model = stn_dict.get('model', 'Airmar')
self.port = stn_dict.get('port', DEFAULT_PORT)
self.max_tries = int(stn_dict.get('max_tries', 10))
self.retry_wait = int(stn_dict.get('retry_wait', 10))
self.last_rain = None
global DEBUG_SERIAL
DEBUG_SERIAL = int(stn_dict.get('debug_serial', 0))
loginf('driver version is %s' % DRIVER_VERSION)
loginf('using serial port %s' % self.port)
self.station = Station(self.port)
self.station.open()
def closePort(self):
if self.station is not None:
self.station.close()
self.station = None
@property
def hardware_name(self):
return self.model
def genLoopPackets(self):
while True:
packet = {'dateTime': int(time.time() + 0.5),
'usUnits': weewx.US}
readings = self.station.get_readings_with_retry(self.max_tries,
self.retry_wait)
#data = Station.parse_readings(readings)
data = self.station.parse_readings(readings)
packet.update(data)
self._augment_packet(packet)
yield packet
def _augment_packet(self, packet):
# calculate the rain delta from rain total
if self.last_rain is not None:
packet['rain'] = packet['long_term_rain'] - self.last_rain
else:
packet['rain'] = None
self.last_rain = packet['long_term_rain']
# no wind direction when wind speed is zero
if 'windSpeed' in packet and not packet['windSpeed']:
packet['windDir'] = None
class Station(object):
def __init__(self, port):
self.port = port
self.baudrate = 4800
self.timeout = 3 # seconds
self.serial_port = None
def __enter__(self):
self.open()
return self
def __exit__(self, _, value, traceback):
self.close()
def open(self):
logdbg("open serial port %s" % self.port)
if "://" in self.port:
self.serial_port = serial.serial_for_url(self.port,
baudrate=self.baudrate,timeout=self.timeout)
else:
self.serial_port = serial.Serial(self.port, self.baudrate,
timeout=self.timeout)
def close(self):
if self.serial_port is not None:
logdbg("close serial port %s" % self.port)
self.serial_port.close()
self.serial_port = None
def get_readings(self):
buf = self.serial_port.readline()
if DEBUG_SERIAL:
logdbg("station said: %s" % buf)
buf = buf.strip() # FIXME: is this necessary?
return buf
def validate_string(self, buf):
if buf[0:1] != '$':
loginf("Unexpected header byte '%s'" % buf[0:1])
if buf[-3:-2] != '*':
loginf("Unexpected footer byte '%s'" % buf[-2:])
[mess, cs] = buf.split("*")
mess = mess[1:]
cs_new = 0
for d in mess:
cs_new = cs_new ^ ord(d)
cs_new = "%2X" % cs_new
if cs_new != cs and "0%s" % str(cs_new).strip() != "%s" % cs:
loginf("Unexpected checksum error [%s], [%s]" % (cs_new, cs))
return buf
def get_readings_with_retry(self, max_tries=5, retry_wait=10):
for ntries in range(0, max_tries):
try:
buf = self.get_readings()
self.validate_string(buf)
return buf
except (serial.serialutil.SerialException, weewx.WeeWxIOError), e:
loginf("Failed attempt %d of %d to get readings: %s" %
(ntries + 1, max_tries, e))
time.sleep(retry_wait)
else:
msg = "Max retries (%d) exceeded for readings" % max_tries
logerr(msg)
raise weewx.RetriesExceeded(msg)
def calc_WCHR(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "windchill_rel"
except (ValueError):
loginf("Wrong data format for windchill_rel '%s'" % buf[idx-2])
return data
def calc_WCHT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "windchill"
except (ValueError):
loginf("Wrong data format for windchill '%s'" % buf[idx-2])
return data
def calc_HINX(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "heatindex"
except (ValueError):
loginf("Wrong data format for heatindex '%s'" % buf[idx-2])
return data
def calc_STNP(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * INHG_PER_BAR
data['name'] = "pressure"
except (ValueError):
loginf("Wrong data format for pressure '%s'" % buf[idx-2])
return data
def calc_PTCH(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "pitch"
except (ValueError):
loginf("Wrong data format for ptich '%s'" % buf[idx-2])
return data
def calc_ROLL(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "roll"
except (ValueError):
loginf("Wrong data format for roll '%s'" % buf[idx-2])
return data
def calc_XACC(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "x_accel"
except (ValueError):
loginf("Wrong data format for x_accel '%s'" % buf[idx-2])
return data
def calc_YACC(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "y_accel"
except (ValueError):
loginf("Wrong data format for y_accel '%s'" % buf[idx-2])
return data
def calc_ZACC(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "z_accel"
except (ValueError):
loginf("Wrong data format for z_accel '%s'" % buf[idx-2])
return data
def calc_RRAT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "rollRate"
except (ValueError):
loginf("Wrong data format for rollRate '%s'" % buf[idx-2])
return data
def calc_PRAT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "pitchRate"
except (ValueError):
loginf("Wrong data format for pitchRate '%s'" % buf[idx-2])
return data
def calc_YRAT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "yawRate"
except (ValueError):
loginf("Wrong data format for yawRate '%s'" % buf[idx-2])
return data
def calc_RRTR(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "rollRate_raw"
except (ValueError):
loginf("Wrong data format for rollRate_raw '%s'" % buf[idx-2])
return data
def calc_PRTR(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "pitchRate_raw"
except (ValueError):
loginf("Wrong data format for pitchRate_raw '%s'" % buf[idx-2])
return data
def calc_YRTR(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "yawRate_raw"
except (ValueError):
loginf("Wrong data format for yawRate_raw '%s'" % buf[idx-2])
return data
def calc_PLAT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "heatingTemp_plate"
except (ValueError):
loginf("Wrong data format for heatingTemp_plate '%s'" % buf[idx-2])
return data
def calc_CAPT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "heatingTemp_cap"
except (ValueError):
loginf("Wrong data format for heatingTemp_cap '%s'" % buf[idx-2])
return data
def calc_PLAV(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "heatingVoltage_pl"
except (ValueError):
loginf("Wrong data format for heatingVoltage_pl '%s'" % buf[idx-2])
return data
def calc_CAPV(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "heatingVoltage_cap"
except (ValueError):
loginf("Wrong data format for heatingVoltage_cap '%s'" % buf[idx-2])
return data
def calc_HUMT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "inTemp"
except (ValueError):
loginf("Wrong data format for inTemp '%s'" % buf[idx-2])
return data
def calc_BRDT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "mbTemp"
except (ValueError):
loginf("Wrong data format for mbTemp '%s'" % buf[idx-2])
return data
def calc_UPPT(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2]) * 1.8 + 32
data['name'] = "upTemp"
except (ValueError):
loginf("Wrong data format for upTemp '%s'" % buf[idx-2])
return data
def calc_BRDV(self, buf, idx):
data = dict()
try:
data['value'] = float(buf[idx-2])
data['name'] = "supplyVoltage"
except (ValueError):
loginf("Wrong data format for supplyVoltage '%s'" % buf[idx-2])
return data
#@staticmethod
def parse_readings(self, raw):
"""Airmar.......
"""
print raw
data = dict()
yx_data = dict()
data['long_term_rain'] = None
(interm, cs) = raw.split("*")
buf = interm.split(",")
if buf[0] == '$HCHDG':
try:
data['heading_magn'] = float(buf[1])
data['deviation_magn'] = float(buf[2])
data['variation_magn'] = float(buf[4])
except (ValueError):
loginf("Wrong data format for $HCHDG '%s, %s, %s'" % (buf[1], buf[2], buf[4]))
if buf[3] == 'E':
data['deviation_magn'] *= -1 #?????????????
if buf[5] == 'E':
data['variation_magn'] *= -1 #?????????????
elif buf[0] == '$HCHDT':
try:
data['heading_true'] = float(buf[1])
except (ValueError):
loginf("Wrong data format for $HCHDT '%s'" % buf[1])
elif buf[0] == '$WIMDA':
try:
data['altimeter'] = float(buf[1])
data['outTemp'] = float(buf[5]) * 1.8 + 32
data['outHumidity'] = float(buf[9])
data['dewpoint'] = float(buf[11]) * 1.8 + 32
data['windDir_true_mda'] = float(buf[13])
data['windDir_magn_mda'] = float(buf[15])
data['windSpeed_mda'] = float(buf[17]) / 1.15077945 #Wind speed, mph
except (ValueError):
loginf("Wrong data format for $WIMDA '%s, %s, %s, %s, %s, %s, %s'" % (buf[1], buf[5], buf[9], buf[11], buf[13], buf[15], buf[17]))
elif buf[0] == '$WIMWD':
try:
data['windDir_true_mwd'] = float(buf[1])
data['windDir_magn_mwd'] = float(buf[3])
data['windSpeed_mwd'] = float(buf[5]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIMWD '%s, %s, %s'" % (buf[1], buf[3], buf[5]))
elif buf[0] == '$WIMWV':
if buf[5] == 'A':
if buf[2] == 'R':
try:
data['windAngle_rel_mwv'] = float(buf[1])
data['windSpeed_rel_mwv'] = float(buf[3]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIMWV A-R '%s, %s'" % (buf[1], buf[3]))
elif buf[2] == 'T':
try:
data['windAngle_theor_mwv'] = float(buf[1])
data['windSpeed_theor_mwv'] = float(buf[3]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIMWV A-T '%s, %s'" % (buf[1], buf[3]))
elif buf[0] == '$TIROT':
if buf[2] == 'A':
try:
data['tiRot'] = float(buf[1])
except (ValueError):
loginf("Wrong data format for $TIROT '%s'" % buf[1])
elif buf[0] == '$HCTHS':
if buf[2] == 'A':
try:
data['true_north_heading'] = float(buf[1])
except (ValueError):
loginf("Wrong data format for $HCTHS '%s'" % buf[1])
elif buf[0] == '$WIVWR':
try:
data['windAngle_rel_vess'] = float(buf[1])
data['windSpeed_rel_vess'] = float(buf[3]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIVWR '%s, %s'" % (buf[1], buf[3]))
if buf[2] == 'R': #R = right
data['windAngle_rel_vess'] *= -1 #???????????????????
elif buf[0] == '$WIVWT':
try:
data['windAngle_true_vess'] = float(buf[1])
data['windSpeed_true_vess'] = float(buf[3]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIVWT '%s, %s'" % (buf[1], buf[3]))
if buf[2] == 'R': #R = right
data['windAngle_true_vess'] *= -1 #???????????????????????
elif buf[0] == '$YXXDR':
for idx in [4, 8, 12, 16]:
yx_data.clear()
try:
typestr = buf[idx]
yx_data = getattr(self, 'calc_'+typestr)(buf, idx)
if 'value' in yx_data:
data[yx_data['name']] = yx_data['value']
except (IndexError):
break
elif buf[0] == '$WIXDR':
if buf[4] == 'RAIN':
try:
data['long_term_rain'] = float(buf[2]) * 0.03937007874015748
data['duration_of_rain'] = float(buf[6])
data['rain_intensity'] = float(buf[10]) * 0.03937007874015748
data['peak_rain_intensity'] = float(buf[14]) * 0.03937007874015748
except (ValueError):
loginf("Wrong data format for $WIXDR RAIN '%s, %s, %s, %s'" % (buf[2], buf[6], buf[10], buf[14]))
if buf[4] == 'WNDA':
try:
data['windAngle_unfilt'] = float(buf[2])
data['windSpeed_unfilt'] = float(buf[6]) / 1.15077945
except (ValueError):
loginf("Wrong data format for $WIXDR WNDA '%s, %s'" % (buf[2], buf[6]))
#else: #Processing of other data sentences
if 'windDir_true_mwd' in data and data['windDir_true_mwd'] is not None:
data['windDir'] = data['windDir_true_mwd']
elif 'windDir_true_mda' in data and data['windDir_true_mda'] is not None:
data['windDir'] = data['windDir_true_mda']
if 'windSpeed_mwd' in data and data['windSpeed_mwd'] is not None:
data['windSpeed'] = data['windSpeed_mwd']
elif 'windSpeed_mda' in data and data['windSpeed_mda'] is not None:
data['windSpeed'] = data['windSpeed_mda']
return data
class AirmarConfEditor(weewx.drivers.AbstractConfEditor):
@property
def default_stanza(self):
return """
[Airmar]
# This section is for the Airmar series of weather stations.
# Serial port such as /dev/ttyS0, /dev/ttyUSB0, or /dev/cuaU0
port = /dev/ttyUSB0
# The station model, e.g., Airmar 150WX
model = Airmar
# The driver to use:
driver = weewx.drivers.airmar
"""
def prompt_for_settings(self):
print "Specify the serial port on which the station is connected, for"
print "example /dev/ttyUSB0 or /dev/ttyS0."
port = self._prompt('port', '/dev/ttyUSB0')
return {'port': port}
# define a main entry point for basic testing of the station without weewx
# engine and service overhead. invoke this as follows from the weewx root dir:
#
# PYTHONPATH=bin python bin/weewx/drivers/airmar.py
if __name__ == '__main__':
import optparse
usage = """%prog [options] [--help]"""
syslog.openlog('airmar', syslog.LOG_PID | syslog.LOG_CONS)
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
parser = optparse.OptionParser(usage=usage)
parser.add_option('--version', dest='version', action='store_true',
help='display driver version')
parser.add_option('--port', dest='port', metavar='PORT',
help='serial port to which the station is connected',
default=DEFAULT_PORT)
(options, args) = parser.parse_args()
if options.version:
print "airmar driver version %s" % DRIVER_VERSION
exit(0)
with Station(options.port) as s:
s.set_logger_mode()
while True:
print time.time(), s.get_readings()