Hopefully this will help others trying to configure the Oregon Scientific WMR 89/89A weather station.
OS: Running a Raspberry Pi Zero W Rev 1.1 Thanks to all who have contributed to the driver for the Oregon Scientifc WMR 89/89A weather station. THE DRIVER IS AVAILABLE FROM from the original author, Matthew Wall at: https://github.com/matthewwall/weewx-wmr89. I've had to make a few additions/amendments to the driver to get it working so I thought I'd add a post in case anyone else is having similar difficulties. I've also attached the modified driver file I'm using if this is easier, but the changes are minor so you may prefer to make them yourself. [SEE ATTACHED modified 'wmr89.py'. Full installation procedure outlined below.] [1] CHANGES to original 'wmr89.py' (a) on line 222, change 'ord(x)' to 'ord(x[0])' (b) on line 107, change 'logdbg("sensors: $s" % pkt)' to 'logdbg("sensors: %s" % pkt)' (c) on line 82, change 'rainRate' to 'rain' (d) on line 113, change 'self._calculate_rain_delta(packet)' to #if packet contains rain data then calculate rain increment if self.sensor_map['rain_total'] in packet: self._calculate_rain_delta(packet) Thats all. [2] FULL INSTALLATION DETAILS The full configuration for the WMR89/89A I followed was: (I have assumed that you have already downloaded and installed the weewx software (Debian version as I'm working on a Raspberry Pi. If you haven't done this yet, during the installation choose the 'Simulator' weather station, although this is not essential.) STEP 1: Download the .zip file from https://github.com/matthewwall/weewx-wmr89, by entering the following commands (careful with 0's (zeros) and O's (capital letter 'oh')) wget -O weewx-wmr89.zip https://github.com/matthewwall/weewx-wmr89/archive/master.zip sudo wee_extension --install=weewx-wmr89.zip STEP 2: Add the CP210X kernel module, by entering the commands: sudo modprobe cp210x sudo sh -c 'echo 0fde ca0a > /sys/bus/usb-serial/drivers/cp210x/new_id' STEP 3: The original driver for the WMR 89/89A is at '/usr/share/weewx/user/wmr89.py'. Either modifiy this file (type 'sudo nano wmr89.py' having changed to the directory '/usr/share/weewx/user/'), or copy the updated 'wmr89.py' attached to this post to the directory '/usr/share/weewx/user/', overwriting the original 'wmr89.py'. STEP 4: Reconfigure weewx. [Note, when selecting a station, choose 'WMR89' and the connection should be too device '/dev/ttyUSB0'.] sudo wee_config --reconfigure STEP 5: Run weewx by entering the command sudo weewxd /etc/weewx/weewx.conf RESULT: You should see a stream of output to the terminal with readings from the weather station. At this point you can stop weewx and run it rather as a daemon, using (see weewx User Guide) sudo /etc/init.d/weewx start -- You received this message because you are subscribed to the Google Groups "weewx-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to weewx-user+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/env python # # Copyright 2018 Matthew Wall # See the file LICENSE.txt for your rights. # # Credits: # Marek for discovering the baud rate that enabled everyone to decode the # serial communications, and for providing a working implementation that # made development of this driver much easier! # # Update: 30 September 2018, Keith Mitchell """Driver for Oregon Scientific WMR89 weather stations The WMR89 uses a CP210x usb-to-serial adapter. This driver communicates directly using python serial, not using pyusb or other USB mechanisms. This means the system must map the device to a serial port, such as /dev/ttyUSB0. This should happen automatically if the system has loaded the cp210x kernel module. To do so manually: sudo modprobe cp210x sudo sh -c 'echo 0fde ca0a > /sys/bus/usb-serial/drivers/cp210x/new_id then the device should show up as /dev/ttyUSB0. """ from __future__ import with_statement import serial import syslog import time import weewx.drivers DRIVER_NAME = 'WMR89' DRIVER_VERSION = '0.3' def loader(config_dict, _): return WMR89Driver(**config_dict[DRIVER_NAME]) def confeditor_loader(): return WMR89ConfEditor() 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) def _fmt(x): return ' '.join(["%0.2X" % ord(c) for c in x]) class WMR89Driver(weewx.drivers.AbstractDevice): """weeWX driver that communicates with a WMR89 station. port - serial port [Required. Default is /dev/ttyUSB0] """ # map sensor values to the database schema fields # the default map is for the wview schema DEFAULT_MAP = { 'pressure': 'pressure', 'windSpeed': 'wind_avg', 'windDir': 'wind_dir', 'windGust': 'wind_gust', 'windchill': 'wind_chill', 'inTemp': 'temperature_in', 'outTemp': 'temperature_out', 'inHumidity': 'humidity_in', 'outHumidity': 'humidity_out', 'dewpoint': 'dewpoint_in', 'dewpoint': 'dewpoint_out', 'rain_total': 'rain_total', 'rain': 'rain_rate'} def __init__(self, **stn_dict): loginf('driver version is %s' % DRIVER_VERSION) self.port = stn_dict.get('port', Station.DEFAULT_PORT) loginf('using serial port %s' % self.port) 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 = None 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 DRIVER_NAME def genLoopPackets(self): for pkt in self.station.get_data(): logdbg("sensors: %s" % pkt) packet = self._map_packet(pkt) logdbg("mapped: %s" % packet) if packet: packet['dateTime'] = int(time.time() + 0.5) packet['usUnits'] = weewx.METRIC # if packet contains rain data then calculate rain increment if self.sensor_map['rain_total'] in packet: self._calculate_rain_delta(packet) yield packet def _map_packet(self, pkt): # map sensor names to database fields # map hardware names to the requested database schema names p = dict() for label in self.sensor_map: if self.sensor_map[label] in pkt: p[label] = pkt[self.sensor_map[label]] return p def _calculate_rain_delta(self, packet): # calculate a rain delta given accumulated rain packet['rain'] = weewx.wxformulas.calculate_rain( packet['rain_total'], self.last_rain) self.last_rain = packet['rain_total'] class Station(object): DEFAULT_PORT = '/dev/ttyUSB0' MARKER = 'f2f2'.decode('hex') HEARTBEAT = 'd100'.decode('hex') def __init__(self, port): self.port = port 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) self.serial_port = serial.Serial( port=self.port, baudrate=128000, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, xonxoff=False, timeout=2) 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 waiting(self): return self.serial_port.inWaiting() def send_heartbeat(self): self.serial_port.write(Station.HEARTBEAT) def read(self): return self.serial_port.read() def get_data(self): # generator that returns data as dict. data come to us as multiple # lines of bytes from the station. decode each line and return the # corresponding data as soon as we receive it. pkt = dict() while True: line = '' if self.waiting() == 0: self.send_heartbeat() time.sleep(0.5) while self.waiting(): c = self.read() line = line + c if len(line): a = line.split(Station.MARKER) a = filter(None, a) for i in range(len(a)): if weewx.debug > 1: logdbg("raw: %s" % _fmt(a[i])) x = a[i][0].encode('hex') y = a[i][2].encode('hex') if x == 'b0': # ignore the station's date and time if weewx.debug > 1: datetime = Station.decode_datetime(a[i]) now = time.time() weeutil.timestamp_to_string(now) logdbg("datetime: %s (%s)" % (datetime, now)) elif x == 'b1': rr, rh, r24, rt = Station.decode_rain(a[i]) pkt['rain_total'] = rt pkt['rain_rate'] = rr elif x == 'b2': avg, gust, wdir, chill = Station.decode_wind(a[i]) pkt['wind_speed'] = avg pkt['wind_gust'] = gust pkt['wind_dir'] = wdir pkt['wind_chill'] = chill elif x == 'b4': pkt['pressure'] = Station.decode_pressure(a[i]) elif x == 'b5': if y == '00': t, h = Station.decode_inside_th(a[i]) pkt['temperature_in'] = t pkt['humidity_in'] = h elif y == '01': t, h = Station.decode_outside_th(a[i]) pkt['temperature_out'] = t pkt['humidity_out'] = h else: loginf("unknown packet type %0.2X: %s" % (ord(x[0]), _fmt(a[i]))) if pkt: yield pkt pkt = dict() time.sleep(0.5) @staticmethod def decode_datetime(x): y = ord(x[5]) + 2000 m = ord(x[6]) d = ord(x[8]) t = ord(x[9]) return "%s.%s.%s:%s" % (y, m, d, t) @staticmethod def decode_inside_th(x): # temperature in degree C, humidity in percent t = 0.1 * (256 * ord(x[3]) + ord(x[4])) h = ord(x[6]) return t, h @staticmethod def decode_outside_th(x): # temperature in degree C, humidity in percent t = 256 * ord(x[3]) + ord(x[4]) if t >= 32768: t = t - 65536 t = 0.1 * t h = ord(x[6]) return t, h @staticmethod def decode_wind(x): # speed in km/h avg = 0.36 * ord(x[3]) gust = 0.36 * ord(x[5]) wdir = 22.5 * ord(x[7]) chill = ord(x[8]) if chill < 125: chill = (ord(x[8]) - 32) * 5 / 9 elif chill > 125: chill = ((chill - 255) - 32) * 5 / 9 elif chill == 125: chill = None return avg, gust, wdir, chill @staticmethod def decode_pressure(x): # pressure is units of mbar return 0.1 * (256 * ord(x[2]) + ord(x[3])) @staticmethod def decode_rain(x): # hardare reports inches and inch/hour, convert to cm and cm/hour # rain in past hour in inches rh = 0.01 * (256 * ord(x[2]) + ord(x[3])) * 2.54 if x[2:4].encode('hex') == 'fffe': rh = None # rain rate in inch/hour rr = 0.01 * (256 * ord(x[4]) + ord(x[5])) * 2.54 # last 24 hours in inches r24 = 0.01 * (256 * ord(x[6]) + ord(x[7])) * 2.54 # rain total in inches rt = 0.01 * (256 * ord(x[8]) + ord(x[9])) * 2.54 return rr, rh, r24, rt @staticmethod def set_baud_rate(): import array import sys import fcntl fd = 0 baudrate = 128000 TCGETS2 = 0x802C542A TCSETS2 = 0x402C542B BOTHER = 0o010000 CBAUD = 0o010017 buf = array.array('i', [0] * 64) # is 44 really fcntl.ioctl(fd, TCGETS2, buf) buf[2] &= ~CBAUD buf[2] |= BOTHER buf[9] = buf[10] = baudrate assert(fcntl.ioctl(fd, TCSETS2, buf)==0) fcntl.ioctl(fd, TCGETS2, buf) if buf[9] != baudrate or buf[10] != baudrate: print("failed. speed is %d %d" % (buf[9],buf[10])) sys.exit(1) class WMR89ConfEditor(weewx.drivers.AbstractConfEditor): @property def default_stanza(self): return """ [WMR89] # This section is for the Oregon Scientific WMR89 weather stations. # Serial port such as /dev/ttyS0, /dev/ttyUSB0, or /dev/cua0 port = %s # The driver to use: driver = user.wmr89 """ % Station.DEFAULT_PORT def prompt_for_settings(self): print "Specify the serial port on which the station is connected, for" print "example: /dev/ttyUSB0 or /dev/ttyS0 or /dev/cua0." port = self._prompt('port', Station.DEFAULT_PORT) return {'port': port} # define a main entry point for basic testing. invoke this as follows from # the weewx root dir: # # PYTHONPATH=bin python bin/weewx/drivers/wmr89.py if __name__ == '__main__': import optparse usage = """%prog [options] [--help]""" syslog.openlog('wmr89', 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=Station.DEFAULT_PORT) (options, args) = parser.parse_args() if options.version: print "%s driver version %s" % (DRIVER_NAME, DRIVER_VERSION) exit(0) with Station(options.port) as station: for pkt in station.get_data(): print time.time(), pkt