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 [email protected].
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