Cleanup of log messages in CC3000 driver. (#479)

* Robustness enhancements to cc3000 driver including py2/py3 support
cc300.py:
- changes to work on both py2/py3 (tested on both)
- change timeout to 1s (more not needed)
- retry commands when reading cmd echo times out (the signal of failure)
- use timeout of 20s for memory clear (which takes about 12s)
- various changes as assumptions of commands not currently correct (tried on 2 units)
- use weewx.wxformulas.calculate_rain for rain delta
- use weewx.crc16

engine.py:
- Failure to get time from driver is not treated as an error.

rsyncupload.py:
- Add timeout option.
- Change to allow single file to be rsynced.

util/logwatch/scripts/services/weewx:
- Changes to organize very detailed cc3000 output in logwatch reports.

* cc3000 driver cleanup and fixes

* rework genStartup, implement history-since, don't convert to unicode on py2

* fix typo

* fix typo

* Change logwatch script to match capitalization change in wxformulas.calculate_rain

* logwatch change to match engine.py log message change

* Another round of cleaning up the logwatch script.

* remove tabs in logwatch script
This commit is contained in:
John Kline
2019-12-27 03:42:39 -08:00
committed by Tom Keffer
parent 829bc0ee59
commit 1b67d896bc
2 changed files with 146 additions and 98 deletions

View File

@@ -377,7 +377,7 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
}
def __init__(self, **stn_dict):
log.info('driver version is %s' % DRIVER_VERSION)
log.info('Driver version is %s' % DRIVER_VERSION)
global DEBUG_SERIAL
DEBUG_SERIAL = int(stn_dict.get('debug_serial', 0))
@@ -389,18 +389,18 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
self.max_tries = int(stn_dict.get('max_tries', 5))
self.model = stn_dict.get('model', 'CC3000')
port = stn_dict.get('port', CC3000.DEFAULT_PORT)
log.info('using serial port %s' % port)
log.info('Using serial port %s' % port)
self.polling_interval = float(stn_dict.get('polling_interval', 1))
log.info('polling interval is %s seconds' % self.polling_interval)
log.info('Polling interval is %s seconds' % self.polling_interval)
self.use_station_time = weeutil.weeutil.to_bool(
stn_dict.get('use_station_time', True))
log.info('using %s time for loop packets' %
log.info('Using %s time for loop packets' %
('station' if self.use_station_time else 'computer'))
# start with the default sensormap, then augment with user-specified
self.sensor_map = dict(self.DEFAULT_SENSOR_MAP)
if 'sensor_map' in stn_dict:
self.sensor_map.update(stn_dict['sensor_map'])
log.info('sensor map is %s' % self.sensor_map)
log.info('Sensor map is %s' % self.sensor_map)
# periodically check the logger memory, then clear it if necessary.
# these track the last time a check was made, and how often to make
@@ -410,7 +410,7 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
self.last_mem_check = 0
self.mem_interval = 7 * 24 * 3600
if self.logger_threshold is not None:
log.info('clear logger at %s records' % self.logger_threshold)
log.info('Clear logger at %s records' % self.logger_threshold)
# track the last rain counter value so we can determine deltas
self.last_rain = None
@@ -420,16 +420,16 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
# report the station configuration
settings = self._init_station_with_retries(self.station, self.max_tries)
log.info('firmware: %s' % settings['firmware'])
log.info('Firmware: %s' % settings['firmware'])
self.arcint = settings['arcint']
log.info('archive_interval: %s' % self.arcint)
log.info('Archive interval: %s' % self.arcint)
self.header = settings['header']
log.info('header: %s' % self.header)
log.info('Header: %s' % self.header)
self.units = weewx.METRICWX if settings['units'] == 'METRIC' else weewx.US
log.info('units: %s' % settings['units'])
log.info('channel: %s' % settings['channel'])
log.info('charger status: %s' % settings['charger'])
log.info('memory: %s' % self.station.get_memory_status())
log.info('Units: %s' % settings['units'])
log.info('Channel: %s' % settings['channel'])
log.info('Charger status: %s' % settings['charger'])
log.info('Memory: %s' % self.station.get_memory_status())
def genLoopPackets(self):
cmd_mode = True
@@ -445,12 +445,12 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
values = self.station.get_current_data(cmd_mode)
now = int(time.time())
ntries = 0
log.debug("values: %s" % values)
log.debug("Values: %s" % values)
if values:
logged_nodata = False
packet = self._parse_current(
values, self.header, self.sensor_map)
log.debug("parsed: %s" % packet)
log.debug("Parsed: %s" % packet)
if packet and 'dateTime' in packet:
if not self.use_station_time:
packet['dateTime'] = int(time.time() + 0.5)
@@ -460,12 +460,12 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
packet['day_rain_total'], self.last_rain)
self.last_rain = packet['day_rain_total']
else:
log.debug("no rain in packet: %s" % packet)
log.debug("packet: %s" % packet)
log.debug("No rain in packet: %s" % packet)
log.debug("Packet: %s" % packet)
yield packet
else:
if not logged_nodata:
log.info("no data from sensors")
log.info("No data from sensors")
logged_nodata = True
# periodically check memory, clear if necessary
@@ -473,13 +473,13 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
nrec = self.station.get_history_usage()
self.last_mem_check = time.time()
if nrec is None:
log.info("memory check: cannot determine memory usage")
log.info("Memory check: Cannot determine memory usage")
else:
log.info("logger is at %d records, "
log.info("Logger is at %d records, "
"logger clearing threshold is %d" %
(nrec, self.logger_threshold))
if self.logger_threshold is not None and nrec >= self.logger_threshold:
log.info("clearing all records from logger")
log.info("Clearing all records from logger")
self.station.clear_memory()
if self.polling_interval:
@@ -501,12 +501,12 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
- the archive interval is constant for entire history.
- the HDR for archive records is the same as current HDR
"""
log.debug("genStartupRecords: since_ts=%s" % since_ts)
log.debug("GenStartupRecords: since_ts=%s" % since_ts)
log.info('Downloading new records (if any).')
last_rain = None
new_records = 0
for pkt in self.gen_records_since_ts(since_ts):
log.debug("pkt: %s" % pkt)
log.debug("Packet: %s" % pkt)
pkt['usUnits'] = self.units
pkt['interval'] = self.arcint
if 'day_rain_total' in pkt:
@@ -514,8 +514,8 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
pkt['day_rain_total'], last_rain)
last_rain = pkt['day_rain_total']
else:
log.debug("no rain in record: %s" % r)
log.debug("packet: %s" % pkt)
log.debug("No rain in record: %s" % r)
log.debug("Packet: %s" % pkt)
new_records += 1
yield pkt
log.info('Downloaded %d new records.' % new_records)
@@ -583,7 +583,7 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
a failure for any one value, then the entire record fails."""
pkt = dict()
if len(values) != len(header) + 1:
log.info("values/header mismatch: %s %s" % (values, header))
log.info("Values/header mismatch: %s %s" % (values, header))
return pkt
for i, v in enumerate(values):
if i >= len(header):
@@ -600,7 +600,7 @@ class CC3000Driver(weewx.drivers.AbstractDevice):
else:
pkt[label] = float(v)
except ValueError as e:
log.error("parse failed for '%s' '%s': %s (idx=%s values=%s)" %
log.error("Parse failed for '%s' '%s': %s (idx=%s values=%s)" %
(header[i], v, e, i, values))
return dict()
return pkt
@@ -634,10 +634,10 @@ def _check_crc(buf):
try:
cs = buf[idx+1:idx+5]
if DEBUG_CHECKSUM:
log.debug("found checksum at %d: %s" % (idx, cs))
log.debug("Found checksum at %d: %s" % (idx, cs))
a = crc16(buf[0:idx]) # calculate checksum
if DEBUG_CHECKSUM:
log.debug("calculated checksum %x" % a)
log.debug("Calculated checksum %x" % a)
b = int(cs, 16) # checksum provided in data
if a != b:
raise ChecksumMismatch(a, b, buf)
@@ -667,7 +667,7 @@ class CC3000(object):
def open(self, timeoutOverride=None):
if DEBUG_OPENCLOSE:
log.debug("open serial port %s" % self.port)
log.debug("Open serial port %s" % self.port)
to = timeoutOverride if timeoutOverride is not None else self.timeout
self.serial_port = serial.Serial(self.port, self.baudrate,
timeout=to)
@@ -675,7 +675,7 @@ class CC3000(object):
def close(self):
if self.serial_port is not None:
if DEBUG_OPENCLOSE:
log.debug("close serial port %s" % self.port)
log.debug("Close serial port %s" % self.port)
self.serial_port.close()
self.serial_port = None
@@ -686,7 +686,7 @@ class CC3000(object):
# command does not do what is expected.
data = data.encode('ascii', 'ignore')
if DEBUG_SERIAL:
log.debug("write: '%s'" % data)
log.debug("Write: '%s'" % data)
n = self.serial_port.write(data)
if n is not None and n != len(data):
raise weewx.WeeWxIOError("Write expected %d chars, sent %d" %
@@ -699,7 +699,7 @@ class CC3000(object):
"""
data = self.serial_port.readline()
if DEBUG_SERIAL:
log.debug("read: '%s' (%s)" % (data, _format_bytes(data)))
log.debug("Read: '%s' (%s)" % (data, _format_bytes(data)))
data = data.strip()
_check_crc(data)
if sys.version_info[0] >= 3:
@@ -713,11 +713,11 @@ class CC3000(object):
self.flush_output()
def flush_input(self):
log.debug("flush input buffer")
log.debug("Flush input buffer")
self.serial_port.flushInput()
def flush_output(self):
log.debug("flush output buffer")
log.debug("Flush output buffer")
self.serial_port.flushOutput()
def queued_bytes(self):
@@ -855,7 +855,7 @@ class CC3000(object):
log.info("%s: The resetting of timeout to %d took %f seconds." % (cmd, self.timeout, close_open_time))
def get_version(self):
log.debug("get firmware version")
log.debug("Get firmware version")
return self.command("VERSION")
# give the station some time to wake up. when we first hit it with a
@@ -868,13 +868,13 @@ class CC3000(object):
self.command('ECHO=?')
def set_echo(self, cmd='ON'):
log.debug("set echo to %s" % cmd)
log.debug("Set echo to %s" % cmd)
data = self.command('ECHO=%s' % cmd)
if data != 'OK':
raise weewx.WeeWxIOError("Set ECHO failed: %s" % data)
def get_header(self):
log.debug("get header")
log.debug("Get header")
data = self.command("HEADER")
cols = data.split(',')
if cols[0] != 'HDR':
@@ -900,7 +900,7 @@ class CC3000(object):
# unlike all of the other accessor methods, the TIME command returns
# OK after it returns the requested parameter. so we have to pop the
# OK off the serial so it does not trip up other commands.
log.debug("get time")
log.debug("Get time")
tstr = self.command("TIME=?")
if tstr not in ['ERROR', 'OK']:
data = self.read()
@@ -912,7 +912,7 @@ class CC3000(object):
def _compose_set_time_command():
ts = time.time()
tstr = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(ts))
log.info("set time to %s (%s)" % (tstr, ts))
log.info("Set time to %s (%s)" % (tstr, ts))
return "TIME=%s" % tstr
def set_time(self):
@@ -923,11 +923,11 @@ class CC3000(object):
(s, data))
def get_dst(self):
log.debug("get daylight saving")
log.debug("Get daylight saving")
return self.command("DST=?")
def set_dst(self, dst):
log.debug("set DST to %s" % dst)
log.debug("Set DST to %s" % dst)
# Firmware 1.3 Build 022 Dec 02 2016 returns 3 lines (<input-dst>,'',OK)
data = self.command("DST=%s" % dst) # echoed input dst
if data != dst:
@@ -941,33 +941,33 @@ class CC3000(object):
(dst, data))
def get_units(self):
log.debug("get units")
log.debug("Get units")
return self.command("UNITS=?")
def set_units(self, units):
log.debug("set units to %s" % units)
log.debug("Set units to %s" % units)
data = self.command("UNITS=%s" % units)
if data != 'OK':
raise weewx.WeeWxIOError("Failed to set units to %s: %s" %
(units, data))
def get_interval(self):
log.debug("get logging interval")
log.debug("Get logging interval")
return int(self.command("LOGINT=?"))
def set_interval(self, interval=5):
log.debug("set logging interval to %d minutes" % interval)
log.debug("Set logging interval to %d minutes" % interval)
data = self.command("LOGINT=%d" % interval)
if data != 'OK':
raise weewx.WeeWxIOError("Failed to set logging interval: %s" %
data)
def get_channel(self):
log.debug("get channel")
log.debug("Get channel")
return self.command("STATION")
def set_channel(self, channel):
log.debug("set channel to %d" % channel)
log.debug("Set channel to %d" % channel)
if channel < 0 or 3 < channel:
raise ValueError("Channel must be 0-3")
data = self.command("STATION=%d" % channel)
@@ -975,15 +975,15 @@ class CC3000(object):
raise weewx.WeeWxIOError("Failed to set channel: %s" % data)
def get_charger(self):
log.debug("get charger")
log.debug("Get charger")
return self.command("CHARGER")
def get_baro(self):
log.debug("get baro")
log.debug("Get baro")
return self.command("BARO")
def set_baro(self, offset):
log.debug("set barometer offset to %d" % offset)
log.debug("Set barometer offset to %d" % offset)
if offset != '0':
parts = offset.split('.')
if (len(parts) != 2 or
@@ -997,7 +997,7 @@ class CC3000(object):
def get_memory_status(self):
# query for logger memory use. output is something like this:
# 6438 bytes, 111 records, 0%
log.debug("get memory status")
log.debug("Get memory status")
return self.command("MEM=?")
def get_history_usage(self):
@@ -1009,7 +1009,7 @@ class CC3000(object):
return None
def clear_memory(self):
log.debug("clear memory")
log.debug("Clear memory")
data = self.command("MEM=CLEAR")
# It's a long wait for the OK. With a greatly increased timeout
# just for MEM=CLEAR, we should be able to read the OK.
@@ -1019,7 +1019,7 @@ class CC3000(object):
raise weewx.WeeWxIOError("Failed to clear memory: %s" % data)
def get_rain(self):
log.debug("get rain total")
log.debug("Get rain total")
# Firmware 1.3 Build 022 Dec 02 2017 returns OK after the rain count
# This is like TIME=?
rstr = self.command("RAIN")
@@ -1030,7 +1030,7 @@ class CC3000(object):
return rstr
def reset_rain(self):
log.debug("reset rain counter")
log.debug("Reset rain counter")
data = self.command("RAIN=RESET")
if data != 'OK':
raise weewx.WeeWxIOError("Failed to reset rain: %s" % data)
@@ -1123,7 +1123,7 @@ class CC3000(object):
if need_cmd:
cmd_cnt += 1
if cmd_cnt > cmd_max:
msg = "download failed after %d attempts" % cmd_max
msg = "Download failed after %d attempts" % cmd_max
log.error(msg)
raise weewx.WeeWxIOError(msg)
cmd = "DOWNLOAD"
@@ -1136,7 +1136,7 @@ class CC3000(object):
try:
data = self.read()
if data == 'OK':
log.debug("downloaded %d records, yielded %d" % (n, yielded))
log.debug("Downloaded %d records, yielded %d" % (n, yielded))
break
elif ',' in data:
values = data.split(',')
@@ -1153,20 +1153,20 @@ class CC3000(object):
elif values[0] == '':
# FIXME: observed 'input overrun' on rpi2 with debian 7
# so try sending another download command to unhang it.
log.error("download hung, initiate another download")
log.error("Download hung, initiate another download")
need_cmd = True
else:
cmd_cnt = 0
log.error("bad record %s '%s' (%s)" %
log.error("Bad record %s '%s' (%s)" %
(n, values[0], data))
elif 'DOWNLOAD' in data:
# some firmware echos the command, but not always
pass
else:
log.error("unexpected response to DOWNLOAD: '%s'" % data)
log.error("Unexpected response to DOWNLOAD: '%s'" % data)
need_cmd = True
except ChecksumError as e:
log.error("download failed for record %s: %s" % (n, e))
log.error("Download failed for record %s: %s" % (n, e))
class CC3000ConfEditor(weewx.drivers.AbstractConfEditor):
@@ -1283,16 +1283,16 @@ if __name__ == '__main__':
if options.getver:
print(s.get_version())
if options.status:
print("firmware:", s.get_version())
print("time:", s.get_time())
print("dst:", s.get_dst())
print("units:", s.get_units())
print("memory:", s.get_memory_status())
print("interval:", s.get_interval() * 60)
print("channel:", s.get_channel())
print("charger:", s.get_charger())
print("baro:", s.get_baro())
print("rain:", s.get_rain())
print("Firmware:", s.get_version())
print("Time:", s.get_time())
print("DST:", s.get_dst())
print("Units:", s.get_units())
print("Memory:", s.get_memory_status())
print("Interval:", s.get_interval() * 60)
print("Channel:", s.get_channel())
print("Charger:", s.get_charger())
print("Baro:", s.get_baro())
print("Rain:", s.get_rain())
if options.getch:
print(s.get_channel())
if options.setch is not None:

View File

@@ -50,7 +50,7 @@ my $ACURITE_BOGUS_STRENGTH = 'acurite: R1: bogus signal strength';
# BARO, CHARGER, DST=?, HEADER, LOGINT=?, MEM=?, NOW, STATION, TIME=?, UNITS=?, RAIN, VERSION
my $CC3000_VALUES_HEADER_MISMATCHES = 'cc3000: values/header mismatch';
my $CC3000_VALUES_HEADER_MISMATCHES = 'cc3000: Values/Header mismatch';
my $CC3000_BARO_CMD_ECHO_TIMED_OUT = 'cc3000: BARO cmd echo timed out';
my $CC3000_BARO_MISSING_COMMAND_ECHO = 'cc3000: BARO echoed as empty string';
@@ -323,7 +323,7 @@ while(defined($_ = <STDIN>)) {
$counts{$ACURITE_BOGUS_STRENGTH} += 1;
} elsif (/purpleair: /) {
push @purpleair, $_;
} elsif (/cc3000: values/) {
} elsif (/cc3000: Values\/header mismatch/) {
$counts{$CC3000_VALUES_HEADER_MISMATCHES} += 1;
# BARO, CHARGER, DST=?, HEADER, LOGINT=?, MEM=?, NOW, RAIN, STATION, TIME=?, UNITS=?, VERSION
@@ -440,10 +440,10 @@ while(defined($_ = <STDIN>)) {
} elsif (/cc3000: VERSION: Retry failed./) {
$counts{$CC3000_VERSION_FAILED_RETRIES} += 1;
} elsif (/engine: error reading time: Failed to get time/) {
} elsif (/engine: Error reading time: Failed to get time/) {
$counts{$CC3000_GET_TIME_FAILED} += 1;
} elsif (/cc3000: set time to /) {
} elsif (/cc3000: Set time to /) {
$counts{$CC3000_SET_TIME_SUCCEEDED} += 1;
} elsif (/cc3000: MEM=CLEAR succeeded./) {
@@ -456,11 +456,11 @@ while(defined($_ = <STDIN>)) {
} elsif (/cc3000: .*: times: .*/) {
push @cc3000_timings, $_;
# cc3000: logger is at 11475 records, logger clearing threshold is 10000
} elsif (/cc3000: logger is at.*/) {
# cc3000: Logger is at 11475 records, logger clearing threshold is 10000
} elsif (/cc3000: Logger is at.*/) {
push @cc3000_mem_clear_info, $_;
# cc3000: clearing all records from logger
} elsif (/cc3000: clearing all records from logger/) {
# cc3000: Clearing all records from logger
} elsif (/cc3000: Clearing all records from logger/) {
push @cc3000_mem_clear_info, $_;
# cc3000: MEM=CLEAR: The resetting of timeout to 20 took 0.001586 seconds.
# cc3000: MEM=CLEAR: The resetting of timeout to 1 took 0.001755 seconds.
@@ -570,7 +570,11 @@ while(defined($_ = <STDIN>)) {
/engine: Daily summaries up to date/ ||
/engine: Using binding/ ||
/engine: Archive will use/ ||
/engine: Starting backfill of daily summaries/ ||
/engine: Platform/ ||
/engine: Locale is/ ||
/wxservices: The following values will be calculated:/ ||
/wxservices: The following algorithms will be used for calculations:/ ||
/manager: Starting backfill of daily summaries/ ||
/manager: Created daily summary tables/ ||
/cheetahgenerator: Running / ||
/cheetahgenerator: skip/ ||
@@ -711,24 +715,57 @@ while(defined($_ = <STDIN>)) {
/te923: STT / ||
/te923: Bad read \(attempt \d of/ || # normal when debugging
/te923: usb error.* No data available/ || # normal when debug=1
/cc3000: found checksum at/ ||
/cc3000: calculated checksum/ ||
/cc3000: record/ ||
/cc3000: got bytes/ ||
/cc3000: open serial port/ ||
/cc3000: close serial port/ ||
/cc3000: station replied to command with/ ||
/cc3000: driver version is/ ||
/cc3000: polling interval is/ ||
/cc3000: pressure offset is/ ||
/cc3000: header is/ ||
/cc3000: units are/ ||
/cc3000: using station time/ ||
/cc3000: using computer time/ ||
/cc3000: using serial port/ ||
/cc3000: set echo to/ ||
/cc3000: get header/ ||
/cc3000: get units/ ||
/cc3000: Archive interval:/ ||
/cc3000: Calculated checksum/ ||
/cc3000: Channel:/ ||
/cc3000: Charger status:/ ||
/cc3000: Clear logger at/ ||
/cc3000: Clear memory/ ||
/cc3000: Close serial port/ ||
/cc3000: Downloaded \d+ new records/ ||
/cc3000: Downloaded \d+ records, yielded \d+/ ||
/cc3000: Downloading new records/ ||
/cc3000: Driver version is/ ||
/cc3000: Firmware:/ ||
/cc3000: Found checksum at/ ||
/cc3000: Flush input bugger/ ||
/cc3000: Flush output bugger/ ||
/cc3000: gen_records: Requested \d+ latest of \d+ records/ ||
/cc3000: gen_records_since_ts: Asking for \d+ records/ ||
/cc3000: GenStartupRecords: since_ts/ ||
/cc3000: Get baro/ ||
/cc3000: Get channel/ ||
/cc3000: Get charger/ ||
/cc3000: Get daylight saving/ ||
/cc3000: Get firmware version/ ||
/cc3000: Get header/ ||
/cc3000: Get logging interval/ ||
/cc3000: Get memory status/ ||
/cc3000: Get rain total/ ||
/cc3000: Get time/ ||
/cc3000: Get units/ ||
/cc3000: Header:/ ||
/cc3000: Memory:/ ||
/cc3000: No rain in packet:/ ||
/cc3000: Open serial port/ ||
/cc3000: Packet:/ ||
/cc3000: Parsed:/ ||
/cc3000: Polling interval is/ ||
/cc3000: Read:/ ||
/cc3000: Reset rain counter/ ||
/cc3000: Sensor map is/ ||
/cc3000: Set barometer offset to/ ||
/cc3000: Set channel to/ ||
/cc3000: Set DST to/ ||
/cc3000: Set echo to/ ||
/cc3000: Set logging interval to/ ||
/cc3000: Set units to/ ||
/cc3000: Units:/ ||
/cc3000: Using computer time/ ||
/cc3000: Using serial port/ ||
/cc3000: Using station time/ ||
/cc3000: Values:/ ||
/cc3000: Write:/ ||
/owfs: driver version is / ||
/owfs: interface is / ||
/owfs: polling interval is / ||
@@ -755,6 +792,7 @@ while(defined($_ = <STDIN>)) {
/forecast: XTideThread: XTide: got no tidal events/ ||
/forecast: .*: forecast version/ ||
/restx: StationRegistry: Station will/ ||
/restx: StationRegistry: Registration not requested/ ||
/restx: .*Data will be uploaded/ ||
/restx: .*wait interval/ ||
/restx: Shut down/ ||
@@ -762,6 +800,16 @@ while(defined($_ = <STDIN>)) {
/restx: \S+: service version is/ ||
/restx: \S+: skipping upload/ ||
/restx: \S+: desired unit system is/ ||
/restx: AWEKAS: Posting not enabled/ ||
/restx: Wunderground: Posting not enabled/ ||
/restx: PWSweather: Posting not enabled/ ||
/restx: CWOP: Posting not enabled/ ||
/restx: WOW: Posting not enabled/ ||
/restx: AWEKAS: Data for station/ ||
/restx: Wunderground: Data for station/ ||
/restx: PWSweather: Posting not enabled/ ||
/restx: CWOP: Posting not enabled/ ||
/restx: WOW: Data for station/ ||
/restx: AWEKAS: url/ ||
/restx: EmonCMS: url/ ||
/restx: EmonCMS: prefix is/ ||