Everything is working but I'm *not *getting the rain gauge data...at all.
Attached are my *sdr.py* and *weewx.conf* files.
There must be something wrong in one of them!
*From the Acurite 5n1 senso*r:
Wind speed works
Wind direction works
Humidity works
Temperature works
Rain gauge does *NOT *work.
Any help appreciated.
Thanks,
-Charlie
--
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 2016-2017 Matthew Wall
# Distributed under the terms of the GNU Public License (GPLv3)
"""
Collect data from stl-sdr. Run rtl_433 on a thread and push the output onto
a queue.
The SDR detects many different sensors and sensor types, so this driver
includes a mechanism to filter the incoming data, and to map the filtered
data onto the weewx database schema and identify the type of data from each
sensor.
Sensors are filtered based on a tuple that identifies uniquely each sensor.
A tuple consists of the observation name, a unique identifier for the hardware,
and the packet type, separated by periods:
<observation_name>.<hardware_id>.<packet_type>
The filter and data types are specified in a sensor_map stanza in the driver
stanza. For example:
[SDR]
driver = user.sdr
[[sensor_map]]
inTemp = temperature.25A6.AcuriteTowerPacket
outTemp = temperature.24A4.AcuriteTowerPacket
rain_total = rain_total.0D67.Acurite5n1Packet
# rain_total = rain_inch.0D67.Acurite5n1Packet
If no sensor_map is specified, no data will be collected.
The deltas stanza indicates which observations are cumulative measures and
how they should be split into delta measures.
[SDR]
...
[[deltas]]
rain = rain_total
In this case, the value for rain will be a delta calculated from sequential
rain_total observations.
To identify sensors, run the driver directly. Alternatively, use the options
log_unknown_sensors and log_unmapped_sensors to see data from the SDR that are
not yet recognized by your configuration.
[SDR]
driver = user.sdr
log_unknown_sensors = True
log_unmapped_sensors = True
The default for each of these is False.
Eventually we would prefer to have all rtl_433 output as json. Unfortunately,
many of the rtl_433 decoders do not emit this format yet (as of January 2017).
So this driver is designed to look for json first, then fall back to single-
or multi-line plain text format.
WARNING: Handling of units and unit systems in rtl_433 is a mess, but it is
getting better. Although there is an option to request SI units, there is no
indicate in the decoder output whether that option is respected, nor does
rtl_433 specify exactly which SI units are used for various types of measure.
There seems to be a pattern of appending a unit label to the observation name
in the JSON data, for example 'wind_speed_mph' instead of just 'wind_speed'.
"""
from __future__ import with_statement
from calendar import timegm
import Queue
import fnmatch
import os
import re
import subprocess
import syslog
import threading
import time
try:
import cjson as json
setattr(json, 'dumps', json.encode)
setattr(json, 'loads', json.decode)
except (ImportError, AttributeError):
try:
import simplejson as json
except ImportError:
import json
import weewx.drivers
from weeutil.weeutil import tobool
DRIVER_NAME = 'SDR'
DRIVER_VERSION = '0.48'
# The default command requests json output from every decoder
# -q - suppress non-data messages
# -U - print timestamps in UTC
# -F json - emit data in json format (not all rtl_433 decoders support this)
# -G - emit data for all rtl decoder (only available in newer rtl_433)
# Use the -R option instead of -G to indicate specific decoders.
DEFAULT_CMD = 'rtl_433 -q -U -F json -G'
def loader(config_dict, _):
return SDRDriver(**config_dict[DRIVER_NAME])
def confeditor_loader():
return SDRConfigurationEditor()
def logmsg(level, msg):
syslog.syslog(level, 'sdr: %s: %s' %
(threading.currentThread().getName(), 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 AsyncReader(threading.Thread):
def __init__(self, fd, queue, label):
threading.Thread.__init__(self)
self._fd = fd
self._queue = queue
self._running = False
self.setDaemon(True)
self.setName(label)
def run(self):
logdbg("start async reader for %s" % self.getName())
self._running = True
for line in iter(self._fd.readline, ''):
self._queue.put(line)
if not self._running:
break
def stop_running(self):
self._running = False
class ProcManager():
TS = re.compile('^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d[\s]+')
def __init__(self):
self._cmd = None
self._process = None
self.stdout_queue = Queue.Queue()
self.stdout_reader = None
self.stderr_queue = Queue.Queue()
self.stderr_reader = None
def startup(self, cmd, path=None, ld_library_path=None):
self._cmd = cmd
loginf("startup process '%s'" % self._cmd)
env = os.environ.copy()
if path:
env['PATH'] = path + ':' + env['PATH']
if ld_library_path:
env['LD_LIBRARY_PATH'] = ld_library_path
try:
self._process = subprocess.Popen(cmd.split(' '),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.stdout_reader = AsyncReader(
self._process.stdout, self.stdout_queue, 'stdout-thread')
self.stdout_reader.start()
self.stderr_reader = AsyncReader(
self._process.stderr, self.stderr_queue, 'stderr-thread')
self.stderr_reader.start()
except (OSError, ValueError), e:
raise weewx.WeeWxIOError("failed to start process: %s" % e)
def shutdown(self):
loginf('shutdown process %s' % self._cmd)
logdbg('waiting for %s' % self.stdout_reader.getName())
self.stdout_reader.stop_running()
self.stdout_reader.join(10.0)
if self.stdout_reader.isAlive():
loginf('timed out waiting for %s' % self.stdout_reader.getName())
self.stdout_reader = None
logdbg('waiting for %s' % self.stderr_reader.getName())
self.stderr_reader.stop_running()
self.stderr_reader.join(10.0)
if self.stderr_reader.isAlive():
loginf('timed out waiting for %s' % self.stderr_reader.getName())
self.stderr_reader = None
logdbg("close stdout")
self._process.stdout.close()
logdbg("close stderr")
self._process.stderr.close()
logdbg('kill process')
self._process.kill()
if self._process.poll() is None:
logerr('process did not respond to kill, shutting down anyway')
self._process = None
def running(self):
return self._process.poll() is None
def get_stderr(self):
lines = []
while not self.stderr_queue.empty():
lines.append(self.stderr_queue.get())
return lines
def get_stdout(self):
lines = []
while self.running():
try:
line = self.stdout_queue.get(True, 3)
m = ProcManager.TS.search(line)
if m and lines:
yield lines
lines = []
lines.append(line)
except Queue.Empty:
yield lines
lines = []
yield lines
class Packet:
def __init__(self):
pass
@staticmethod
def parse_text(ts, payload, lines):
return None
@staticmethod
def parse_json(obj):
return None
TS_PATTERN = re.compile('(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)')
@staticmethod
def parse_time(line):
ts = None
try:
m = Packet.TS_PATTERN.search(line)
if m:
utc = time.strptime(m.group(1), "%Y-%m-%d %H:%M:%S")
ts = timegm(utc)
except Exception, e:
logerr("parse timestamp failed for '%s': %s" % (line, e))
return ts
@staticmethod
def get_float(obj, key_):
if key_ in obj:
try:
return float(obj[key_])
except ValueError:
pass
return None
@staticmethod
def get_int(obj, key_):
if key_ in obj:
try:
return int(obj[key_])
except ValueError:
pass
return None
@staticmethod
def parse_lines(lines, parseinfo=None):
# parse each line, splitting on colon for name:value
# tuple in parseinfo is label, pattern, lambda
# if there is a label, use it to transform the name
# if there is a pattern, use it to match the value
# if there is a lamba, use it to convert the value
if parseinfo is None:
parseinfo = dict()
packet = dict()
for line in lines[1:]:
if line.count(':') == 1:
try:
(name, value) = [x.strip() for x in line.split(':')]
if name in parseinfo:
if parseinfo[name][1]:
m = parseinfo[name][1].search(value)
if m:
value = m.group(1)
else:
logdbg("regex failed for %s:'%s'" %
(name, value))
if parseinfo[name][2]:
value = parseinfo[name][2](value)
if parseinfo[name][0]:
name = parseinfo[name][0]
packet[name] = value
else:
logdbg("ignoring %s:%s" % (name, value))
except Exception, e:
logerr("parse failed for line '%s': %s" % (line, e))
else:
logdbg("skip line '%s'" % line)
while lines:
lines.pop(0)
return packet
@staticmethod
def add_identifiers(pkt, sensor_id='', packet_type=''):
# qualify each field name with details about the sensor. not every
# sensor has all three fields.
# observation.<sensor_id>.<packet_type>
packet = dict()
if 'dateTime' in pkt:
packet['dateTime'] = pkt.pop('dateTime', 0)
if 'usUnits' in pkt:
packet['usUnits'] = pkt.pop('usUnits', 0)
for n in pkt:
packet["%s.%s.%s" % (n, sensor_id, packet_type)] = pkt[n]
return packet
class Acurite(object):
@staticmethod
def insert_ids(pkt, pkt_type):
# there should be a sensor_id field in the packet to identify sensor.
# ensure the sensor_id is upper-case - it should be 4 hex characters.
sensor_id = str(pkt.pop('hardware_id', '0000')).upper()
return Packet.add_identifiers(pkt, sensor_id, pkt_type)
class AcuriteTowerPacket(Packet):
# initial implementation was single-line
# 2016-08-30 23:57:20 Acurite tower sensor 0x37FC Ch A: 26.7 C 80.1 F 16 % RH
#
# multi-line was introduced nov2016 - only single line is supported here
# 2017-01-12 02:55:10 : Acurite tower sensor : 12391 : B
# Temperature: 18.0 C
# Humidity: 68
# Battery: 0
# : 68
IDENTIFIER = "Acurite tower sensor"
PATTERN = re.compile('0x([0-9a-fA-F]+) Ch ([A-C]): ([\d.-]+) C ([\d.-]+) F ([\d]+) % RH')
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
m = AcuriteTowerPacket.PATTERN.search(lines[0])
if m:
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt['hardware_id'] = m.group(1)
pkt['channel'] = m.group(2)
pkt['temperature'] = float(m.group(3))
pkt['temperature_F'] = float(m.group(4))
pkt['humidity'] = float(m.group(5))
pkt = Acurite.insert_ids(pkt, AcuriteTowerPacket.__name__)
else:
loginf("AcuriteTowerPacket: unrecognized data: '%s'" % lines[0])
lines.pop(0)
return pkt
# JSON format as of mid-2018
# {"time" : "2018-07-21 01:53:56", "model" : "Acurite tower sensor", "id" : 13009, "sensor_id" : 13009, "channel" : "A", "temperature_C" : 15.000, "humidity" : 16, "battery_low" : 1}
# {"time" : "2018-07-21 01:52:24", "model" : "Acurite tower sensor", "id" : 13009, "sensor_id" : 13009, "channel" : "A", "temperature_C" : 15.600, "humidity" : 16, "battery_low" : 0}
# JSON format as of early 2017
# {"time" : "2017-01-12 03:43:05", "model" : "Acurite tower sensor", "id" : 521, "channel" : "A", "temperature_C" : 0.800, "humidity" : 68, "battery" : 0, "status" : 68}
# {"time" : "2017-01-12 03:43:11", "model" : "Acurite tower sensor", "id" : 5585, "channel" : "C", "temperature_C" : 21.100, "humidity" : 32, "battery" : 0, "status" : 68}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['hardware_id'] = "%04x" % obj.get('id', 0)
pkt['channel'] = obj.get('channel')
# support both battery status keywords
if 'battery_low' in obj:
pkt['battery'] = Packet.get_int(obj, 'battery_low')
else:
pkt['battery'] = Packet.get_int(obj, 'battery')
pkt['status'] = obj.get('status')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return Acurite.insert_ids(pkt, AcuriteTowerPacket.__name__)
class Acurite5n1Packet(Packet):
# 2016-08-31 16:41:39 Acurite 5n1 sensor 0x0BFA Ch C, Msg 31, Wind 15 kmph / 9.3 mph 270.0^ W (3), rain gauge 0.00 in
# 2016-08-30 23:57:25 Acurite 5n1 sensor 0x0BFA Ch C, Msg 38, Wind 2 kmph / 1.2 mph, 21.3 C 70.3 F 70 % RH
# 2016-09-27 17:09:34 Acurite 5n1 sensor 0x062C Ch A, Total rain fall since last reset: 2.00
#
# the 'rain fall since last reset' seems to be emitted once when rtl_433
# starts up, then never again. the rain measure in the type 31 messages
# is a cumulative value, but not the same as rain since last reset.
IDENTIFIER = "Acurite 5n1 sensor"
PATTERN = re.compile('0x([0-9a-fA-F]+) Ch ([A-C]), (.*)')
RAIN = re.compile('Total rain fall since last reset: ([\d.]+)')
MSG = re.compile('Msg (\d+), (.*)')
MSG31 = re.compile('Wind ([\d.]+) kmph / ([\d.]+) mph ([\d.]+).*rain gauge ([\d.]+) in')
MSG38 = re.compile('Wind ([\d.]+) kmph / ([\d.]+) mph, ([\d.-]+) C ([\d.-]+) F ([\d.]+) % RH')
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
m = Acurite5n1Packet.PATTERN.search(lines[0])
if m:
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt['hardware_id'] = m.group(1)
pkt['channel'] = m.group(2)
payload = m.group(3)
m = Acurite5n1Packet.MSG.search(payload)
if m:
msg_type = m.group(1)
payload = m.group(2)
if msg_type == '31':
m = Acurite5n1Packet.MSG31.search(payload)
if m:
pkt['wind_speed'] = float(m.group(1))
pkt['wind_speed_mph'] = float(m.group(2))
pkt['wind_dir'] = float(m.group(3))
pkt['rain_total'] = float(m.group(4))
else:
loginf("Acurite5n1Packet: no match for type 31: '%s'"
% payload)
elif msg_type == '38':
m = Acurite5n1Packet.MSG38.search(payload)
if m:
pkt['wind_speed'] = float(m.group(1))
pkt['wind_speed_mph'] = float(m.group(2))
pkt['temperature'] = float(m.group(3))
pkt['temperature_F'] = float(m.group(4))
pkt['humidity'] = float(m.group(5))
else:
loginf("Acurite5n1Packet: no match for type 38: '%s'"
% payload)
else:
loginf("Acurite5n1Packet: unknown message type %s"
" in line '%s'" % (msg_type, lines[0]))
else:
m = Acurite5n1Packet.RAIN.search(payload)
if m:
total = float(m.group(1))
pkt['rain_since_reset'] = total
loginf("Acurite5n1Packet: rain since reset: %s" % total)
else:
loginf("Acurite5n1Packet: unknown message format: '%s'" %
lines[0])
else:
loginf("Acurite5n1Packet: unrecognized data: '%s'" % lines[0])
lines.pop(0)
return Acurite.insert_ids(pkt, Acurite5n1Packet.__name__)
# sample json output from rtl_433 as of jan2017
# {"time" : "2017-01-16 02:34:12", "model" : "Acurite 5n1 sensor", "sensor_id" : 3066, "channel" : "C", "sequence_num" : 1, "battery" : "OK", "message_type" : 49, "wind_speed" : 0.000, "wind_dir_deg" : 67.500, "wind_dir" : "ENE", "rainfall_accumulation" : 0.000, "raincounter_raw" : 8978}
# {"time" : "2017-01-16 02:37:33", "model" : "Acurite 5n1 sensor", "sensor_id" : 3066, "channel" : "C", "sequence_num" : 1, "battery" : "OK", "message_type" : 56, "wind_speed" : 0.000, "temperature_F" : 27.500, "humidity" : 56}
# some changes to rtl_433 as of dec2017
# {"time" : "2017-12-24 02:07:00", "model" : "Acurite 5n1 sensor", "sensor_id" : 2662, "channel" : "A", "sequence_num" : 2, "battery" : "OK", "message_type" : 56, "wind_speed_mph" : 0.000, "temperature_F" : 47.500, "humidity" : 74}
# {"time" : "2017-12-24 02:07:18", "model" : "Acurite 5n1 sensor", "sensor_id" : 2662, "channel" : "A", "sequence_num" : 2, "battery" : "OK", "message_type" : 49, "wind_speed_mph" : 0.000, "wind_dir_deg" : 157.500, "wind_dir" : "SSE", "rainfall_accumulation_inch" : 0.000, "raincounter_raw" : 421}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.US
pkt['hardware_id'] = "%04x" % obj.get('sensor_id', 0)
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['status'] = obj.get('status')
msg_type = obj.get('message_type')
if msg_type == 49: # 0x31
pkt['wind_speed'] = Packet.get_float(obj, 'wind_speed_mph')
pkt['wind_dir'] = Packet.get_float(obj, 'wind_dir_deg')
pkt['rain_counter'] = Packet.get_int(obj, 'raincounter_raw')
elif msg_type == 56: # 0x38
pkt['wind_speed'] = Packet.get_float(obj, 'wind_speed_mph')
pkt['temperature'] = Packet.get_float(obj, 'temperature_F')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
# put some units on the rain total - each tip is 0.01 inch
# if 'rain_counter' in pkt:
if 'rain_inch' in pkt:
# pkt['rain_total'] = pkt['rain_counter'] * 0.01 # inch
pkt['rain_total'] = pkt['rain_total']
return Acurite.insert_ids(pkt, Acurite5n1Packet.__name__)
class Acurite606TXPacket(Packet):
# 2017-03-20: Acurite 606TX Temperature Sensor
# {"time" : "2017-03-04 16:18:12", "model" : "Acurite 606TX Sensor", "id" : 48, "battery" : "OK", "temperature_C" : -1.100}
IDENTIFIER = "Acurite 606TX Sensor"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
sensor_id = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt = Packet.add_identifiers(pkt, sensor_id, Acurite606TXPacket.__name__)
return pkt
class Acurite986Packet(Packet):
# 2016-10-31 15:24:29 Acurite 986 sensor 0x2c87 - 2F: 16.7 C 62 F
# 2016-10-31 15:23:54 Acurite 986 sensor 0x85ed - 1R: 16.7 C 62 F
# {"time" : "2018-04-22 18:01:03", "model" : "Acurite 986 Sensor", "id" : 43248, "channel" : "1R", "temperature_F" : 69, "battery" : "OK", "status" : 0}
# The 986 hardware_id changes, so using the 2F and 1R as the hardware
# identifer. As long as you only have one set of sendors and your
# close neighbors have none.
# Older releases of rtl_433 used 'Acurite 986 sensor', while recent
# versions use 'Acurite 986 Sensor'. So we try to be compatible by
# matching on the least that we can.
# IDENTIFIER = "Acurite 986 sensor"
# IDENTIFIER = "Acurite 986 Sensor"
IDENTIFIER = "Acurite 986"
PATTERN = re.compile('0x([0-9a-fA-F]+) - (1R|2F): ([\d.-]+) C ([\d.-]+) F')
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
m = Acurite986Packet.PATTERN.search(lines[0])
if m:
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt['hardware_id'] = m.group(2)
pkt['channel'] = m.group(1)
pkt['temperature'] = float(m.group(3))
pkt['temperature_F'] = float(m.group(4))
else:
loginf("Acurite986Packet: unrecognized data: '%s'" % lines[0])
lines.pop(0)
return Acurite.insert_ids(pkt, Acurite986Packet.__name__)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['hardware_id'] = obj.get('id', 0)
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
if 'temperature_F' in obj:
pkt['usUnits'] = weewx.US
pkt['temperature'] = Packet.get_float(obj, 'temperature_F')
else:
pkt['usUnits'] = weewx.METRIC
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
return Acurite.insert_ids(pkt, Acurite986Packet.__name__)
class AcuriteLightningPacket(Packet):
# with rtl_433 update of 19mar2017
# 2017-03-19 16:48:31 Acurite lightning 0x976F Ch A Msg Type 0x02: 66.2 F 25 % RH Strikes 1 Distance 0 L_status 0x02 - c0 97* 6f 99 50 72 81 c0 62*
# 2017-03-19 16:48:47 Acurite lightning 0x976F Ch A Msg Type 0x02: 66.2 F 25 % RH Strikes 1 Distance 0 L_status 0x02 - c0 97* 6f 99 50 72 81 c0 62*
# pre-19mar2017
# 2016-11-04 04:34:58 Acurite lightning 0x536F Ch A Msg Type 0x51: 15 C 58 % RH Strikes 50 Distance 69 - c0 53 6f 3a d1 0f b2 c5 13*
# 2016-11-04 04:43:14 Acurite lightning 0x536F Ch A Msg Type 0x51: 15 C 58 % RH Strikes 55 Distance 5 - c0 53 6f 3a d1 0f b7 05 58*
# 2016-11-04 04:43:22 Acurite lightning 0x536F Ch A Msg Type 0x51: 15 C 58 % RH Strikes 55 Distance 69 - c0 53 6f 3a d1 0f b7 c5 18
# 2017-01-16 02:37:39 Acurite lightning 0x526F Ch A Msg Type 0x11: 67 C 38 % RH Strikes 47 Distance 81 - dd 52* 6f a6 11 c3 af d1 98*
# April 21, 2018 - JSON support
# {"time" : "2018-04-21 19:12:53", "model" : "Acurite Lightning 6045M", "id" : 151, "channel" : "C", "temperature_F" : 66.900, "humidity" : 33, "strike_count" : 47, "storm_dist" : 12, "active" : 1, "rfi" : 0, "ussb1" : 1, "battery" : "LOW", "exception" : 0, "raw_msg" : "0097af2150f9afcc2b"}
# IDENTIFIER = "Acurite lightning"
IDENTIFIER = "Acurite Lightning 6045M"
PATTERN = re.compile('0x([0-9a-fA-F]+) Ch (.) Msg Type 0x([0-9a-fA-F]+): ([\d.-]+) ([CF]) ([\d.]+) % RH Strikes ([\d]+) Distance ([\d.]+)')
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.US
pkt['channel'] = obj.get('channel')
pkt['hardware_id'] = "%04x" % obj.get('id', 0)
pkt['temperature'] = obj.get('temperature_F')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['humidity'] = obj.get('humidity')
pkt['active'] = obj.get('active')
pkt['rfi'] = obj.get('rfi')
pkt['ussb1'] = obj.get('ussb1')
pkt['exception'] = obj.get('exception')
pkt['strikes_total'] = obj.get('strike_count')
pkt['distance'] = obj.get('storm_dist')
return Acurite.insert_ids(pkt, AcuriteLightningPacket.__name__)
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
m = AcuriteLightningPacket.PATTERN.search(lines[0])
if m:
pkt['dateTime'] = ts
units = m.group(5)
if units == 'C':
pkt['usUnits'] = weewx.METRIC
else:
pkt['usUnits'] = weewx.US
pkt['hardware_id'] = m.group(1)
pkt['channel'] = m.group(2)
pkt['msg_type'] = m.group(3)
pkt['temperature'] = float(m.group(4))
pkt['humidity'] = float(m.group(6))
pkt['strikes_total'] = float(m.group(7))
pkt['distance'] = float(m.group(8))
else:
loginf("AcuriteLightningPacket: unrecognized data: %s" % lines[0])
lines.pop(0)
return Acurite.insert_ids(pkt, AcuriteLightningPacket.__name__)
class Acurite00275MPacket(Packet):
IDENTIFIER = "00275rm"
# {"time" : "2017-03-09 21:59:11", "model" : "00275rm", "probe" : 2, "id" : 3942, "battery" : "OK", "temperature_C" : 23.300, "humidity" : 34, "ptemperature_C" : 22.700, "crc" : "ok"}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['hardware_id'] = "%04x" % obj.get('id', 0)
pkt['probe'] = obj.get('probe')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature_probe'] = Packet.get_float(obj, 'ptemperature_C')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return Acurite.insert_ids(pkt, Acurite00275MPacket.__name__)
class AcuriteWT450Packet(Packet):
IDENTIFIER = "WT450 sensor"
# {"time" : "2017-09-14 20:24:43", "model" : "WT450 sensor", "id" : 1, "channel" : 2, "battery" : "OK", "temperature_C" : 25.090, "humidity" : 49}
# {"time" : "2017-09-14 20:24:44", "model" : "WT450 sensor", "id" : 1, "channel" : 2, "battery" : "OK", "temperature_C" : 25.110, "humidity" : 49}
# {"time" : "2017-09-14 20:24:44", "model" : "WT450 sensor", "id" : 1, "channel" : 2, "battery" : "OK", "temperature_C" : 25.120, "humidity" : 49}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['sid'] = Packet.get_int(obj, 'id')
pkt['channel'] = Packet.get_int(obj, 'channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
_id = "%s:%s" % (pkt['sid'], pkt['channel'])
return Packet.add_identifiers(pkt, _id, AcuriteWT450Packet.__name__)
class AlectoV1Packet(Packet):
# {"time" : "2018-08-29 17:07:34", "model" : "AlectoV1 Temperature Sensor", "id" : 88, "channel" : 2, "battery" : "OK", "temperature_C" : 27.700, "humidity" : 42, "mic" : "CHECKSUM"}
IDENTIFIER = "AlectoV1 Temperature Sensor"
PARSEINFO = {
'ID': ['station_id', None, lambda x: int(x)],
'Temperature':
['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity':
['humidity', re.compile('([\d.-]+) C'), lambda x: float(x)]
}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, AlectoV1Packet.PARSEINFO))
return AlectoV1Packet.insert_ids(pkt)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['station_id'] = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return AlectoV1Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, AlectoV1Packet.__name__)
return pkt
class AmbientF007THPacket(Packet):
# 2017-01-21 18:17:16 : Ambient Weather F007TH Thermo-Hygrometer
# House Code: 80
# Channel: 1
# Temperature: 61.8
# Humidity: 13 %
IDENTIFIER = "Ambient Weather F007TH Thermo-Hygrometer"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Temperature': [
'temperature', re.compile('([\d.-]+) F'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, AmbientF007THPacket.PARSEINFO))
house_code = pkt.pop('house_code', 0)
channel = pkt.pop('channel', 0)
sensor_id = "%s:%s" % (channel, house_code)
pkt = Packet.add_identifiers(
pkt, sensor_id, AmbientF007THPacket.__name__)
return pkt
# {"time" : "2017-01-21 13:01:30", "model" : "Ambient Weather F007TH Thermo-Hygrometer", "device" : 80, "channel" : 1, "temperature_F" : 61.800, "humidity" : 10}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.US
house_code = obj.get('device', 0)
channel = obj.get('channel')
pkt['temperature'] = Packet.get_float(obj, 'temperature_F')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
sensor_id = "%s:%s" % (channel, house_code)
pkt = Packet.add_identifiers(
pkt, sensor_id, AmbientF007THPacket.__name__)
return pkt
class CalibeurRF104Packet(Packet):
# 2016-11-01 01:25:28 :Calibeur RF-104
# ID: 1
# Temperature: 1.8 C
# Humidity: 71 %
# 2016-11-04 05:16:39 :Calibeur RF-104
# ID: 1
# Temperature: -2.2 C
# Humidity: 71 %
IDENTIFIER = "Calibeur RF-104"
PARSEINFO = {
'ID': ['id', None, lambda x: int(x)],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, CalibeurRF104Packet.PARSEINFO))
pkt_id = pkt.pop('id', 0)
sensor_id = "%s" % pkt_id
pkt = Packet.add_identifiers(
pkt, sensor_id, CalibeurRF104Packet.__name__)
return pkt
class FOWH1080Packet(Packet):
# 2016-09-02 22:26:05 :Fine Offset WH1080 weather station
# Msg type: 0
# StationID: 0026
# Temperature: 19.9 C
# Humidity: 78 %
# Wind string: E
# Wind degrees: 90
# Wind avg speed: 0.00
# Wind gust: 1.22
# Total rainfall: 144.3
# Battery: OK
# {"time" : "2016-11-04 14:40:38", "model" : "Fine Offset WH1080 weather station", "msg_type" : 0, "id" : 38, "temperature_C" : 12.500, "humidity" : 68, "direction_str" : "E", "direction_deg" : "90", "speed" : 8.568, "gust" : 12.240, "rain" : 249.600, "battery" : "OK"}
# apparently rain total is in mm, not cm
# apparently wind speed is m/s not kph
IDENTIFIER = "Fine Offset WH1080 weather station"
PARSEINFO = {
# 'Msg type': ['msg_type', None, None],
'StationID': ['station_id', None, None],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': [
'humidity', re.compile('([\d.]+) %'), lambda x: float(x)],
# 'Wind string': ['wind_dir_ord', None, None],
'Wind degrees': ['wind_dir', None, lambda x: int(x)],
'Wind avg speed': ['wind_speed', None, lambda x: float(x)],
'Wind gust': ['wind_gust', None, lambda x: float(x)],
'Total rainfall': ['rain_total', None, lambda x: float(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.WXMETRIC
pkt.update(Packet.parse_lines(lines, FOWH1080Packet.PARSEINFO))
return FOWH1080Packet.insert_ids(pkt)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['station_id'] = obj.get('id')
pkt['msg_type'] = Packet.get_int(obj, 'msg_type')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['wind_dir'] = Packet.get_float(obj, 'direction_deg')
pkt['wind_speed'] = Packet.get_float(obj, 'speed')
pkt['wind_gust'] = Packet.get_float(obj, 'gust')
pkt['rain_total'] = Packet.get_float(obj, 'rain')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return FOWH1080Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWH1080Packet.__name__)
return pkt
class FOWHx080Packet(Packet):
# 2017-05-15 11:58:31: Fine Offset Electronics WH1080 / WH3080 Weather Station
# Msg type: 0
# Station ID: 236
# Temperature: 23.9 C
# Humidity: 48%
# Wind string: NE
# Wind degrees: 45
# Wind Avg Speed: 1.22
# Wind gust: 2.45
# Total rainfall: 525.3
# Battery: OK
# 2017-05-15 12:04:48: Fine Offset Electronics WH1080 / WH3080 Weather Station
# Msg type: 1
# Station ID: 173
# Signal Type: WWVB / MSF
# Hours: 21
# Minutes: 71
# Seconds: 11
# Year: 2165
# Month: 25
# Day: 70
# apparently there are different identifiers for the same packet, depending
# on which version of rtl_433 is running. one version has extra spaces,
# while another version does not. so for now, and until rtl_433
# stabilizes, match on something unique to these packets that still matches
# the strings from different rtl_433 versions.
#IDENTIFIER = "Fine Offset Electronics WH1080 / WH3080 Weather Station"
#IDENTIFIER = "Fine Offset Electronics WH1080/WH3080 Weather Station"
IDENTIFIER = "Fine Offset Electronics WH1080"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
# older versions of rlt_433 user 'station_id'
if 'station_id' in obj:
pkt['station_id'] = obj.get('station_id')
# but some newer versions of rtl_433 seem to use 'id'
if 'id' in obj:
pkt['station_id'] = obj.get('id')
pkt['msg_type'] = Packet.get_int(obj, 'msg_type')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['wind_dir'] = Packet.get_float(obj, 'direction_deg')
pkt['wind_speed'] = Packet.get_float(obj, 'speed')
pkt['wind_gust'] = Packet.get_float(obj, 'gust')
pkt['rain_total'] = Packet.get_float(obj, 'rain')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['signal_type'] = 1 if obj.get('signal_type') == 'WWVB / MSF' else 0
pkt['hours'] = Packet.get_int(obj, 'hours')
pkt['minutes'] = Packet.get_int(obj, 'minutes')
pkt['seconds'] = Packet.get_int(obj, 'seconds')
pkt['year'] = Packet.get_int(obj, 'year')
pkt['month'] = Packet.get_int(obj, 'month')
pkt['day'] = Packet.get_int(obj, 'day')
return FOWHx080Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWHx080Packet.__name__)
return pkt
class FOWH3080Packet(Packet):
# 2017-05-15 11:58:08: Fine Offset Electronics WH3080 Weather Station
# Msg type: 2
# UV Sensor ID: 225
# Sensor Status: OK
# UV Index: 8
# Lux: 120160.5
# Watts / m: 175.93
# Foot-candles: 11167.33
# {"time" : "2017-05-15 17:21:07", "model" : "Fine Offset Electronics WH3080 Weather Station", "msg_type" : 2, "uv_sensor_id" : 225, "uv_status" : "OK", "uv_index" : 1, "lux" : 7837.000, "wm" : 11.474, "fc" : 728.346}
IDENTIFIER = "Fine Offset Electronics WH3080 Weather Station"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['station_id'] = obj.get('uv_sensor_id')
pkt['msg_type'] = Packet.get_int(obj, 'msg_type')
pkt['uv_index'] = Packet.get_float(obj, 'uv_index')
pkt['luminosity'] = Packet.get_float(obj, 'lux')
pkt['radiation'] = Packet.get_float(obj, 'wm')
pkt['illumination'] = Packet.get_float(obj, 'fc')
pkt['uv_status'] = 0 if obj.get('uv_status') == 'OK' else 1
return FOWH3080Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWH3080Packet.__name__)
return pkt
class FOWH25Packet(Packet):
# 2016-09-02 22:26:05 : Fine Offset Electronics, WH25
# ID: 239
# Temperature: 19.9 C
# Humidity: 78 %
# Pressure: 1007.9 hPa
#
# 2018-10-09 19:45:12 : Fine Offset Electronics, WH25
# id : 21
# temperature_C : 20.900
# humidity : 65
# pressure_hPa : 980.400
# battery : OK
# mic : CHECKSUM
# {"time" : "2017-03-25 05:33:57", "model" : "Fine Offset Electronics, WH25", "id" : 239, "temperature_C" : 30.200, "humidity" : 68, "pressure" : 1008.000}
# {"time" : "2018-10-10 13:37:11", "model" : "Fine Offset Electronics, WH25", "id" : 21, "temperature_C" : 21.600, "humidity" : 66, "pressure_hPa" : 972.800, "battery" : "OK", "mic" : "CHECKSUM"}
IDENTIFIER = "Fine Offset Electronics, WH25"
PARSEINFO = {
'ID': ['station_id', None, lambda x: int(x)],
'Temperature':
['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)],
'Pressure':
['pressure', re.compile('([\d.-]+) hPa'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, FOWH25Packet.PARSEINFO))
return FOWH25Packet.insert_ids(pkt)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['station_id'] = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['pressure'] = Packet.get_float(obj, 'pressure_hPa')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return FOWH25Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWH25Packet.__name__)
return pkt
class FOWH2Packet(Packet):
# {"time" : "2018-08-29 17:08:33", "model" : "Fine Offset Electronics, WH2 Temperature/Humidity sensor", "id" : 129, "temperature_C" : 24.200, "mic" : "CRC"}
IDENTIFIER = "Fine Offset Electronics, WH2"
PARSEINFO = {
'ID': ['station_id', None, lambda x: int(x)],
'Temperature':
['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)]
}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, FOWH2Packet.PARSEINFO))
return FOWH2Packet.insert_ids(pkt)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['station_id'] = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
return FOWH2Packet.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWH2Packet.__name__)
return pkt
class FOWH65BPacket(Packet):
# 2018-10-10 13:37:02 : Fine Offset WH65B
# id : 89
# temperature_C : 17.600
# humidity : 93
# wind_dir_deg : 224
# wind_speed_ms : 1.540
# gust_speed_ms : 2.240
# rainfall_mm : 325.500
# uv : 130
# uvi : 0
# light_lux : 13454.000
# battery : OK
# mic : CRC
# This is for a WH65B which is the sensor array for an Ambient Weather
# WS-2902A. The same sensor array is used for several models.
# {"time" : "2018-10-10 13:37:02", "model" : "Fine Offset WH65B", "id" : 89, "temperature_C" : 17.600, "humidity" : 93, "wind_dir_deg" : 224, "wind_speed_ms" : 1.540, "gust_speed_ms" : 2.240, "rainfall_mm" : 325.500, "uv" : 130, "uvi" : 0, "light_lux" : 13454.000, "battery" : "OK", "mic" : "CRC"}
IDENTIFIER = "Fine Offset WH65B"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRICWX
pkt['station_id'] = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['wind_dir'] = Packet.get_float(obj, 'wind_dir_deg')
pkt['wind_speed'] = Packet.get_float(obj, 'wind_speed_ms')
pkt['wind_gust'] = Packet.get_float(obj, 'gust_speed_ms')
pkt['rain_total'] = Packet.get_float(obj, 'rainfall_mm')
pkt['uv'] = Packet.get_float(obj, 'uv')
pkt['uv_index'] = Packet.get_float(obj, 'uvi')
pkt['light'] = Packet.get_float(obj, 'light_lux') * 0.007893
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return FOWH65BPacket.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
station_id = pkt.pop('station_id', '0000')
pkt = Packet.add_identifiers(pkt, station_id, FOWH65BPacket.__name__)
return pkt
class Hideki(object):
@staticmethod
def insert_ids(pkt, pkt_type):
channel = pkt.pop('channel', 0)
code = pkt.pop('rolling_code', 0)
sensor_id = "%s:%s" % (channel, code)
pkt = Packet.add_identifiers(pkt, sensor_id, pkt_type)
return pkt
class HidekiTS04Packet(Packet):
# 2016-08-31 17:41:30 : HIDEKI TS04 sensor
# Rolling Code: 9
# Channel: 1
# Battery: OK
# Temperature: 27.30 C
# Humidity: 60 %
# {"time" : "2016-11-04 14:44:37", "model" : "HIDEKI TS04 sensor", "rc" : 9, "channel" : 1, "battery" : "OK", "temperature_C" : 12.400, "humidity" : 61}
IDENTIFIER = "HIDEKI TS04 sensor"
PARSEINFO = {
'Rolling Code': ['rolling_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, HidekiTS04Packet.PARSEINFO))
return Hideki.insert_ids(pkt, HidekiTS04Packet.__name__)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['rolling_code'] = obj.get('rc')
pkt['channel'] = obj.get('channel')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return Hideki.insert_ids(pkt, HidekiTS04Packet.__name__)
class HidekiWindPacket(Packet):
# 2017-01-16 05:39:42 : HIDEKI Wind sensor
# Rolling Code: 0
# Channel: 4
# Battery: OK
# Temperature: -5.0 C
# Wind Strength: 2.57 km/h
# Direction: 45.0 \xc2\xb0
# {"time" : "2017-01-16 04:38:39", "model" : "HIDEKI Wind sensor", "rc" : 0, "channel" : 4, "battery" : "OK", "temperature_C" : -4.400, "windstrength" : 2.897, "winddirection" : 292.500}
IDENTIFIER = "HIDEKI Wind sensor"
PARSEINFO = {
'Rolling Code': ['rolling_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Wind Strength': ['wind_speed', re.compile('([\d.]+) km/h'), lambda x: float(x)],
'Direction': ['wind_dir', re.compile('([\d.]+) '), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, HidekiWindPacket.PARSEINFO))
return Hideki.insert_ids(pkt, HidekiWindPacket.__name__)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['rolling_code'] = obj.get('rc')
pkt['channel'] = obj.get('channel')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['wind_speed'] = Packet.get_float(obj, 'windstrength')
pkt['wind_dir'] = Packet.get_float(obj, 'winddirection')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return Hideki.insert_ids(pkt, HidekiWindPacket.__name__)
class HidekiRainPacket(Packet):
# 2017-01-16 05:39:42 : HIDEKI Rain sensor
# Rolling Code: 0
# Channel: 4
# Battery: OK
# Rain: 2622.900
# {"time" : "2017-01-16 04:38:50", "model" : "HIDEKI Rain sensor", "rc" : 0, "channel" : 4, "battery" : "OK", "rain" : 2622.900}
IDENTIFIER = "HIDEKI Rain sensor"
PARSEINFO = {
'Rolling Code': ['rolling_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Rain': ['rain_total', re.compile('([\d.]+) '), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, HidekiRainPacket.PARSEINFO))
return Hideki.insert_ids(pkt, HidekiRainPacket.__name__)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['rolling_code'] = obj.get('rc')
pkt['channel'] = obj.get('channel')
pkt['rain_total'] = Packet.get_float(obj, 'rain')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return Hideki.insert_ids(pkt, HidekiRainPacket.__name__)
class LaCrosseWSPacket(Packet):
# 2016-09-08 00:43:52 :LaCrosse WS :9 :202
# Temperature: 21.0 C
# 2016-09-08 00:43:53 :LaCrosse WS :9 :202
# Humidity: 92
# 2016-09-08 00:43:53 :LaCrosse WS :9 :202
# Wind speed: 0.0 m/s
# Direction: 67.500
# 2016-11-03 17:43:20 :LaCrosse WS :9 :202
# Rainfall: 850.04 mm
# {"time" : "2016-11-04 14:42:49", "model" : "LaCrosse WS", "ws_id" : 9, "id" : 202, "temperature_C" : 12.100}
# {"time" : "2016-11-04 14:44:58", "model" : "LaCrosse WS", "ws_id" : 9, "id" : 202, "humidity" : 67}
# {"time" : "2016-11-04 14:49:16", "model" : "LaCrosse WS", "ws_id" : 9, "id" : 202, "wind_speed_ms" : 0.800, "wind_direction" : 270.000}
IDENTIFIER = "LaCrosse WS"
PARSEINFO = {
'Wind speed': [
'wind_speed', re.compile('([\d.]+) m/s'), lambda x: float(x)],
'Direction': ['wind_dir', None, lambda x: float(x)],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', None, lambda x: int(x)],
'Rainfall': [
'rain_total', re.compile('([\d.]+) mm'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRICWX
pkt.update(Packet.parse_lines(lines, LaCrosseWSPacket.PARSEINFO))
parts = payload.split(':')
if len(parts) == 3:
pkt['ws_id'] = parts[1].strip()
pkt['hw_id'] = parts[2].strip()
return LaCrosseWSPacket.insert_ids(pkt)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRICWX
pkt['ws_id'] = obj.get('ws_id')
pkt['hw_id'] = obj.get('id')
if 'temperature_C' in obj:
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
if 'humidity' in obj:
pkt['humidity'] = Packet.get_float(obj, 'humidity')
if 'wind_speed_ms' in obj:
pkt['wind_speed'] = Packet.get_float(obj, 'wind_speed_ms')
if 'wind_direction' in obj:
pkt['wind_dir'] = Packet.get_float(obj, 'wind_direction')
if 'rain' in obj:
pkt['rain_total'] = Packet.get_float(obj, 'rain')
return LaCrosseWSPacket.insert_ids(pkt)
@staticmethod
def insert_ids(pkt):
ws_id = pkt.pop('ws_id', 0)
hardware_id = pkt.pop('hw_id', 0)
sensor_id = "%s:%s" % (ws_id, hardware_id)
pkt = Packet.add_identifiers(pkt, sensor_id, LaCrosseWSPacket.__name__)
return pkt
class LaCrosseTX141THBv2Packet(Packet):
# {"time" : "2017-01-16 15:24:43", "temperature" : 54.140, "humidity" : 34, "id" : 221, "model" : "LaCrosse TX141TH-Bv2 sensor", "battery" : "OK", "test" : "Yes"}
IDENTIFIER = "LaCrosse TX141TH-Bv2 sensor"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.US
sensor_id = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt = Packet.add_identifiers(pkt, sensor_id, LaCrosseTX141THBv2Packet.__name__)
return pkt
class LaCrosseTXPacket(Packet):
# {"time" : "2017-07-30 21:11:19", "model" : "LaCrosse TX Sensor", "id" : 127, "humidity" : 34.000}
# {"time" : "2017-07-30 21:11:19", "model" : "LaCrosse TX Sensor", "id" : 127, "temperature_C" : 27.100}
IDENTIFIER = "LaCrosse TX Sensor"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
sensor_id = obj.get('id')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt = Packet.add_identifiers(pkt, sensor_id, LaCrosseTXPacket.__name__)
return pkt
class RubicsonTempPacket(Packet):
# 2017-01-15 14:49:03 : Rubicson Temperature Sensor
# House Code: 14
# Channel: 1
# Battery: OK
# Temperature: 4.5 C
# CRC: OK
IDENTIFIER = "Rubicson Temperature Sensor"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature': ['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, RubicsonTempPacket.PARSEINFO))
channel = pkt.pop('channel', 0)
code = pkt.pop('house_code', 0)
sensor_id = "%s:%s" % (channel, code)
return Packet.add_identifiers(pkt, sensor_id, RubicsonTempPacket.__name__)
# {"time" : "2017-01-17 20:47:41", "model" : "Rubicson Temperature Sensor", "id" : 14, "channel" : 1, "battery" : "OK", "temperature_C" : -1.800, "crc" : "OK"}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
channel = obj.get('channel', 0)
code = obj.get('id', 0)
sensor_id = "%s:%s" % (channel, code)
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
return Packet.add_identifiers(pkt, sensor_id, RubicsonTempPacket.__name__)
class OS(object):
@staticmethod
def insert_ids(pkt, pkt_type):
channel = pkt.pop('channel', 0)
code = pkt.pop('house_code', 0)
sensor_id = "%s:%s" % (channel, code)
return Packet.add_identifiers(pkt, sensor_id, pkt_type)
class OSPCR800Packet(Packet):
# 2016-11-03 04:36:23 : OS : PCR800
# House Code: 93
# Channel: 0
# Battery: OK
# Rain Rate: 0.0 in/hr
# Total Rain: 41.0 in
IDENTIFIER = "PCR800"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Rain Rate':
['rain_rate', re.compile('([\d.]+) in'), lambda x: float(x)],
'Total Rain':
['rain_total', re.compile('([\d.]+) in'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.US
pkt.update(Packet.parse_lines(lines, OSPCR800Packet.PARSEINFO))
return OS.insert_ids(pkt, OSPCR800Packet.__name__)
# {"time" : "2018-08-04 15:29:27", "brand" : "OS", "model" : "PCR800", "id" : 236, "channel" : 0, "battery" : "OK", "rain_rate" : 0.000, "rain_total" : 109.594}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.US
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['rain_rate'] = Packet.get_float(obj, 'rain_rate')
pkt['rain_total'] = Packet.get_float(obj, 'rain_total')
return OS.insert_ids(pkt, OSPCR800Packet.__name__)
# apparently rtl_433 uses BHTR968 when it should be BTHR968
class OSBTHR968Packet(Packet):
# Added 2017-04-22 ALG
# 2017-09-12 21:44:55 : OS : BHTR968
# House Code: 111
# Channel: 0
# Battery: OK
# Celcius: 26.20 C
# Fahrenheit: 79.16 F
# Humidity: 36 %
# Pressure: 1012 mbar
IDENTIFIER = "BHTR968"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature': ['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)],
'Pressure': ['pressure', re.compile('([\d.]+) mbar'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSBTHR968Packet.PARSEINFO))
return OS.insert_ids(pkt, OSBTHR968Packet.__name__)
# {"time" : "2017-01-18 14:56:03", "brand" : "OS", "model" :"BHTR968", "id" : 111, "channel" : 0, "battery" : "OK", "temperature_C" : 27.200, "temperature_F" : 80.960, "humidity" : 46, "pressure" : 1013}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['pressure'] = Packet.get_float(obj, 'pressure')
return OS.insert_ids(pkt, OSBTHR968Packet.__name__)
class OSTHGR122NPacket(Packet):
# 2016-09-12 21:44:55 : OS : THGR122N
# House Code: 96
# Channel: 3
# Battery: OK
# Temperature: 27.30 C
# Humidity: 36 %
IDENTIFIER = "THGR122N"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSTHGR122NPacket.PARSEINFO))
return OS.insert_ids(pkt, OSTHGR122NPacket.__name__)
# {"time" : "2017-01-18 14:56:03", "brand" : "OS", "model" :"THGR122N", "id" : 211, "channel" : 1, "battery" : "LOW", "temperature_C" : 7.900, "humidity" : 27}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return OS.insert_ids(pkt, OSTHGR122NPacket.__name__)
class OSTHGR810Packet(Packet):
# rtl_433 circa jul 2016 emits this
# 2016-09-01 22:05:47 :Weather Sensor THGR810
# House Code: 122
# Channel: 1
# Battery: OK
# Celcius: 26.70 C
# Fahrenheit: 80.06 F
# Humidity: 58 %
# rtl_433 circa nov 2016 emits this
# 2016-11-04 02:21:37 :OS :THGR810
# House Code: 122
# Channel: 1
# Battery: OK
# Celcius: 22.20 C
# Fahrenheit: 71.96 F
# Humidity: 57 %
IDENTIFIER = "THGR810"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Celcius': [
'temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Fahrenheit': [
'temperature_F', re.compile('([\d.-]+) F'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSTHGR810Packet.PARSEINFO))
return OS.insert_ids(pkt, OSTHGR810Packet.__name__)
# {"time" : "2016-11-04 14:40:05", "brand" : "OS", "model" : "THGR810", "id" : 122, "channel" : 1, "battery" : "OK", "temperature_C" : 20.900, "temperature_F" : 69.620, "humidity" : 57}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return OS.insert_ids(pkt, OSTHGR810Packet.__name__)
class OSTHR228NPacket(Packet):
# 2016-09-09 11:59:10 : Thermo Sensor THR228N
# House Code: 111
# Channel: 2
# Battery: OK
# Temperature: 24.70 C
IDENTIFIER = "Thermo Sensor THR228N"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Temperature':
['temperature', re.compile('([\d.-]+) C'), lambda x : float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSTHR228NPacket.PARSEINFO))
return OS.insert_ids(pkt, OSTHR228NPacket.__name__)
class OSUV800Packet(Packet):
# 2017-01-30 22:00:12 : OS : UV800
# House Code: 207
# Channel: 1
# Battery: OK
# UV Index: 0
IDENTIFIER = "UV800"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'UV Index':
['uv_index', re.compile('([\d.-]+) C'), lambda x : float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSUV800Packet.PARSEINFO))
return OS.insert_ids(pkt, OSUV800Packet.__name__)
# {"time" : "2017-01-30 22:19:40", "brand" : "OS", "model" : "UV800", "id" : 207, "channel" : 1, "battery" : "OK", "uv" : 0}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['uv_index'] = Packet.get_float(obj, 'uv')
return OS.insert_ids(pkt, OSUV800Packet.__name__)
class OSWGR800Packet(Packet):
# 2016-11-03 04:36:34 : OS : WGR800
# House Code: 85
# Channel: 0
# Battery: OK
# Gust: 1.1 m/s
# Average: 1.1 m/s
# Direction: 22.5 degrees
IDENTIFIER = "WGR800"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Gust': [
'wind_gust', re.compile('([\d.]+) m'), lambda x: float(x)],
'Average': [
'wind_speed', re.compile('([\d.]+) m'), lambda x: float(x)],
'Direction': [
'wind_dir', re.compile('([\d.]+) degrees'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRICWX
pkt.update(Packet.parse_lines(lines, OSWGR800Packet.PARSEINFO))
return OS.insert_ids(pkt, OSWGR800Packet.__name__)
# {"time" : "2018-08-04 15:29:19", "brand" : "OS", "model" : "WGR800", "id" : 93, "channel" : 0, "battery" : "OK", "gust" : 0.700, "average" : 1.000, "direction" : 315.000}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['wind_gust'] = Packet.get_float(obj, 'gust')
pkt['wind_speed'] = Packet.get_float(obj, 'average')
pkt['wind_dir'] = Packet.get_float(obj, 'direction')
return OS.insert_ids(pkt, OSWGR800Packet.__name__)
class OSTHN802Packet(Packet):
# 2017-08-03 17:24:08 : OS : THN802
# House Code: 157
# Channel: 3
# Battery: OK
# Celcius: 26.60 C
IDENTIFIER = "THN802"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Celcius': ['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSTHN802Packet.PARSEINFO))
return OS.insert_ids(pkt, OSTHN802Packet.__name__)
# {"time" : "2017-08-03 17:41:24", "brand" : "OS", "model" : "THN802", "id" : 157, "channel" : 3, "battery" : "OK", "temperature_C" : 26.700}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
return OS.insert_ids(pkt, OSTHN802Packet.__name__)
class OSBTHGN129Packet(Packet):
# 2017-08-03 17:24:03 : OS : BTHGN129
# House Code: 146
# Channel: 5
# Battery: OK
# Celcius: 32.00 C
# Humidity: 50 %
# Pressure: 959.36 mPa
IDENTIFIER = "BTHGN129"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Channel': ['channel', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Celcius': ['temperature', re.compile('([\d.-]+) C'), lambda x: float(x)],
'Humidity': ['humidity', re.compile('([\d.]+) %'), lambda x: float(x)],
'Pressure': ['pressure', re.compile('([\d.]+) mPa'), lambda x: float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, OSBTHGN129Packet.PARSEINFO))
return OS.insert_ids(pkt, OSBTHGN129Packet.__name__)
# {"time" : "2017-08-03 17:41:48", "brand" : "OS", "model" : "BTHGN129", "id" : 146, "channel" : 5, "battery" : "OK", "temperature_C" : 31.700, "humidity" : 52, "pressure_hPa" : 959.364}
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['channel'] = obj.get('channel')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['pressure'] = Packet.get_float(obj, 'pressure_hPa')
return OS.insert_ids(pkt, OSBTHGN129Packet.__name__)
class ProloguePacket(Packet):
# 2017-03-19 : Prologue Temperature and Humidity Sensor
# {"time" : "2017-03-15 20:14:19", "model" : "Prologue sensor", "id" : 5, "rid" : 166, "channel" : 1, "battery" : "OK", "button" : 0, "temperature_C" : -0.700, "humidity" : 49}
IDENTIFIER = "Prologue sensor"
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
sensor_id = obj.get('rid')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
pkt['humidity'] = Packet.get_float(obj, 'humidity')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['channel'] = obj.get('channel')
pkt = Packet.add_identifiers(pkt, sensor_id, ProloguePacket.__name__)
return pkt
class NexusTemperaturePacket(Packet):
# 2018-06-30 01:12:12 : Nexus Temperature
# House Code: 55
# Battery: OK
# Channel: 1
# Temperature: 27.10 C
# 2018-08-01 22:03:11 : Nexus Temperature/Humidity
# House Code: 180
# Battery: OK
# Channel: 1
# Temperature: 20.10 C
# Humidity: 42 %
IDENTIFIER = "Nexus Temperature"
PARSEINFO = {
'House Code': ['house_code', None, lambda x: int(x)],
'Battery': ['battery', None, lambda x: 0 if x == 'OK' else 1],
'Channel': ['channel', None, lambda x: int(x)],
'Temperature':
['temperature', re.compile('([\d.-]+) C'), lambda x : float(x)],
'Humidity':
['humidity', re.compile('([\d.-]+) %'), lambda x : float(x)]}
@staticmethod
def parse_text(ts, payload, lines):
pkt = dict()
pkt['dateTime'] = ts
pkt['usUnits'] = weewx.METRIC
pkt.update(Packet.parse_lines(lines, NexusTemperaturePacket.PARSEINFO))
return OS.insert_ids(pkt, NexusTemperaturePacket.__name__)
@staticmethod
def parse_json(obj):
pkt = dict()
pkt['dateTime'] = Packet.parse_time(obj.get('time'))
pkt['usUnits'] = weewx.METRIC
pkt['house_code'] = obj.get('id')
pkt['battery'] = 0 if obj.get('battery') == 'OK' else 1
pkt['channel'] = obj.get('channel')
pkt['temperature'] = Packet.get_float(obj, 'temperature_C')
if 'humidity' in obj:
pkt['humidity'] = Packet.get_float(obj, 'humidity')
return OS.insert_ids(pkt, NexusTemperaturePacket.__name__)
class PacketFactory(object):
# FIXME: do this with class introspection
KNOWN_PACKETS = [
AcuriteTowerPacket,
Acurite5n1Packet,
Acurite606TXPacket,
Acurite986Packet,
AcuriteLightningPacket,
Acurite00275MPacket,
AcuriteWT450Packet,
AlectoV1Packet,
AmbientF007THPacket,
CalibeurRF104Packet,
FOWHx080Packet,
FOWH1080Packet,
FOWH3080Packet,
FOWH25Packet,
FOWH2Packet,
FOWH65BPacket,
HidekiTS04Packet,
HidekiWindPacket,
HidekiRainPacket,
LaCrosseWSPacket,
LaCrosseTX141THBv2Packet,
LaCrosseTXPacket,
RubicsonTempPacket,
OSPCR800Packet,
OSBTHR968Packet,
OSTHGR122NPacket,
OSTHGR810Packet,
OSTHR228NPacket,
OSUV800Packet,
OSWGR800Packet,
OSTHN802Packet,
OSBTHGN129Packet,
ProloguePacket,
NexusTemperaturePacket]
@staticmethod
def create(lines):
# return a list of packets from the specified lines
logdbg("lines=%s" % lines)
while lines:
pkt = None
if lines[0].startswith('{'):
pkt = PacketFactory.parse_json(lines)
if pkt is None:
logdbg("punt unrecognized line '%s'" % lines[0])
lines.pop(0)
else:
pkt = PacketFactory.parse_text(lines)
if pkt is not None:
yield pkt
@staticmethod
def parse_json(lines):
try:
obj = json.loads(lines[0])
if 'model' in obj:
for parser in PacketFactory.KNOWN_PACKETS:
if obj['model'].find(parser.IDENTIFIER) >= 0:
return parser.parse_json(obj)
logdbg("parse_json: unknown model %s" % obj['model'])
except ValueError, e:
logdbg("parse_json failed: %s" % e)
return None
@staticmethod
def parse_text(lines):
ts, payload = PacketFactory.parse_firstline(lines[0])
if ts and payload:
logdbg("parse_text: ts=%s payload=%s" % (ts, payload))
for parser in PacketFactory.KNOWN_PACKETS:
if payload.find(parser.IDENTIFIER) >= 0:
pkt = parser.parse_text(ts, payload, lines)
logdbg("pkt=%s" % pkt)
return pkt
logdbg("parse_text: unknown format: ts=%s payload=%s" %
(ts, payload))
logdbg("parse_text failed: ts=%s payload=%s line=%s" %
(ts, payload, lines[0]))
lines.pop(0)
return None
TS_PATTERN = re.compile('(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)[\s]+:*(.*)')
@staticmethod
def parse_firstline(line):
ts = payload = None
try:
m = PacketFactory.TS_PATTERN.search(line)
if m:
utc = time.strptime(m.group(1), "%Y-%m-%d %H:%M:%S")
ts = timegm(utc)
payload = m.group(2).strip()
except Exception, e:
logerr("parse timestamp failed for '%s': %s" % (line, e))
return ts, payload
class SDRConfigurationEditor(weewx.drivers.AbstractConfEditor):
@property
def default_stanza(self):
return """
[SDR]
# This section is for the software-defined radio driver.
# The driver to use
driver = user.sdr
# How to invoke the rtl_433 command
# cmd = %s
# The sensor map associates observations with database fields. Each map
# element consists of a tuple on the left and a database field name on the
# right. The tuple on the left consists of:
#
# <observation_name>.<sensor_identifier>.<packet_type>
#
# The sensor_identifier is hardware-specific. For example, Acurite sensors
# have a 4 character hexadecimal identifier, whereas fine offset sensor
# clusters have a 4 digit identifier.
#
# glob-style pattern matching is supported for the sensor_identifier.
#
# map data from any fine offset sensor cluster to database field names
# [[sensor_map]]
# windGust = wind_gust.*.FOWH1080Packet
# outBatteryStatus = battery.*.FOWH1080Packet
# rain_total = rain_total.*.FOWH1080Packet
# windSpeed = wind_speed.*.FOWH1080Packet
# windDir = wind_dir.*.FOWH1080Packet
# outHumidity = humidity.*.FOWH1080Packet
# outTemp = temperature.*.FOWH1080Packet
""" % DEFAULT_CMD
class SDRDriver(weewx.drivers.AbstractDevice):
# map the counter total to the counter delta. for example, the pair
# rain:rain_total
# will result in a delta called 'rain' from the cumulative 'rain_total'.
# these are applied to mapped packets.
DEFAULT_DELTAS = {
'rain': 'rain_total',
'strikes': 'strikes_total'}
def __init__(self, **stn_dict):
loginf('driver version is %s' % DRIVER_VERSION)
self._log_unknown = tobool(stn_dict.get('log_unknown_sensors', False))
self._log_unmapped = tobool(stn_dict.get('log_unmapped_sensors', False))
self._sensor_map = stn_dict.get('sensor_map', {})
loginf('sensor map is %s' % self._sensor_map)
self._deltas = stn_dict.get('deltas', SDRDriver.DEFAULT_DELTAS)
loginf('deltas is %s' % self._deltas)
self._counter_values = dict()
cmd = stn_dict.get('cmd', DEFAULT_CMD)
path = stn_dict.get('path', None)
ld_library_path = stn_dict.get('ld_library_path', None)
self._last_pkt = None # avoid duplicate sequential packets
self._mgr = ProcManager()
self._mgr.startup(cmd, path, ld_library_path)
def closePort(self):
self._mgr.shutdown()
@property
def hardware_name(self):
return 'SDR'
def genLoopPackets(self):
while self._mgr.running():
for lines in self._mgr.get_stdout():
for packet in PacketFactory.create(lines):
if packet:
packet = self.map_to_fields(packet, self._sensor_map)
if packet:
if packet != self._last_pkt:
logdbg("packet=%s" % packet)
self._last_pkt = packet
self._calculate_deltas(packet)
yield packet
else:
logdbg("ignoring duplicate packet %s" % packet)
elif self._log_unmapped:
loginf("unmapped: %s (%s)" % (lines, packet))
elif self._log_unknown:
loginf("unparsed: %s" % lines)
self._mgr.get_stderr() # flush the stderr queue
else:
logerr("err: %s" % self._mgr.get_stderr())
raise weewx.WeeWxIOError("rtl_433 process is not running")
def _calculate_deltas(self, pkt):
for k in self._deltas:
label = self._deltas[k]
if label in pkt:
pkt[k] = self._calculate_delta(
label, pkt[label], self._counter_values.get(label))
self._counter_values[label] = pkt[label]
@staticmethod
def _calculate_delta(label, newtotal, oldtotal):
delta = None
if newtotal is not None and oldtotal is not None:
if newtotal >= oldtotal:
delta = newtotal - oldtotal
else:
loginf("%s decrement ignored:"
" new: %s old: %s" % (label, newtotal, oldtotal))
return delta
@staticmethod
def map_to_fields(pkt, sensor_map):
# selectively get elements from the packet using the specified sensor
# map. if the identifier is found, then use its value. if not, then
# skip it completely (it is not given a None value). include the
# time stamp and unit system only if we actually got data.
packet = dict()
for n in sensor_map.keys():
label = SDRDriver._find_match(sensor_map[n], pkt.keys())
if label:
packet[n] = pkt.get(label)
if packet:
for k in ['dateTime', 'usUnits']:
packet[k] = pkt[k]
return packet
@staticmethod
def _find_match(pattern, keylist):
# find the first key in pkt that matches the specified pattern.
# the general form of a pattern is:
# <observation_name>.<sensor_id>.<packet_type>
# do glob-style matching.
if pattern in keylist:
return pattern
match = None
pparts = pattern.split('.')
if len(pparts) == 3:
for k in keylist:
kparts = k.split('.')
if (len(kparts) == 3 and
SDRDriver._part_match(pparts[0], kparts[0]) and
SDRDriver._part_match(pparts[1], kparts[1]) and
SDRDriver._part_match(pparts[2], kparts[2])):
match = k
break
elif pparts[0] == k:
match = k
break
return match
@staticmethod
def _part_match(pattern, value):
# use glob matching for parts of the tuple
matches = fnmatch.filter([value], pattern)
return True if matches else False
if __name__ == '__main__':
import optparse
usage = """%prog [--debug] [--help] [--version]
[--action=(show-packets | show-detected | list-supported)]
[--cmd=RTL_CMD] [--path=PATH] [--ld_library_path=LD_LIBRARY_PATH]
Actions:
show-packets: display each packet (default)
show-detected: display a running count of the number of each packet type
list-supported: show a list of the supported packet types
Hide:
This is a comma-separate list of the types of data that should not be
displayed. Default is to show everything."""
syslog.openlog('sdr', syslog.LOG_PID | syslog.LOG_CONS)
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
parser = optparse.OptionParser(usage=usage)
parser.add_option('--version', dest='version', action='store_true',
help='display driver version')
parser.add_option('--debug', dest='debug', action='store_true',
help='display diagnostic information while running')
parser.add_option('--cmd', dest='cmd', default=DEFAULT_CMD,
help='rtl_433 command with options')
parser.add_option('--path', dest='path',
help='value for PATH')
parser.add_option('--ld_library_path', dest='ld_library_path',
help='value for LD_LIBRARY_PATH')
parser.add_option('--hide', dest='hidden', default='empty',
help='output to be hidden: out, parsed, unparsed, empty')
parser.add_option('--action', dest='action', default='show-packets',
help='actions include show-packets, show-detected, list-supported')
(options, args) = parser.parse_args()
if options.version:
print "sdr driver version %s" % DRIVER_VERSION
exit(1)
if options.debug:
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
if options.action == 'list-supported':
for pt in PacketFactory.KNOWN_PACKETS:
print pt.IDENTIFIER
elif options.action == 'show-detected':
# display identifiers for detected sensors
mgr = ProcManager()
mgr.startup(options.cmd, path=options.path,
ld_library_path=options.ld_library_path)
detected = dict()
for lines in mgr.get_stdout():
# print "out:", lines
for p in PacketFactory.create(lines):
if p:
del p['usUnits']
del p['dateTime']
keys = p.keys()
label = re.sub(r'^[^\.]+', '', keys[0])
if label not in detected:
detected[label] = 0
detected[label] += 1
print detected
else:
# display output and parsed/unparsed packets
hidden = [x.strip() for x in options.hidden.split(',')]
mgr = ProcManager()
mgr.startup(options.cmd, path=options.path,
ld_library_path=options.ld_library_path)
for lines in mgr.get_stdout():
if 'out' not in hidden and (
'empty' not in hidden or len(lines)):
print "out:", lines
for p in PacketFactory.create(lines):
if p:
if 'parsed' not in hidden:
print 'parsed: %s' % p
else:
if 'unparsed' not in hidden and (
'empty' not in hidden or len(lines)):
print "unparsed:", lines
for lines in mgr.get_stderr():
print "err:", lines
# WEEWX CONFIGURATION FILE
#
# Copyright (c) 2009-2015 Tom Keffer <[email protected]>
# See the file LICENSE.txt for your rights.
##############################################################################
# This section is for general configuration information.
# Set to 1 for extra debug info, otherwise comment it out or set to zero
debug = 1
# Root directory of the weewx data file hierarchy for this station
WEEWX_ROOT = /
# How long to wait before timing out a socket (FTP, HTTP) connection
socket_timeout = 20
# Do not modify this. It is used when installing and updating weewx.
version = 3.8.2
##############################################################################
# This section is for information about the station.
[Station]
# Description of the station location
location = "The MicroRanch in Buda, Texas"
# Latitude and longitude in decimal degrees
latitude = 30.114
longitude = -97.891
# Altitude of the station, with unit it is in. This is downloaded from
# from the station if the hardware supports it.
altitude = 850.0, foot
# Set to type of station hardware. There must be a corresponding stanza
# in this file with a 'driver' parameter indicating the driver to be used.
station_type = SDR
# If you have a website, you may specify an URL
#station_url = http://www.example.com
# The start of the rain year (1=January; 10=October, etc.). This is
# downloaded from the station if the hardware supports it.
rain_year_start = 1
# Start of week (0=Monday, 6=Sunday)
week_start = 6
##############################################################################
[AcuRite]
# This section is for AcuRite weather stations.
# The station model, e.g., 'AcuRite 01025' or 'AcuRite 02032C'
model = AcuRite 01035
# The driver to use:
driver = weewx.drivers.acurite
##############################################################################
[Simulator]
# This section is for the weewx weather station simulator
# The time (in seconds) between LOOP packets.
loop_interval = 2.5
# The simulator mode can be either 'simulator' or 'generator'.
# Real-time simulator. Sleep between each LOOP packet.
mode = simulator
# Generator. Emit LOOP packets as fast as possible (useful for testing).
#mode = generator
# The start time. Format is YYYY-mm-ddTHH:MM. If not specified, the default
# is to use the present time.
#start = 2011-01-01T00:00
# The driver to use:
driver = weewx.drivers.simulator
##############################################################################
# This section is for uploading data to Internet sites
[StdRESTful]
[[StationRegistry]]
# To register this weather station with weewx, set this to true
register_this_station = false
[[AWEKAS]]
# This section is for configuring posts to AWEKAS.
# If you wish to do this, set the option 'enable' to true,
# and specify a username and password.
# To guard against parsing errors, put the password in quotes.
enable = false
username = replace_me
password = replace_me
[[CWOP]]
# This section is for configuring posts to CWOP.
# If you wish to do this, set the option 'enable' to true,
# and specify the station ID (e.g., CW1234).
enable = false
station = replace_me
# If this is an APRS (radio amateur) station, uncomment
# the following and replace with a passcode (e.g., 12345).
#passcode = replace_me (APRS stations only)
[[PWSweather]]
# This section is for configuring posts to PWSweather.com.
# If you wish to do this, set the option 'enable' to true,
# and specify a station and password.
# To guard against parsing errors, put the password in quotes.
enable = false
station = replace_me
password = replace_me
[[WOW]]
# This section is for configuring posts to WOW.
# If you wish to do this, set the option 'enable' to true,
# and specify a station and password.
# To guard against parsing errors, put the password in quotes.
enable = false
station = replace_me
password = replace_me
[[Wunderground]]
# This section is for configuring posts to the Weather Underground.
# If you wish to do this, set the option 'enable' to true,
# and specify a station (e.g., 'KORHOODR3') and password.
# To guard against parsing errors, put the password in quotes.
enable = false
station = replace_me
password = replace_me
# Set the following to True to have weewx use the WU "Rapidfire"
# protocol. Not all hardware can support it. See the User's Guide.
rapidfire = False
##############################################################################
# This section specifies what reports, using which skins, to generate.
[StdReport]
# Where the skins reside, relative to WEEWX_ROOT
SKIN_ROOT = /etc/weewx/skins
# Where the generated reports should go, relative to WEEWX_ROOT
HTML_ROOT = /var/www/html/weewx
# The database binding indicates which data should be used in reports.
data_binding = wx_binding
# Each of the following subsections defines a report that will be run.
[[StandardReport]]
# See the customizing guide to change the units, plot types and line
# colors, modify the fonts, display additional sensor data, and other
# customizations. Many of those changes can be made here by overriding
# parameters, or by modifying templates within the skin itself.
# The StandardReport uses the 'Standard' skin, which contains the
# images, templates and plots for the report.
skin = Standard
[[[Units]]]
[[[[Groups]]]]
group_altitude = foot
group_speed2 = mile_per_hour2
group_pressure = inHg
group_rain = inch
group_rainrate = inch_per_hour
group_temperature = degree_F
group_degree_day = degree_F_day
group_speed = mile_per_hour
[[FTP]]
# FTP'ing the results to a webserver is treated as just another report,
# albeit one with an unusual report generator!
skin = Ftp
# If you wish to use FTP, uncomment and fill out the next four lines.
# Use quotes around passwords to guard against parsing errors.
#user = replace with the ftp username
#password = replace with the ftp password
#server = replace with the ftp server name, e.g, www.threefools.org
#path = replace with the ftp destination directory (e.g., /weather)
# Set to True for an FTP over TLS (FTPS) connection. Not all servers
# support this.
secure_ftp = False
# To upload files from something other than what HTML_ROOT is set
# to above, specify a different HTML_ROOT here.
#HTML_ROOT = /var/www/html/weewx
# Most FTP servers use port 21
port = 21
# Set to 1 to use passive mode, zero for active mode
passive = 1
[[RSYNC]]
# rsync'ing to a webserver is treated as just another report
skin = Rsync
# If you wish to use rsync, you must configure passwordless ssh using
# public/private key authentication from the user account that weewx
# runs as to the user account on the remote machine where the files
# will be copied.
#
# The server, user, and path determine where files will be sent.
# The server is the server name, such as www.threefools.org
# The user is the username, such as weewx
# The path is the destination directory, such as /var/www/html/weather
# Be sure that the user has write permissions on the destination!
#server = replace_me
#user = replace_me
#path = replace_me
# Rsync can be configured to remove files from the remote server if
# they don't exist under HTML_ROOT locally. USE WITH CAUTION: if you
# make a mistake in the remote path, you could could unintentionally
# cause unrelated files to be deleted. Set to 1 to enable remote file
# deletion, zero to allow files to accumulate remotely.
delete = 0
##############################################################################
# This service acts as a filter, converting the unit system coming from
# the hardware to a unit system in the database.
[StdConvert]
# The target_unit affects only the unit system in the database. Once
# chosen it cannot be changed without converting the entire database.
# Modification of target_unit after starting weewx will result in
# corrupt data - the database will contain a mix of US and METRIC data.
#
# The value of target_unit does not affect the unit system for
# reporting - reports can display US, Metric, or any combination of units.
#
# In most cases, target_unit should be left as the default: US
#
# In particular, those migrating from a standard wview installation
# should use US since that is what the wview database contains.
# DO NOT MODIFY THIS VALUE UNLESS YOU KNOW WHAT YOU ARE DOING!
target_unit = US # Options are 'US', 'METRICWX', or 'METRIC'
##############################################################################
# This section can adjust data using calibration expressions.
[StdCalibrate]
[[Corrections]]
# For each type, an arbitrary calibration expression can be given.
# It should be in the units defined in the StdConvert section.
# Example:
foo = foo + 0.2
##############################################################################
# This section is for quality control checks. If units are not specified,
# values must be in the units defined in the StdConvert section.
[StdQC]
[[MinMax]]
barometer = 26, 32.5, inHg
outTemp = -40, 120, degree_F
inTemp = 10, 120, degree_F
outHumidity = 0, 100
inHumidity = 0, 100
windSpeed = 0, 120, mile_per_hour
pressure = 24, 34.5, inHg
##############################################################################
# This section controls the origin of derived values.
[StdWXCalculate]
[[Calculations]]
# Derived quantities are calculated by this service. Possible values
are:
# hardware - use the value provided by hardware
# software - use the value calculated by weewx
# prefer_hardware - use value provide by hardware if available,
# otherwise use value calculated by weewx
pressure = prefer_hardware
barometer = prefer_hardware
altimeter = prefer_hardware
windchill = prefer_hardware
heatindex = prefer_hardware
dewpoint = prefer_hardware
inDewpoint = prefer_hardware
rainRate = prefer_hardware
##############################################################################
# For hardware that supports it, this section controls how often the
# onboard clock gets updated.
[StdTimeSynch]
# How often to check the weather station clock for drift (in seconds)
clock_check = 14400
# How much it can drift before we will correct it (in seconds)
max_drift = 5
##############################################################################
# This section is for configuring the archive service.
[StdArchive]
# If the station hardware supports data logging then the archive interval
# will be downloaded from the station. Otherwise, specify it (in seconds).
archive_interval = 300
# If possible, new archive records are downloaded from the station
# hardware. If the hardware does not support this, then new archive
# records will be generated in software.
# Set the following to "software" to force software record generation.
record_generation = hardware
# Whether to include LOOP data in hi/low statistics
loop_hilo = True
# The data binding used to save archive records
data_binding = wx_binding
##############################################################################
# This section binds a data store to a database.
[DataBindings]
[[wx_binding]]
# The database must match one of the sections in [Databases].
# This is likely to be the only option you would want to change.
database = archive_sqlite
# The name of the table within the database
table_name = archive
# The manager handles aggregation of data for historical summaries
manager = weewx.wxmanager.WXDaySummaryManager
# The schema defines the structure of the database.
# It is *only* used when the database is created.
schema = schemas.wview.schema
##############################################################################
# This section defines various databases.
[Databases]
# A SQLite database is simply a single file
[[archive_sqlite]]
database_type = SQLite
database_name = weewx.sdb
# MySQL
[[archive_mysql]]
database_type = MySQL
database_name = weewx
##############################################################################
# This section defines defaults for the different types of databases.
[DatabaseTypes]
# Defaults for SQLite databases
[[SQLite]]
driver = weedb.sqlite
# Directory in which the database files are located
SQLITE_ROOT = /var/lib/weewx
# Defaults for MySQL databases
[[MySQL]]
driver = weedb.mysql
# The host where the database is located
host = localhost
# The user name for logging in to the host
user = weewx
# The password for the user name (quotes guard against parsing errors)
password = weewx
##############################################################################
# This section configures the internal weewx engine.
[Engine]
[[Services]]
# This section specifies the services that should be run. They are
# grouped by type, and the order of services within each group
# determines the order in which the services will be run.
prep_services = weewx.engine.StdTimeSynch
data_services = ,
process_services = weewx.engine.StdConvert, weewx.engine.StdCalibrate,
weewx.engine.StdQC, weewx.wxservices.StdWXCalculate
archive_services = weewx.engine.StdArchive
restful_services = weewx.restx.StdStationRegistry,
weewx.restx.StdWunderground, weewx.restx.StdPWSweather, weewx.restx.StdCWOP,
weewx.restx.StdWOW, weewx.restx.StdAWEKAS
report_services = weewx.engine.StdPrint, weewx.engine.StdReport
##############################################################################
[SDR]
# This section is for the software-defined radio driver.
# The driver to use
driver = user.sdr
cmd = rtl_433 -M UTC -F json -G -vv
[[sensor_map]]
windDir = wind_dir.0D67.Acurite5n1Packet
windSpeed = wind_speed.0D67.Acurite5n1Packet
outTemp = temperature.0D67.Acurite5n1Packet
outHumidity = humidity.0D67.Acurite5n1Packet
rain_total = rain_total.0D67.Acurite5n1Packet
inTemp = temperature.0032.AcuriteTowerPacket
inHumidity = humidity.0032.AcuriteTowerPacket
# rain_total = rain_total.0D67.Acurite5n1Packet
[[deltas]]