I've made a quick, first pass at porting the WMR89 driver to Python 3 and WeeWX V4.
If you have the patience, we can probably get it ported. Run it from the command line first --- don't try to run WeeWX with it If you used setup.py install: * PYTHONPATH=/home/weewx/bin python3 wmr89.py* If you used package install: * PYTHONPATH=/usr/share/weewx python3 wmr89.py* Let it run for a minute or two (if it runs at all!), then post the log. -tk On Sun, May 3, 2020 at 9:46 AM andr3id <[email protected]> wrote: > I've been using the WMR89 driver from fcauwe > https://github.com/fcauwe/weewx for a while, actually totally forgetting > that I've had it as an add on and that it is not found in the official > repository. > > I've got excited when the weewx 4.0 release was available, specially with > the update to Python3 which I prefer over Python2, and updated my system > without thinking. > > Ofcourse the driver for WMR89 stopped working since it's written in > Python2 and I had to change to the Python2 version of weewx to get my > system up again. I run weewx on a raspberry pi 4 with raspbian. > > Is anyone currently developing a WMR89 driver for weewx 4.0 and Python3? > I've been planning to do that myself, but if it's already onging, I could > just probably contribute with some developing and testing. > > > > > -- > You received this message because you are subscribed to the Google Groups > "weewx-development" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion on the web visit > https://groups.google.com/d/msgid/weewx-development/070d8fed-462d-4b3c-b013-bb941315e59f%40googlegroups.com > <https://groups.google.com/d/msgid/weewx-development/070d8fed-462d-4b3c-b013-bb941315e59f%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- You received this message because you are subscribed to the Google Groups "weewx-development" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-development/CAPq0zECRL6c922O%2BBcdB2cM_RGLcEoc6LOUjrUnqy3pLCZ%2BfWw%40mail.gmail.com.
# # Copyright (c) 2012=2020 Will Page <[email protected]> # and Tom Keffer <[email protected]> # # See the file LICENSE.txt for your full rights. # """Classes and functions for interfacing with Oregon Scientific WMR89, See https://www.wxforum.net/index.php?topic=27581 for documentation on the serial protocol """ from __future__ import absolute_import from __future__ import print_function import binascii import sys import time import serial import weewx.drivers # ########################################## # The following was lifted from the utility 'six' # Copyright (c) 2010-2018 Benjamin Peterson PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY2: def byte2int(bs): return ord(bs[0]) if PY3: import operator byte2int = operator.itemgetter(0) # ########################################## DRIVER_NAME = 'WMR89' DRIVER_VERSION = "1.0.0" DEFAULT_PORT = '/dev/ttyS0' def loader(config_dict, engine): # @UnusedVariable return WMR89(**config_dict[DRIVER_NAME]) def confeditor_loader(): return WMR89ConfEditor() try: # Test for new-style weewx logging by trying to import weeutil.logger import weeutil.logger import logging log = logging.getLogger(__name__) def logdbg(msg): log.debug(msg) def loginf(msg): log.info(msg) def logerr(msg): log.error(msg) except ImportError: # Old-style weewx logging import syslog def logmsg(level, msg): syslog.syslog(level, 'wmr89: %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 WMR89ProtocolError(weewx.WeeWxIOError): """Used to signal a protocol error condition""" class SerialWrapper(object): """Wraps a serial connection returned from package serial""" # WMR89 specific settings serialconfig = { "baudrate": 128000, "bytesize": serial.EIGHTBITS, "parity": serial.PARITY_NONE, "stopbits": serial.STOPBITS_ONE, "timeout": 2, "xonxoff": False } def __init__(self, port): self.serial_port = serial.Serial(port, **SerialWrapper.serialconfig) logdbg("Opened up serial port %s" % port) def flush_input(self): self.serial_port.flushInput() def queued_bytes(self): return self.serial_port.inWaiting() def read(self, chars=1): _buffer = self.serial_port.read(chars) N = len(_buffer) if N != chars: raise weewx.WeeWxIOError("Expected to read %d chars; got %d instead" % (chars, N)) return _buffer def readAll(self): _buffer = bytearray() while self.serial_port.inWaiting() > 0: _buffer += self.serial_port.read() return _buffer def inWaiting(self): return self.serial_port.inWaiting() def write(self, buf): self.serial_port.write(buf) def closePort(self): self.serial_port.close() self.serial_port = None # ============================================================================== # Class WMR89 # ============================================================================== class WMR89(weewx.drivers.AbstractDevice): """Driver for the Oregon Scientific WMR89 console. The connection to the console will be open after initialization""" DEFAULT_MAP = { 'barometer': 'barometer', 'pressure': 'pressure', 'windSpeed': 'wind_speed', 'windDir': 'wind_dir', 'windGust': 'wind_gust', 'windGustDir': 'wind_gust_dir', 'windBatteryStatus': 'battery_status_wind', 'inTemp': 'temperature_in', 'outTemp': 'temperature_out', 'extraTemp1': 'temperature_1', 'extraTemp2': 'temperature_2', 'extraTemp3': 'temperature_3', 'extraTemp4': 'temperature_4', 'extraTemp5': 'temperature_5', 'extraTemp6': 'temperature_6', 'extraTemp7': 'temperature_7', 'extraTemp8': 'temperature_8', 'inHumidity': 'humidity_in', 'outHumidity': 'humidity_out', 'extraHumid1': 'humidity_1', 'extraHumid2': 'humidity_2', 'extraHumid3': 'humidity_3', 'extraHumid4': 'humidity_4', 'extraHumid5': 'humidity_5', 'extraHumid6': 'humidity_6', 'extraHumid7': 'humidity_7', 'extraHumid8': 'humidity_8', 'inTempBatteryStatus': 'battery_status_in', 'outTempBatteryStatus': 'battery_status_out', 'extraBatteryStatus1': 'battery_status_1', # was batteryStatusTHx 'extraBatteryStatus2': 'battery_status_2', # or batteryStatusTx 'extraBatteryStatus3': 'battery_status_3', 'extraBatteryStatus4': 'battery_status_4', 'extraBatteryStatus5': 'battery_status_5', 'extraBatteryStatus6': 'battery_status_6', 'extraBatteryStatus7': 'battery_status_7', 'extraBatteryStatus8': 'battery_status_8', 'inDewpoint': 'dewpoint_in', 'dewpoint': 'dewpoint_out', 'dewpoint0': 'dewpoint_0', 'dewpoint1': 'dewpoint_1', 'dewpoint2': 'dewpoint_2', 'dewpoint3': 'dewpoint_3', 'dewpoint4': 'dewpoint_4', 'dewpoint5': 'dewpoint_5', 'dewpoint6': 'dewpoint_6', 'dewpoint7': 'dewpoint_7', 'dewpoint8': 'dewpoint_8', 'rain': 'rain', 'rainTotal': 'rain_total', 'rainRate': 'rain_rate', 'hourRain': 'rain_hour', 'rain24': 'rain_24', 'yesterdayRain': 'rain_yesterday', 'rainBatteryStatus': 'battery_status_rain', 'windchill': 'windchill'} def __init__(self, **stn_dict): """Initialize an object of type WMR89. NAMED ARGUMENTS: model: Which station model is this? [Optional. Default is 'WMR89'] port: The serial port of the WMR89. [Optional. Default is '/dev/ttyUSB0'] sensor_map: A dictionary that maps sensor names to emitted observation names. [Optional. Default is given by WMR89.DEFAULT_MAP.] """ loginf('driver version is %s' % DRIVER_VERSION) self.model = stn_dict.get('model', 'WMR89') self.port = stn_dict.get('port', '/dev/ttyUSB0') self.sensor_map = dict(self.DEFAULT_MAP) if 'sensor_map' in stn_dict: self.sensor_map.update(stn_dict['sensor_map']) loginf('sensor map is %s' % self.sensor_map) self.last_rain_total = None # Create the specified port self.serial_wrapper = SerialWrapper(self.port) @property def hardware_name(self): return self.model def closePort(self): """Close the connection to the console. """ self.serial_wrapper.closePort() def genLoopPackets(self): """Generator function that continuously returns loop packets""" while True: # request data if self.serial_wrapper.inWaiting() == 0: self.serial_wrapper.write(b'\xd1\x00') time.sleep(0.5) # read data buf = self.serial_wrapper.readAll() if buf: # The start of each packet is demarcated with the hex sequence 0xf2f2. Separate them, while getting # rid of any zeros self.log_hex('buf', buf) raw_packets = [_f for _f in buf.split(b'\xf2\xf2') if _f] # Loop over each packet for raw_packet in raw_packets: if weewx.debug >= 2: self.log_hex('raw_packet', raw_packet) packet = None if raw_packet[0] == 0xb0: # date/time NOK packet = self._wmr89_time_packet(raw_packet) elif raw_packet[0] == 0xb1: # Rain NOK packet = self._wmr89_rain_packet(raw_packet) elif raw_packet[0] == 0xb2: # Wind OK packet = self._wmr89_wind_packet(raw_packet) elif raw_packet[0] == 0xb4: # Pressure OK packet = self._wmr89_pressure_packet(raw_packet) elif raw_packet[0] == 0xb5: # T/Hum OK packet = self._wmr89_temp_packet(raw_packet) else: logdbg("Invalid data packet (%s)." % raw_packet) if packet: mapped_packet = self._sensors_to_fields(packet, self.sensor_map) if mapped_packet: # print _record yield mapped_packet @staticmethod def _sensors_to_fields(oldrec, sensor_map): # map a record with observation names to a record with db field names if oldrec: newrec = dict() for k in sensor_map: if sensor_map[k] in oldrec: newrec[k] = oldrec[sensor_map[k]] if newrec: newrec['dateTime'] = oldrec['dateTime'] newrec['usUnits'] = oldrec['usUnits'] return newrec return None # ========================================================================== # Oregon Scientific WMR89 utility functions # ========================================================================== def log_hex(self, id, packet): """Log a bytearray as a hexadecimal string""" logdbg("%d, %s, '%s': %s" % (int(time.time() + 0.5), time.asctime(), id, binascii.hexlify(packet))) def _wmr89_wind_packet(self, packet): """Decode a wind packet. Wind speed will be in kph""" ## 0 1 2 3 4 5 6 7 8 9 10 ## b2 0b 00 00 00 00 00 02 7f 01 3e ## ? Wa Wg Wd Wc ? CS? Wa = byte2int(packet[3]) * 0.36 Wg = byte2int(packet[5]) * 0.36 Wd = byte2int(packet[7]) * 22.5 Wc = byte2int(packet[8]) if Wc < 125: Wc = (byte2int(packet[8]) - 32) * 5.0 / 9.0 elif Wc == 125: Wc = None elif Wc > 125: Wc = (((Wc - 255) - 32) * 5.0 / 9.0) _record = { 'wind_speed': Wa, 'wind_dir': Wd, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC, 'wind_gust': Wg, 'windchill': Wc } return _record def _wmr89_rain_packet(self, packet): ## 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ## b1 11 ff fe 00 08 00 22 00 48 0e 01 01 0d 18 03 66 ## b1 11 ff fe 00 11 00 11 00 95 0e 01 01 0d 18 03 ab: 4,3 mm / 11 = 17 ## b1 11 ff fe 00 ca 00 db 00 5f 0e 01 01 0d 18 04 f8: 116,3 mm - 163,1 / db=219 / ## b1 11 ff fe 00 2a 00 3b 00 be 0e 01 01 0d 18 04 17: 270,8mm - 309,6 / 3b=59 / ## ? r/h-- rain last24 Rtot ? ? ? ? ? ? CS? # station units are inch and inch/hr while the internal metric units are # cm and cm/hr. # byte 2-3: rain per hour # fffe = no value if packet[2:4] == b'\xff\xfe': Rh = None else: Rh = (256 * byte2int(packet[2]) + byte2int(packet[3])) * 2.54 / 100 # byte 4-5: actual rain /100 in inch Ra = (256 * byte2int(packet[4]) + byte2int(packet[5])) * 2.54 / 100 # byte 6-7: last 24h /100 in inch R24 = (256 * byte2int(packet[6]) + byte2int(packet[7])) * 2.54 / 100 # byte 8-9: tot /100 in inch Rtot = (256 * byte2int(packet[8]) + byte2int(packet[9])) * 2.54 / 100 _record = { 'rain_rate': Ra, 'rain_total': Rtot, 'rain_hour': Rh, 'rain_24': R24, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } _record['rain'] = weewx.wxformulas.calculate_rain(_record['rain_total'], self.last_rain_total) self.last_rain_total = _record['rain_total'] return _record def _wmr89_temp_packet(self, packet): ## b50b01006c005408fd0286 ## b50b027fff33ff7fff04f0 ## b50b037fff33ff7fff04f1 ## b50b0100da002f0afd02d1 ## 0 1 2 3 4 5 6 7 8 9 10 ## b5 0b 01 00 12 00 54 ff fd 03 23 ## b5 0b 01 00 d7 00 2e 0a fd 02 cd <<-- batterie low ## b5 0b 01 00 d6 00 2e 09 fd 02 cb ## ? sensor temp ? hum dew ? ? ? temp = 256 * byte2int(packet[3]) + byte2int(packet[4]) if temp >= 32768: temp = temp - 65536 temp *= 0.1 # According to specifications the WMR89 humidity range are 25/95% if byte2int(packet[6]) == 254: hum = 95 elif byte2int(packet[6]) == 252: hum = 25 else: hum = float(byte2int(packet[6])) dew = byte2int(packet[7]) if dew == 125: dew = None elif dew > 125: dew -= 256 if byte2int(packet[8]) == 253: heatindex = None else: heatindex = float(byte2int(packet[7])) if packet[2] == 0: _record = { 'humidity_in': hum, 'temperature_in': float(temp), 'dewpoint_in': dew, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } elif packet[2] == 0x01: _record = { 'humidity_out': hum, 'temperature_out': float(temp), 'dewpoint_out': dew, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } elif packet[2] == 0x02: _record = { 'humidity_1': hum, 'temperature_1': float(temp), 'dewpoint_1': dew, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } elif packet[2] == 0x03: _record = { 'humidity_2': hum, 'temperature_2': float(temp), 'dewpoint_2': dew, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } else: _record = None return _record def _wmr89_pressure_packet(self, packet): ## 0 1 2 3 4 5 6 7 8 ## b4 09 27 e9 27 e9 03 02 e0 ## b4 09 27 ea 28 16 03 02 0f ## ? baro press ? ? ? ## weather display? barometric compensation Pr = (256 * byte2int(packet[2]) + byte2int(packet[3])) * 0.1 bar = (256 * byte2int(packet[4]) + byte2int(packet[5])) * 0.1 _record = { 'pressure': Pr, 'barometer': bar, 'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRIC } return _record def _wmr89_time_packet(self, packet): """The (partial) time packet is not used by weewx. However, the last time is saved in case getTime() is called.""" # DateTime='20'+str(ord(packet[5])).zfill(2)+'/'+str(ord(packet[6])).zfill(2)+'/'+str(ord(packet[7])).zfill(2)+' '+str(ord(packet[8])).zfill(2)+':'+str(ord(packet[9])).zfill(2 # min1, min10 = self._get_nibble_data(packet[1:]) # minutes = min1 + ((min10 & 0x07) * 10) # cur = time.gmtime() # self.last_time = time.mktime( # (cur.tm_year, cur.tm_mon, cur.tm_mday, # cur.tm_hour, minutes, 0, # cur.tm_wday, cur.tm_yday, cur.tm_isdst)) return None class WMR89ConfEditor(weewx.drivers.AbstractConfEditor): @property def default_stanza(self): return """ [WMR89] # This section is for the Oregon Scientific WMR89 # Serial port such as /dev/ttyS0, /dev/ttyUSB0, or /dev/cuaU0 port = /dev/ttyUSB0 # The driver to use: driver = weewx.drivers.wmr89 # Sensor map: map from sensor name to observation name [[sensor_map]] """ 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} def modify_config(self, config_dict): print(""" Setting rainRate, windchill, and dewpoint calculations to hardware.""") config_dict.setdefault('StdWXCalculate', {}) config_dict['StdWXCalculate'].setdefault('Calculations', {}) config_dict['StdWXCalculate']['Calculations']['rainRate'] = 'hardware' config_dict['StdWXCalculate']['Calculations']['windchill'] = 'hardware' config_dict['StdWXCalculate']['Calculations']['dewpoint'] = 'hardware' # Define a main entry point for basic testing without the weewx engine. # Invoke this as follows from the weewx root dir: # # PYTHONPATH=bin python bin/weewx/drivers/wmr89.py if __name__ == '__main__': import optparse import syslog # Redefine these so they always use syslog. This ensures running the driver directly will work under # WeeWX V3 and V4. def logmsg(level, msg): syslog.syslog(level, 'wmr89: %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) usage = """Usage: %prog --help %prog --version %prog [--port=PORT]""" syslog.openlog('wmr89', syslog.LOG_PID | syslog.LOG_CONS) syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) weewx.debug = 2 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='The port to use. Default is %s' % DEFAULT_PORT, default=DEFAULT_PORT) options, args = parser.parse_args() if options.version: print("WMR89 driver version %s" % DRIVER_VERSION) exit(0) syslog.syslog(syslog.LOG_DEBUG, "wmr89: Running genLoopPackets()") stn = WMR89(port=options.port) for packet in stn.genLoopPackets(): print(packet)
