Hi folks,

I could not see a formal procedure to submit a patch for weewx.
So I though I would add it here for comments. 

This is a request for comments, for driver support for the WMR89a Weather 
Station from Oregon Scientific. The actual sensor data is captured an 
Arduino 
sketch, and then relayed to WeeWX over a USB Serial connection. 

This driver does *not* connect directly to the WMR89 basestation, so any 
stats
calculated by the basestation are not collected.

WRM89 sensors supported
 * Oregon-THGR810 Temp-Hygro
 * Oregon-WGR800 Anemometer

This is the initial RFC, circuit layout and Rainsensor support to follow. 

Ray K
>From 107da0baf8805200a71fbb4956b7a0d3cbb153a3 Mon Sep 17 00:00:00 2001
From: Ray Kinsella <raykinsell...@gmail.com>
Date: Thu, 30 Nov 2017 17:31:39 +0000
Subject: [PATCH] wmr89: New driver supporting the OS WMR89a

New driver for the WMR89a Weather Station from Oregon Scientific.
The actual sensor daata is captured an Arduino firmware, and then
relayed to WeeWX over a USB Serial connection. The data is encoded
in a way very similar to other OS devices, with minor differences
like byte order and checksum calculation.

This driver does *not* connect to the WMR89 basestation, so any stats
collected by the basestation are not collected.
WRM89a sensors supported
 * Oregon-THGR810 Temp-Hygro
 * Oregon-WGR800 Anemometer
---
 bin/weewx/drivers/wmr9x8.py | 312 +++++++++++++++++++++++-----------
 examples/wmr89/wmr89.ino    | 405 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 614 insertions(+), 103 deletions(-)
 create mode 100755 examples/wmr89/wmr89.ino

diff --git a/bin/weewx/drivers/wmr9x8.py b/bin/weewx/drivers/wmr9x8.py
index 01db44a..226d098 100644
--- a/bin/weewx/drivers/wmr9x8.py
+++ b/bin/weewx/drivers/wmr9x8.py
@@ -6,7 +6,7 @@
 """Classes and functions for interfacing with Oregon Scientific WM-918, WMR9x8,
 and WMR-968 weather stations
 
-See 
+See
   http://wx200.planetfall.com/wx200.txt
   http://www.qsl.net/zl1vfo/wx200/wx200.txt
   http://ed.toton.org/projects/weather/station-protocol.txt
@@ -24,8 +24,8 @@ import operator
 import syslog
 
 import serial
-
 import weewx.drivers
+import collections
 
 from math import exp
 
@@ -33,6 +33,11 @@ DRIVER_NAME = 'WMR9x8'
 DRIVER_VERSION = "3.2.2"
 DEFAULT_PORT = '/dev/ttyS0'
 
+# pstart, plen is payload start and len positions
+# cstart, clen is checksum start and len positions
+ModelSettings = collections.namedtuple(
+    'ModelSettings','pstart plen psize cstart clen csum decoder gettype')
+
 def loader(config_dict, engine):  # @UnusedVariable
     return WMR9x8(**config_dict[DRIVER_NAME])
 
@@ -64,22 +69,82 @@ def channel_decoder(chan):
         raise WMR9x8ProtocolError("Bad channel number %d" % chan)
     return outchan
 
+def get_nibble_data(packet):
+    nibbles = bytearray()
+    for byte in packet:
+        nibbles.extend([(byte & 0x0F), (byte & 0xF0) >> 4])
+    return nibbles
+
+def get_type_wmr89(vals):
+
+    stype = ((vals[0] * 0x100) + vals[1])
+    if stype in model_settings["wmr89"].psize:
+        return stype
+
+def get_type_wmr9x8(vals):
+
+    if (vals[0] == 0xFF and vals[1] == 0xFF and vals[2]
+        in model_settings["wmr9x8"].psize):
+        return vals[2]
+
+def get_type_wm918(vals):
+
+    if vals[0] in model_settings["wmr89"].psize:
+        return stype
+
+def checksum(vals, settings):
+
+    csum = reduce(operator.add, pdata[settings.cstart:settings.clen]) & 0xFF
+
+    return csum == vals[settings.clen]
+
+def checksum_wmr89(vals, settings):
+
+    nibbles = get_nibble_data(vals)
+
+    calc_csum = reduce(operator.add,
+                       nibbles[settings.cstart:settings.clen]) & 0xFF
+    calc_csum = (calc_csum & 0x0F) << 4 | (calc_csum & 0xF0) >> 4
+
+    nibbles = nibbles[settings.clen: settings.clen + 2]
+
+    bits_csum = nibbles[0] << 4 | nibbles[1]
+
+    return calc_csum == bits_csum
+
 # Dictionary that maps a measurement code, to a function that can decode it:
 # packet_type_decoder_map and packet_type_size_map are filled out using the @<type>_registerpackettype
 # decorator below
-wmr9x8_packet_type_decoder_map = {}
-wmr9x8_packet_type_size_map = {}
 
-wm918_packet_type_decoder_map = {}
-wm918_packet_type_size_map = {}
+# Model spefic settings describing the payload and the checksum.
+model_settings = {
+    "wmr9x8" : ModelSettings(pstart = 2, plen = None, cstart = 0 ,clen = -1,
+                             csum=checksum, decoder = {}, psize = {},
+                             gettype = get_type_wmr9x8),
+    "wmr89": ModelSettings(pstart = 2, plen = -1, cstart = 0, clen = -3,
+                           csum=checksum_wmr89, decoder = {}, psize = {},
+                           gettype = get_type_wmr89),
+    "wm918" : ModelSettings(pstart = 0, plen = None, cstart = 0, clen = -1,
+                            csum=checksum, decoder = {}, psize = {},
+                            gettype = get_type_wm918)
+}
 
 def wmr9x8_registerpackettype(typecode, size):
     """ Function decorator that registers the function as a handler
         for a particular packet type.  Parameters to the decorator
         are typecode and size (in bytes). """
     def wrap(dispatcher):
-        wmr9x8_packet_type_decoder_map[typecode] = dispatcher
-        wmr9x8_packet_type_size_map[typecode] = size
+        model_settings["wmr9x8"].decoder[typecode] = dispatcher
+        model_settings["wmr9x8"].psize[typecode] = size
+    return wrap
+
+def wmr89_registerpackettype(sensorcode, size):
+    """ Function decorator that registers the function as a handler
+        for a particular packet type.  Parameters to the decorator
+        are sensorcode. """
+    def wrap(dispatcher):
+        model_settings["wmr89"].decoder[sensorcode] = dispatcher
+        model_settings["wmr89"].psize[sensorcode] = size
     return wrap
 
 def wm918_registerpackettype(typecode, size):
@@ -87,15 +152,15 @@ def wm918_registerpackettype(typecode, size):
         for a particular packet type.  Parameters to the decorator
         are typecode and size (in bytes). """
     def wrap(dispatcher):
-        wm918_packet_type_decoder_map[typecode] = dispatcher
-        wm918_packet_type_size_map[typecode] = size
+        model_settings["wm918"].decoder[typecode] = dispatcher
+        model_settings["wm918"].psize[typecode] = size
     return wrap
 
 
 class SerialWrapper(object):
     """Wraps a serial connection returned from package serial"""
 
-    def __init__(self, port):
+    def __init__(self, port, baudrate):
         self.port = port
         # WMR9x8 specific settings
         self.serialconfig = {
@@ -103,7 +168,8 @@ class SerialWrapper(object):
             "parity": serial.PARITY_NONE,
             "stopbits": serial.STOPBITS_ONE,
             "timeout": None,
-            "rtscts": 1
+            "rtscts": 1,
+            "baudrate": baudrate,
         }
 
     def flush_input(self):
@@ -202,7 +268,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
         model: Which station model is this?
         [Optional. Default is 'WMR968']
 
-        port: The serial port of the WM918/WMR918/WMR968.
+        port: The serial port of the WMR89/WMR918/WMR968.
         [Required if serial communication]
 
         baudrate: Baudrate of the port.
@@ -240,65 +306,58 @@ class WMR9x8(weewx.drivers.AbstractDevice):
         self.port.closePort()
 
     def genLoopPackets(self):
-        """Generator function that continuously returns loop packets"""
+
+        preambleBufSize = 3
+        preambleBuf = []
         buf = []
-        # We keep a buffer the size of the largest supported packet
-        wmr9x8max = max(wmr9x8_packet_type_size_map.items(), key=operator.itemgetter(1))[1]
-        wm918max = max(wm918_packet_type_size_map.items(), key=operator.itemgetter(1))[1]
-        preBufferSize = max(wmr9x8max, wm918max)
+
+        """Generator function that continuously returns loop packets"""
+
         while True:
-            buf.extend(map(ord, self.port.read(preBufferSize - len(buf))))
-            # WMR-9x8/968 packets are framed by 0xFF characters
-            if buf[0] == 0xFF and buf[1] == 0xFF and buf[2] in wmr9x8_packet_type_size_map:
-                # Look up packet type, the expected size of this packet type
-                ptype = buf[2]
-                psize = wmr9x8_packet_type_size_map[ptype]
-                # Capture only the data belonging to this packet
-                pdata = buf[0:psize]
-                if weewx.debug >= 2:
-                    self.log_packet(pdata)
-                # Validate the checksum
-                sent_checksum = pdata[-1]
-                calc_checksum = reduce(operator.add, pdata[0:-1]) & 0xFF
-                if sent_checksum == calc_checksum:
-                    logdbg("Received WMR9x8 data packet.")
-                    payload = pdata[2:-1]
-                    _record = wmr9x8_packet_type_decoder_map[ptype](self, payload)
-                    _record = self._sensors_to_fields(_record, self.sensor_map)
-                    if _record is not None:
-                        yield _record
-                    # Eliminate all packet data from the buffer
-                    buf = buf[psize:]
-                else:
-                    logdbg("Invalid data packet (%s)." % pdata)
-                    # Drop the first byte of the buffer and start scanning again
-                    buf.pop(0)
-            # WM-918 packets have no framing
-            elif buf[0] in wm918_packet_type_size_map:
-                # Look up packet type, the expected size of this packet type
-                ptype = buf[0]
-                psize = wm918_packet_type_size_map[ptype]
-                # Capture only the data belonging to this packet
-                pdata = buf[0:psize]
-                # Validate the checksum
-                sent_checksum = pdata[-1]
-                calc_checksum = reduce(operator.add, pdata[0:-1]) & 0xFF
-                if sent_checksum == calc_checksum:
-                    logdbg("Received WM-918 data packet.")
-                    payload = pdata[0:-1] # send all of packet but crc
-                    _record = wm918_packet_type_decoder_map[ptype](self, payload)
-                    _record = self._sensors_to_fields(_record, self.sensor_map)
-                    if _record is not None:
-                        yield _record
-                    # Eliminate all packet data from the buffer
-                    buf = buf[psize:]
-                else:
-                    logdbg("Invalid data packet (%s)." % pdata)
-                    # Drop the first byte of the buffer and start scanning again
-                    buf.pop(0)
+            preambleBuf.extend(
+                map(ord, self.port.read(preambleBufSize - len(preambleBuf))))
+
+            psize = 0
+
+            for model, settings in model_settings.iteritems():
+                ptype = settings.gettype(preambleBuf)
+
+                if ptype is not None:
+                    logdbg("Received " + model + " data packet.")
+
+                    psize = settings.psize[ptype]
+                    decoder = settings.decoder[ptype]
+
+            if ptype is None:
+                logdbg("Invalid data packet")
+                preambleBuf.pop(0)
+                continue
+
+
+            #At this point the rest of the packet should be waiting to be read.
+            #Only read to the length of the packet, leave the rest in the buffer
+            buf = preambleBuf
+            buf.extend(map(ord, self.port.read(psize - len(preambleBuf))))
+
+            preambleBuf = []
+
+            if weewx.debug >= 2:
+                self.log_packet(buf)
+
+            if settings.csum(buf, settings):
+
+                #Trim the packet len
+                payload = buf[settings.pstart:settings.plen]
+                _record = decoder(self, payload)
+                _record = self._sensors_to_fields(_record, self.sensor_map)
+
+                if _record is not None:
+                    yield _record
+                # Eliminate all packet data from the buffer
+                buf = []
             else:
-                logdbg("Advancing buffer by one for the next potential packet")
-                buf.pop(0)
+                logdbg("Checksum failed, invalid data packet (%s)." % buf)
+                # Drop the first byte of the buffer and start scanning again
 
     @staticmethod
     def _sensors_to_fields(oldrec, sensor_map):
@@ -327,24 +386,71 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
         if connection_type == "serial":
             port = stn_dict['port']
-            return SerialWrapper(port)
+            baudrate = stn_dict['baudrate']
+            return SerialWrapper(port, baudrate)
         raise weewx.UnsupportedFeature(stn_dict['type'])
 
-    @staticmethod
-    def _get_nibble_data(packet):
-        nibbles = bytearray()
-        for byte in packet:
-            nibbles.extend([(byte & 0x0F), (byte & 0xF0) >> 4])
-        return nibbles
 
     def log_packet(self, packet):
-        packet_str = ','.join(["x%x" % v for v in packet])
+        packet_str = ','.join(["{:02x}".format(v) for v in packet])
         print "%d, %s, %s" % (int(time.time() + 0.5), time.asctime(), packet_str)
 
+    @wmr89_registerpackettype(sensorcode=0x9148, size=10)
+    def _wmr89_wind_packet(self, packet):
+        """Decode a wind packet. Wind speed will be in kph"""
+        chan, null, null, status,  dir1, null, null, gust10th, gust1, gust10, avg10th, avg1, avg10, _ = get_nibble_data(packet[:])
+
+        battery = (status & 0x04) >> 2
+
+        # The console returns wind speeds in m/s. Our metric system requires
+        # kph, so the result needs to be multiplied by 3.6
+        _record = {
+            'battery_status_wind': battery,
+            'wind_speed': ((avg10th / 10.0) + avg1 + (avg10 * 10)) * 3.6,
+            'wind_dir': (dir1 & 0x0f) * 360.0 / 16.0,
+            'dateTime': int(time.time() + 0.5),
+            'usUnits': weewx.METRIC
+        }
+        # Sometimes the station emits a wind gust that is less than the
+        # average wind. Ignore it if this is the case.
+        windGustSpeed = ((gust10th / 10.0) + gust1 + (gust10 * 10)) * 3.6
+        if windGustSpeed >= _record['wind_speed']:
+            _record['wind_gust'] = windGustSpeed
+
+        return _record
+
+    @wmr89_registerpackettype(sensorcode=0x8f42, size=9)
+    def _wmr89_thermohydro_packet(self, packet):
+        chan, null, null, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, _, _ = get_nibble_data(packet[:])
+
+        chan = channel_decoder(chan)
+
+        battery = (status & 0x04) >> 2
+
+        _record = {
+            'dateTime': int(time.time() + 0.5),
+            'usUnits': weewx.METRIC,
+            'battery_status_%d' % chan :battery
+        }
+
+        _record['humidity_out'] = hum1 + (hum10 * 10)
+
+        tempoverunder = temp100etc & 0x04
+        if not tempoverunder:
+            temp = (temp10th / 10.0) + temp1 + (temp10 * 10) + ((temp100etc & 0x03) * 100)
+            if temp100etc & 0x08:
+                temp = -temp
+            _record['temperature_out'] = temp
+        else:
+            _record['temperature_out'] = None
+
+        return _record
+
     @wmr9x8_registerpackettype(typecode=0x00, size=11)
     def _wmr9x8_wind_packet(self, packet):
+
         """Decode a wind packet. Wind speed will be in kph"""
-        null, status, dir1, dir10, dir100, gust10th, gust1, gust10, avg10th, avg1, avg10, chillstatus, chill1, chill10 = self._get_nibble_data(packet[1:]) # @UnusedVariable
+        null, status, dir1, dir10, dir100, gust10th, gust1, gust10, avg10th, avg1, avg10, chillstatus, chill1, chill10 = get_nibble_data(packet[1:]) # @UnusedVariable
 
         battery = (status & 0x04) >> 2
 
@@ -372,12 +478,12 @@ class WMR9x8(weewx.drivers.AbstractDevice):
             _record['windchill'] = chill
         else:
             _record['windchill'] = None
-        
+
         return _record
 
     @wmr9x8_registerpackettype(typecode=0x01, size=16)
     def _wmr9x8_rain_packet(self, packet):
-        null, status, cur1, cur10, cur100, tot10th, tot1, tot10, tot100, tot1000, yest1, yest10, yest100, yest1000, totstartmin1, totstartmin10, totstarthr1, totstarthr10, totstartday1, totstartday10, totstartmonth1, totstartmonth10, totstartyear1, totstartyear10 = self._get_nibble_data(packet[1:]) # @UnusedVariable
+        null, status, cur1, cur10, cur100, tot10th, tot1, tot10, tot100, tot1000, yest1, yest10, yest100, yest1000, totstartmin1, totstartmin10, totstarthr1, totstarthr10, totstartday1, totstartday10, totstartmonth1, totstartmonth10, totstartyear1, totstartyear10 = get_nibble_data(packet[1:]) # @UnusedVariable
         battery = (status & 0x04) >> 2
 
         # station units are mm and mm/hr while the internal metric units are
@@ -399,7 +505,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wmr9x8_registerpackettype(typecode=0x02, size=9)
     def _wmr9x8_thermohygro_packet(self, packet):
-        chan, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10 = self._get_nibble_data(packet[1:])
+        chan, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10 = get_nibble_data(packet[1:])
 
         chan = channel_decoder(chan)
 
@@ -430,7 +536,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wmr9x8_registerpackettype(typecode=0x03, size=9)
     def _wmr9x8_mushroom_packet(self, packet):
-        _, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10 = self._get_nibble_data(packet[1:])
+        _, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10 = get_nibble_data(packet[1:])
 
         battery = (status & 0x04) >> 2
         _record = {
@@ -448,7 +554,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
             _record['temperature_out'] = temp
         else:
             _record['temperature_out'] = None
-            
+
         dewunder = bool(status & 0x01)
         # If dew point is valid, save it.
         if not dewunder:
@@ -458,7 +564,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wmr9x8_registerpackettype(typecode=0x04, size=7)
     def _wmr9x8_therm_packet(self, packet):
-        chan, status, temp10th, temp1, temp10, temp100etc = self._get_nibble_data(packet[1:])
+        chan, status, temp10th, temp1, temp10, temp100etc = get_nibble_data(packet[1:])
 
         chan = channel_decoder(chan)
         battery = (status & 0x04) >> 2
@@ -477,7 +583,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wmr9x8_registerpackettype(typecode=0x05, size=13)
     def _wmr9x8_in_thermohygrobaro_packet(self, packet):
-        null, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10, baro1, baro10, wstatus, null2, slpoff10th, slpoff1, slpoff10, slpoff100 = self._get_nibble_data(packet[1:]) # @UnusedVariable
+        null, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10, baro1, baro10, wstatus, null2, slpoff10th, slpoff1, slpoff10, slpoff100 = get_nibble_data(packet[1:]) # @UnusedVariable
 
         battery = (status & 0x04) >> 2
         hum = hum1 + (hum10 * 10)
@@ -495,12 +601,12 @@ class WMR9x8(weewx.drivers.AbstractDevice):
             dew = dew1 + (dew10 * 10)
         else:
             dew = None
-            
+
         rawsp = ((baro10 & 0xF) << 4) | baro1
         sp = rawsp + 795
         pre_slpoff = (slpoff10th / 10.0) + slpoff1 + (slpoff10 * 10) + (slpoff100 * 100)
         slpoff = (1000 + pre_slpoff) if pre_slpoff < 400.0 else pre_slpoff
-        
+
         _record = {
             'battery_status_in': battery,
             'humidity_in': hum,
@@ -516,7 +622,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wmr9x8_registerpackettype(typecode=0x06, size=14)
     def _wmr9x8_in_ext_thermohygrobaro_packet(self, packet):
-        null, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10, baro1, baro10, baro100, wstatus, null2, slpoff10th, slpoff1, slpoff10, slpoff100, slpoff1000 = self._get_nibble_data(packet[1:]) # @UnusedVariable
+        null, status, temp10th, temp1, temp10, temp100etc, hum1, hum10, dew1, dew10, baro1, baro10, baro100, wstatus, null2, slpoff10th, slpoff1, slpoff10, slpoff100, slpoff1000 = get_nibble_data(packet[1:]) # @UnusedVariable
 
         battery = (status & 0x04) >> 2
         hum = hum1 + (hum10 * 10)
@@ -538,7 +644,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
         rawsp = ((baro100 & 0x01) << 8) | ((baro10 & 0xF) << 4) | baro1
         sp = rawsp + 600
         slpoff = (slpoff10th / 10.0) + slpoff1 + (slpoff10 * 10) + (slpoff100 * 100) + (slpoff1000 * 1000)
-        
+
         _record = {
             'battery_status_in': battery,
             'humidity_in': hum,
@@ -556,7 +662,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
     def _wmr9x8_time_packet(self, packet):
         """The (partial) time packet is not used by weewx.
         However, the last time is saved in case getTime() is called."""
-        min1, min10 = self._get_nibble_data(packet[1:])
+        min1, min10 = get_nibble_data(packet[1:])
         minutes = min1 + ((min10 & 0x07) * 10)
 
         cur = time.gmtime()
@@ -570,7 +676,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
     def _wmr9x8_clock_packet(self, packet):
         """The clock packet is not used by weewx.
         However, the last time is saved in case getTime() is called."""
-        min1, min10, hour1, hour10, day1, day10, month1, month10, year1, year10 = self._get_nibble_data(packet[1:])
+        min1, min10, hour1, hour10, day1, day10, month1, month10, year1, year10 = get_nibble_data(packet[1:])
         year = year1 + (year10 * 10)
         # The station initializes itself to "1999" as the first year
         # Thus 99 = 1999, 00 = 2000, 01 = 2001, etc.
@@ -590,8 +696,8 @@ class WMR9x8(weewx.drivers.AbstractDevice):
     @wm918_registerpackettype(typecode=0xcf, size=27)
     def _wm918_wind_packet(self, packet):
         """Decode a wind packet. Wind speed will be in m/s"""
-        gust10th, gust1, gust10, dir1, dir10, dir100, avg10th, avg1, avg10, avgdir1, avgdir10, avgdir100 = self._get_nibble_data(packet[1:7])
-        _chill10, _chill1 = self._get_nibble_data(packet[16:17])
+        gust10th, gust1, gust10, dir1, dir10, dir100, avg10th, avg1, avg10, avgdir1, avgdir10, avgdir100 = get_nibble_data(packet[1:7])
+        _chill10, _chill1 = get_nibble_data(packet[16:17])
 
         # The console returns wind speeds in m/s. Our metric system requires
         # kph, so the result needs to be multiplied by 3.6
@@ -611,7 +717,7 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wm918_registerpackettype(typecode=0xbf, size=14)
     def _wm918_rain_packet(self, packet):
-        cur1, cur10, cur100, _stat, yest1, yest10, yest100, yest1000, tot1, tot10, tot100, tot1000 = self._get_nibble_data(packet[1:7])
+        cur1, cur10, cur100, _stat, yest1, yest10, yest100, yest1000, tot1, tot10, tot100, tot1000 = get_nibble_data(packet[1:7])
 
         # It is reported that total rainfall is biased by +0.5 mm
         _record = {
@@ -634,8 +740,8 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wm918_registerpackettype(typecode=0x8f, size=35)
     def _wm918_humidity_packet(self, packet):
-        hum1, hum10 = self._get_nibble_data(packet[8:9])
-        humout1, humout10 = self._get_nibble_data(packet[20:21])
+        hum1, hum10 = get_nibble_data(packet[8:9])
+        humout1, humout10 = get_nibble_data(packet[20:21])
 
         hum = hum1 + (hum10 * 10)
         humout = humout1 + (humout10 * 10)
@@ -649,8 +755,8 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wm918_registerpackettype(typecode=0x9f, size=34)
     def _wm918_therm_packet(self, packet):
-        temp10th, temp1, temp10, null = self._get_nibble_data(packet[1:3]) # @UnusedVariable
-        tempout10th, tempout1, tempout10, null = self._get_nibble_data(packet[16:18]) # @UnusedVariable
+        temp10th, temp1, temp10, null = get_nibble_data(packet[1:3]) # @UnusedVariable
+        tempout10th, tempout1, tempout10, null = get_nibble_data(packet[16:18]) # @UnusedVariable
 
         temp = (temp10th / 10.0) + temp1 + ((temp10 & 0x7) * 10)
         temp *= -1 if (temp10 & 0x08) else 1
@@ -667,8 +773,8 @@ class WMR9x8(weewx.drivers.AbstractDevice):
 
     @wm918_registerpackettype(typecode=0xaf, size=31)
     def _wm918_baro_dew_packet(self, packet):
-        baro1, baro10, baro100, baro1000, slp10th, slp1, slp10, slp100, slp1000, fmt, prediction, trend, dewin1, dewin10 = self._get_nibble_data(packet[1:8]) # @UnusedVariable
-        dewout1, dewout10 = self._get_nibble_data(packet[18:19]) # @UnusedVariable
+        baro1, baro10, baro100, baro1000, slp10th, slp1, slp10, slp100, slp1000, fmt, prediction, trend, dewin1, dewin10 = get_nibble_data(packet[1:8]) # @UnusedVariable
+        dewout1, dewout10 = get_nibble_data(packet[18:19]) # @UnusedVariable
 
         #dew = dewin1 + (dewin10 * 10)
         #dewout = dewout1 + (dewout10 *10)
@@ -737,7 +843,7 @@ if __name__ == '__main__':
     syslog.openlog('wmr9x8', 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')
@@ -746,7 +852,7 @@ if __name__ == '__main__':
                       default=DEFAULT_PORT)
     parser.add_option('--gen-packets', dest='gen_packets', action='store_true',
                       help="Generate packets indefinitely")
-    
+
     (options, args) = parser.parse_args()
 
     if options.version:
@@ -757,6 +863,6 @@ if __name__ == '__main__':
         syslog.syslog(syslog.LOG_DEBUG, "wmr9x8: Running genLoopPackets()")
         stn_dict = {'port': options.port}
         stn = WMR9x8(**stn_dict)
-        
+
         for packet in stn.genLoopPackets():
             print packet
diff --git a/examples/wmr89/wmr89.ino b/examples/wmr89/wmr89.ino
new file mode 100755
index 0000000..001318e
--- /dev/null
+++ b/examples/wmr89/wmr89.ino
@@ -0,0 +1,405 @@
+// Modified for use with the WeeWX and the WMR89a - Ray Kinsella
+//  - Check for the sync bits and then remove. 
+//  - Modified the interrupt handler to make it robust (interrupt safe).
+//  - Added flexibily so bitstream length can vary with the sensor.
+//  - Output to serial in binary, removes the need to do nasty string conversion.
+//  - Fixed the bit ordering in the serial output. 
+// Oregon V2 decoder added - Dominique Pierre
+// Oregon V3 decoder revisited - Dominique Pierre
+// New code to decode OOK signals from weather sensors, etc.
+// 2017-11-03 <raykinsell...@gmail.com> 
+// 2010-04-11 <j...@equi4.com> http://opensource.org/licenses/mit-license.php
+// $Id: ookDecoder.pde 5331 2010-04-17 10:45:17Z jcw $
+
+// Define OUTPUT_ASCII to debug on arduino serial console. 
+//#define OUTPUT_ASCII
+#define OUTPUT_LEDS
+
+// Decoding algorthim uses the change from continuous bit flipping (all 1s)
+// to the a constant flip bit (gives 1010) to detect the start of real sensor data.
+// This has the effect of capturing the sync bits, as though it where useful data.
+#define IGNORE_SYNC_BITS
+#define NUMBER_OF_SYNC_BITS 4
+
+class BoardLEDs {
+private:
+  const    byte BASEPIN = 2;
+  const    byte THROTTLEVALUE = 100;
+  unsigned long LEDTimer[3];
+  byte     checktimer;
+
+public:
+  BoardLEDs() 
+  {
+     memset(&LEDTimer,0, sizeof(LEDTimer));
+     checktimer = 0;
+  }
+
+  Setup()
+  {
+#ifdef OUTPUT_LEDS
+    for(int i=0; i < 3; i++)
+      pinMode(BASEPIN + i, OUTPUT);
+#endif
+  }
+
+  void SetLED(byte LED)
+  {
+#ifdef OUTPUT_LEDS
+    //Set LED ON
+    digitalWrite(BASEPIN + LED, HIGH);
+    LEDTimer[LED] = millis() + 2000;
+#endif
+  }
+
+  void CheckTimer()
+  {
+    unsigned long now;
+    
+#ifdef OUTPUT_LEDS
+    // Now need to do it everytime we are poked.
+    checktimer++;
+    if(checktimer > THROTTLEVALUE)
+      checktimer = 0;
+    else
+      return;
+    
+    now = millis();
+    
+    for(int i=0; i < 3; i++)
+    {
+       if(LEDTimer[i] && LEDTimer[i] < now)
+       {
+        //Turn off LED
+        digitalWrite(BASEPIN + i, LOW);
+        LEDTimer[i] = 0;
+       }
+    }
+#endif
+  }
+};
+
+class DecodeOOK {
+protected:
+
+    byte total_bits, bits, bitlen, flip, state, pos, syncbits, data[25];
+
+    virtual char decode (word width) =0;
+    
+public:
+
+    enum { UNKNOWN, T0, T1, T2, T3, OK, DONE };
+
+    DecodeOOK () { resetDecoder(); }
+
+    bool nextPulse (word width) {
+        if (state != DONE)
+        
+            switch (decode(width)) {
+                case -1: resetDecoder(); break;
+                case 1:  done(); break;
+            }
+        return isDone();
+    }
+    
+    bool isDone () const { return state == DONE; }
+
+    const byte* getData (byte& count) const {
+        count = pos;
+        return data; 
+    }
+    
+    void resetDecoder () {
+        bitlen = total_bits = bits = pos = flip = 0;
+        syncbits = NUMBER_OF_SYNC_BITS;
+        state = UNKNOWN;
+    }
+        
+    virtual void gotBit (char value) = 0;
+    
+    // store a bit using Manchester encoding
+    void manchester (char value) {
+        flip ^= value; // manchester code, long pulse flips the bit
+        gotBit(flip);
+    }
+    
+    // move bits to the front so that all the bits are aligned to the end
+    void alignTail (byte max =0) {
+        // align bits
+        if (bits != 0) {
+            data[pos] >>= 8 - bits;
+            for (byte i = 0; i < pos; ++i)
+                data[i] = (data[i] >> bits) | (data[i+1] << (8 - bits));
+            bits = 0;
+        }
+        // optionally shift bytes down if there are too many of 'em
+        if (max > 0 && pos > max) {
+            byte n = pos - max;
+            pos = max;
+            for (byte i = 0; i < pos; ++i)
+                data[i] = data[i+n];
+        }
+    }
+    
+    void reverseBits () {
+        for (byte i = 0; i < pos; ++i) {
+            byte b = data[i];
+            for (byte j = 0; j < 8; ++j) {
+                data[i] = (data[i] << 1) | (b & 1);
+                b >>= 1;
+            }
+        }
+    }
+    
+    void reverseNibbles () {
+        for (byte i = 0; i < pos; ++i)
+            data[i] = (data[i] << 4) | (data[i] >> 4);
+    }
+    
+    void done () {
+        while (bits)
+            gotBit(0); // padding
+        state = DONE;
+    }
+};
+
+class OregonDecoderV3 : public DecodeOOK {
+
+private:
+
+    byte SensorLED;
+
+    const byte sensorlookup[3][4] = 
+    { { 0x8F, 0x42, 0x48, 0x00} , // Sensor ID = F8 24
+      { 0x91, 0x48, 0x50, 0x01} , // Sensor ID = 91 48
+      { 0x0, 0x0, 0x0, 0x02}  };
+
+    virtual byte lookupLength(byte sensorcode0, byte sensorcode1)
+    {
+      for(int i = 0; i < 3; i++)
+      {      
+        if( sensorlookup[i][0] == sensorcode0 &&
+            sensorlookup[i][1] == sensorcode1)
+            {
+              SensorLED = sensorlookup[i][3];
+              return sensorlookup[i][2];
+            }
+      }
+
+      return -1;
+    }
+
+public:
+    OregonDecoderV3() {}
+    
+    // add one bit to the packet data buffer
+    virtual void gotBit (char value) {
+#ifdef IGNORE_SYNC_BITS
+        if (syncbits > 0)
+        {
+          syncbits--;
+          state = OK;
+
+          return;
+        }
+#endif
+        data[pos] = (data[pos] >> 1) | (value ? 0x80 : 00);
+        total_bits++;
+        pos = total_bits >> 3;
+        if (pos >= sizeof data) {
+            resetDecoder();
+            return;
+        }
+        state = OK;
+    }
+
+    virtual char decode (word width) {
+        if (200 <= width && width < 1200) {
+            byte w = width >= 700;
+            switch (state) {
+                case UNKNOWN:
+                    if (w == 0)
+                        ++flip;
+                    // first first long pulse indictates a state transition.
+                    else if (32 <= flip) {
+                        flip = 1;
+                        manchester(1);
+                    } else
+                        return -1;
+                    break;
+                case OK:
+                    if (w == 0)
+                        state = T0;
+                    else
+                        manchester(1);
+                    break;
+                case T0:
+                    if (w == 0)
+                        manchester(0);
+                    else
+                        return -1;
+                    break;                   
+            }
+        } else {
+            return -1;
+        }
+
+        // initial 32 bits contain the sensor id
+        if ( total_bits < 32 ) 
+        {
+          return 0;
+        }
+        else if ( total_bits == 32 )
+        {
+          bitlen = lookupLength(data[0], data[1]);
+          if (bitlen == -1) return -1;
+
+          return 0;
+        }
+        else
+        {
+          return  total_bits == bitlen ? 1: 0;
+        }
+    }
+
+    byte getSensorLED()
+    {
+      return SensorLED;
+    }
+};
+
+
+OregonDecoderV3 orscV3;
+BoardLEDs     ledMgr;
+
+enum eErrors 
+  {
+    eNoMoreSlots,
+    eErrorMax,
+  };
+
+#define PORT 2
+#define VECTORLEN 32
+#define VECTORNUM 4
+
+volatile word pulsetab[VECTORNUM][VECTORLEN];
+
+volatile byte vectoridx;
+volatile byte vectorslot;
+word errors[eErrorMax];
+
+#if defined(__AVR_ATmega1280__)
+void ext_int_1(void) {
+#else
+ISR(ANALOG_COMP_vect) {
+#endif
+    static word last;
+    word pulse; 
+    
+    // determine the pulse length in microseconds, for either polarity
+    pulse = micros() - last;
+    last += pulse;
+
+    if(VECTORLEN <= vectorslot)
+    {
+      errors[eNoMoreSlots]++;
+    }
+    else
+    {
+      pulsetab[vectoridx][vectorslot] = pulse;
+      vectorslot++;
+    }
+}
+
+void reportSerial (class DecodeOOK& decoder) {
+    byte pos;
+    const byte* data = decoder.getData(pos);
+
+    
+
+    for (byte i = 0; i < pos; ++i) {
+#ifdef OUTPUT_ASCII
+        Serial.print((data[i] & 0x0F), HEX);
+        Serial.print(data[i] >> 4, HEX);
+#else 
+        //Sensor encoding is nibble orientated, but we captured in bytes.
+        //Compensate by output LSB first, then the MSB. 
+        //Serial.write((data[i] & 0x0F) << 4 | data[i] >> 4);
+        Serial.write(data[i]);
+#endif
+    }
+
+#ifdef OUTPUT_ASCII
+    Serial.println();
+#endif
+        
+    decoder.resetDecoder();
+}
+
+
+void setup () {
+    Serial.begin(115200);
+
+#ifdef OUTPUT_LEDS
+    ledMgr.Setup();
+#endif
+
+#ifdef OUTPUT_ASCII
+    Serial.println("\n[ookDecoder]");
+#endif
+ 
+    vectoridx = vectorslot = 0;
+    
+#if !defined(__AVR_ATmega1280__)
+    pinMode(13 + PORT, INPUT);  // use the AIO pin
+    digitalWrite(13 + PORT, 1); // enable pull-up
+
+    // use analog comparator to switch at 1.1V bandgap transition
+    ACSR = _BV(ACBG) | _BV(ACI) | _BV(ACIE);
+
+    // set ADC mux to the proper port
+    ADCSRA &= ~ bit(ADEN);
+    ADCSRB |= bit(ACME);
+    ADMUX = PORT - 1;
+#else
+   attachInterrupt(1, ext_int_1, CHANGE);
+
+   DDRE  &= ~_BV(PE5);
+   PORTE &= ~_BV(PE5);
+#endif
+}
+
+void loop () {
+
+    byte    slot, slots, idx;
+    word    localtab[VECTORLEN];
+    char    message[20];
+    
+    cli();
+    
+    idx = vectoridx;
+    slots = vectorslot;
+    
+    vectorslot = 0;
+    if((vectoridx + 1) >= VECTORNUM)
+      vectoridx = 0;
+    else
+      vectoridx++;
+      
+    sei();
+
+    for(slot=0; slot < slots; slot++)
+    {
+      if (pulsetab[idx][slot] != 0) {
+          if (orscV3.nextPulse(pulsetab[idx][slot]))
+          {
+              ledMgr.SetLED(orscV3.getSensorLED());
+              reportSerial(orscV3);
+          }
+      }
+   }
+
+   ledMgr.CheckTimer();
+}
+
+
-- 
1.9.1

Reply via email to