Hi! The driver seems to work, but the function byte2int has to be removed. I don't think it's needed since binascii.hexlify and binascii.unxelify sorts the types out correctly. I have attached an update without the byte2int function and here is some output from my tests with Python 2.7 and Python 3.7:
$ PYTHONPATH=/usr/share/weewx python2 wmr89.py --port=/dev/ttyUSB1 {'dateTime': 1588602699, 'inHumidity': 35.0, 'inDewpoint': 6, 'usUnits': 16, 'inTemp': 21.900000000000002} {'dateTime': 1588602700, 'inHumidity': 35.0, 'inDewpoint': 6, 'usUnits': 16, 'inTemp': 21.900000000000002} {'windchill': None, 'dateTime': 1588602725, 'windDir': 315.0, 'windSpeed': 17.28, 'windGust': 18.36, 'usUnits': 16} {'outHumidity': 45.0, 'outTemp': 12.700000000000001, 'usUnits': 16, 'dewpoint': 1, 'dateTime': 1588602725} {'windchill': None, 'dateTime': 1588602726, 'windDir': 315.0, 'windSpeed': 17.28, 'windGust': 18.36, 'usUnits': 16} {'outHumidity': 45.0, 'outTemp': 12.700000000000001, 'usUnits': 16, 'dewpoint': 1, 'dateTime': 1588602726} {'dewpoint1': 4, 'extraTemp1': 19.8, 'dateTime': 1588602754, 'usUnits': 16, 'extraHumid1': 35.0} {'dewpoint1': 4, 'extraTemp1': 19.8, 'dateTime': 1588602755, 'usUnits': 16, 'extraHumid1': 35.0} {'dateTime': 1588602760, 'inHumidity': 35.0, 'inDewpoint': 6, 'usUnits': 16, 'inTemp': 22.0} {'dateTime': 1588602761, 'inHumidity': 35.0, 'inDewpoint': 6, 'usUnits': 16, 'inTemp': 22.0} {'windchill': None, 'dateTime': 1588602775, 'windDir': 292.5, 'windSpeed': 11.879999999999999, 'windGust': 17.28, 'usUnits': 16} {'windchill': None, 'dateTime': 1588602776, 'windDir': 292.5, 'windSpeed': 11.879999999999999, 'windGust': 17.28, 'usUnits': 16} $ PYTHONPATH=/usr/share/weewx python3 wmr89.py --port=/dev/ttyUSB1 {'inTemp': 22.0, 'inHumidity': 35.0, 'inDewpoint': 6, 'dateTime': 1588602820, 'usUnits': 16} {'inTemp': 22.0, 'inHumidity': 35.0, 'inDewpoint': 6, 'dateTime': 1588602821, 'usUnits': 16} {'windSpeed': 14.04, 'windDir': 292.5, 'windGust': 11.879999999999999, 'windchill': None, 'dateTime': 1588602830, 'usUnits': 16} {'windSpeed': 14.04, 'windDir': 292.5, 'windGust': 11.879999999999999, 'windchill': None, 'dateTime': 1588602831, 'usUnits': 16} {'outTemp': 13.0, 'outHumidity': 45.0, 'dewpoint': 2, 'dateTime': 1588602831, 'usUnits': 16} {'windSpeed': 14.04, 'windDir': 292.5, 'windGust': 11.879999999999999, 'windchill': None, 'dateTime': 1588602832, 'usUnits': 16} {'outTemp': 13.0, 'outHumidity': 45.0, 'dewpoint': 2, 'dateTime': 1588602832, 'usUnits': 16} {'extraTemp1': 19.8, 'extraHumid1': 35.0, 'dewpoint1': 4, 'dateTime': 1588602871, 'usUnits': 16} {'extraTemp1': 19.8, 'extraHumid1': 35.0, 'dewpoint1': 4, 'dateTime': 1588602872, 'usUnits': 16} {'inTemp': 22.0, 'inHumidity': 35.0, 'inDewpoint': 6, 'dateTime': 1588602879, 'usUnits': 16} {'inTemp': 22.0, 'inHumidity': 35.0, 'inDewpoint': 6, 'dateTime': 1588602880, 'usUnits': 16} {'windSpeed': 18.0, 'windDir': 292.5, 'windGust': 17.64, 'windchill': None, 'dateTime': 1588602886, 'usUnits': 16} {'windSpeed': 18.0, 'windDir': 292.5, 'windGust': 17.64, 'windchill': None, 'dateTime': 1588602887, 'usUnits': 16} I will continue to analyse and verify the received data compared to the data showed on the weather station display, but this is more related to understanding the data from WMR89 and not making the driver work in WeeWX. Thanks for your time and support! -- 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 weewx-development+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-development/0c5eb37b-0a0a-4f23-b5cd-ff623117802c%40googlegroups.com.
# # Copyright (c) 2012=2020 Will Page <compen...@gmail.com> # and Tom Keffer <tkef...@gmail.com> # # 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 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 mapped_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 = packet[3] * 0.36 Wg = packet[5] * 0.36 Wd = packet[7] * 22.5 Wc = packet[8] if Wc < 125: Wc = (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 * packet[2] + packet[3]) * 2.54 / 100 # byte 4-5: actual rain /100 in inch Ra = (256 * packet[4] + packet[5]) * 2.54 / 100 # byte 6-7: last 24h /100 in inch R24 = (256 * packet[6] + packet[7]) * 2.54 / 100 # byte 8-9: tot /100 in inch Rtot = (256 * packet[8] + 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 * packet[3] + packet[4] if temp >= 32768: temp = temp - 65536 temp *= 0.1 # According to specifications the WMR89 humidity range are 25/95% if packet[6] == 254: hum = 95 elif packet[6] == 252: hum = 25 else: hum = float(packet[6]) dew = packet[7] if dew == 125: dew = None elif dew > 125: dew -= 256 if packet[8] == 253: heatindex = None else: heatindex = float(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 * packet[2] + packet[3]) * 0.1 bar = (256 * packet[4] + 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)