apply configurator pattern to ws28xx driver

This commit is contained in:
Matthew Wall
2014-10-31 03:18:27 +00:00
parent 6479cef141
commit a043379f66
6 changed files with 206 additions and 263 deletions

View File

@@ -1,245 +0,0 @@
#!/usr/bin/env python
# $Id$
#
# Copyright 2013 Matthew Wall
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# See http://www.gnu.org/licenses/
"""Command line utility for configuring LaCrosse WS-28xx weather stations
Many thanks to Eddie De Pieri, who did the first python implementation of a
WS-28xx driver, and Lucas Heijst, who sorted out the communication and timing
issues between weather station console and transceiver.
"""
import optparse
import syslog
import time
import sys
import weewx.drivers.ws28xx
import weewx.units
import weeutil.weeutil
description = """Configuration utility for WS-28xx weather stations."""
usage = """%prog [config_file] [options] [--debug]"""
epilog = """Mutating actions will request confirmation before proceeding."""
def main():
syslog.openlog('wee_config_ws28xx', syslog.LOG_PID|syslog.LOG_CONS)
# Create a command line parser:
parser = optparse.OptionParser(description=description, usage=usage, epilog=epilog)
# Add the various options:
parser.add_option("--config", dest="cfgfn", type=str, metavar="FILE",
help="configuration file")
parser.add_option("--check-transceiver", dest="check", action="store_true",
help="check USB transceiver")
parser.add_option("--pair", dest="pair", action="store_true",
help="pair the USB transceiver with a station console")
parser.add_option("--info", dest="info", action="store_true",
help="display weather station configuration")
parser.add_option("--set-interval", dest="interval", type=int, metavar="N",
help="set logging interval to N minutes")
parser.add_option("--current", dest="current", action="store_true",
help="get the current weather conditions")
parser.add_option("--history-since", dest="recmin", type=int, metavar="N",
help="display history records since N minutes ago")
parser.add_option("--history", dest="nrecords", type=int, metavar="N",
help="display N history records")
parser.add_option("--maxtries", dest="maxtries", type=int,
help="maximum number of retries, 0 indicates no max")
parser.add_option("-y", dest="noprompt", action="store_true",
help="answer yes to every prompt")
parser.add_option("--debug", dest="debug", action="store_true",
help="display diagnostic information while running")
# Now we are ready to parse the command line:
(options, args) = parser.parse_args()
if options.debug is not None:
weewx.debug = options.debug
config_fn, config_dict = weeutil.weeutil.read_config(options.cfgfn, args)
print 'Using configuration file %s' % config_fn
print 'Driver version %s' % weewx.drivers.ws28xx.DRIVER_VERSION
altitude_m = weewx.units.getAltitudeM(config_dict)
station = weewx.drivers.ws28xx.WS28xx(altitude=altitude_m,
**config_dict['WS28xx'])
if options.noprompt:
prompt = False
else:
prompt = True
maxtries = 3 if options.maxtries is None else int(options.maxtries)
if options.check:
check_transceiver(station, maxtries)
elif options.pair:
pair(station, maxtries)
elif options.interval is not None:
set_interval(station, maxtries, options.interval, prompt)
elif options.current:
current(station, maxtries)
elif options.nrecords is not None:
history(station, maxtries, count=options.nrecords)
elif options.recmin is not None:
ts = int(time.time()) - options.recmin * 60
history(station, maxtries, ts=ts)
else:
info(station, maxtries)
station.shutDown()
def check_transceiver(station, maxtries):
"""See if the transceiver is installed and operational."""
print 'Checking for transceiver...'
ntries = 0
while ntries < maxtries:
ntries += 1
if station.transceiver_is_present():
print 'Transceiver is present'
sn = station.get_transceiver_serial()
print 'serial: %s' % sn
tid = station.get_transceiver_id()
print 'id: %d (0x%04x)' % (tid, tid)
break
print 'Not found (attempt %d of %d) ...' % (ntries, maxtries)
time.sleep(5)
else:
print 'Transceiver not responding.'
def pair(station, maxtries):
"""Pair the transceiver with the station console."""
print 'Pairing transceiver with console...'
maxwait = 90 # how long to wait between button presses, in seconds
ntries = 0
while ntries < maxtries or maxtries == 0:
if station.transceiver_is_paired():
print 'Transceiver is paired to console'
break
ntries += 1
msg = 'Press and hold the [v] key until "PC" appears'
if maxtries > 0:
msg += ' (attempt %d of %d)' % (ntries, maxtries)
else:
msg += ' (attempt %d)' % ntries
print msg
now = start_ts = int(time.time())
while now - start_ts < maxwait and not station.transceiver_is_paired():
time.sleep(5)
now = int(time.time())
else:
print 'Transceiver not paired to console.'
def get_interval(station, maxtries):
cfg = get_config(station, maxtries)
if cfg is None:
return None
return weewx.drivers.ws28xx.getHistoryInterval(cfg['history_interval'])
def get_config(station, maxtries):
start_ts = None
ntries = 0
while ntries < maxtries or maxtries == 0:
cfg = station.get_config()
if cfg is not None:
return cfg
ntries += 1
if start_ts is None:
start_ts = int(time.time())
else:
dur = int(time.time()) - start_ts
print 'No data after %d seconds (press SET to sync)' % dur
time.sleep(30)
return None
def set_interval(station, maxtries, interval, prompt):
"""Set the station archive interval"""
print "This feature is not yet implemented"
def info(station, maxtries):
"""Query the station then display the settings."""
print 'Querying the station for the configuration...'
cfg = get_config(station, maxtries)
if cfg is not None:
print_dict(cfg)
def current(station, maxtries):
"""Get current weather observation."""
print 'Querying the station for current weather data...'
start_ts = None
ntries = 0
while ntries < maxtries or maxtries == 0:
packet = station.get_observation()
if packet is not None:
print_dict(packet)
break
ntries += 1
if start_ts is None:
start_ts = int(time.time())
else:
dur = int(time.time()) - start_ts
print 'No data after %d seconds (press SET to sync)' % dur
time.sleep(30)
def history(station, maxtries, ts=0, count=0):
"""Display the indicated number of records or the records since the
specified timestamp (local time, in seconds)"""
print "Querying the station for historical records..."
ntries = 0
last_n = n = nrem = None
last_ts = now = int(time.time())
station.start_caching_history(since_ts=ts, num_rec=count)
while nrem is None or nrem > 0:
if ntries >= maxtries:
print 'Giving up after %d tries' % ntries
break
time.sleep(30)
ntries += 1
now = int(time.time())
n = station.get_num_history_scanned()
if n == last_n:
dur = now - last_ts
print 'No data after %d seconds (press SET to sync)' % dur
else:
ntries = 0
last_ts = now
last_n = n
nrem = station.get_uncached_history_count()
ni = station.get_next_history_index()
li = station.get_latest_history_index()
msg = " scanned %s records: current=%s latest=%s remaining=%s\r" % (n, ni, li, nrem)
sys.stdout.write(msg)
sys.stdout.flush()
station.stop_caching_history()
records = station.get_history_cache_records()
station.clear_history_cache()
print
print 'Found %d records' % len(records)
for r in records:
print r
def print_dict(data):
for x in sorted(data.keys()):
if x == 'dateTime':
print '%s: %s' % (x, weeutil.weeutil.timestamp_to_string(data[x]))
else:
print '%s: %s' % (x, data[x])
if __name__=="__main__" :
main()

View File

@@ -59,9 +59,10 @@ class DeviceConfigurator(object):
if options.debug is not None:
weewx.debug = options.debug
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
self.do_config(options, config_dict)
prompt = False if options.noprompt else True
self.do_config(options, config_dict, prompt)
def do_config(self, options, config_dict):
def do_config(self, options, config_dict, prompt):
raise NotImplementedError("Method 'do_config' not implemented")
def get_parser(self):

View File

@@ -84,9 +84,8 @@ class CC3000Configurator(weewx.abstractstation.DeviceConfigurator):
parser.add_option("--set-units", dest="units", metavar="UNITS",
help="set units to METRIC or ENGLISH")
def do_config(self, options, config_dict):
def do_config(self, options, config_dict, prompt):
self.station = CC3000Driver(**config_dict['CC3000'])
prompt = False if options.noprompt else True
if options.nrecords is not None:
self.show_history(options.nrecords)
elif options.current:

View File

@@ -247,7 +247,7 @@ class TE923Configurator(weewx.abstractstation.DeviceConfigurator):
type=str, metavar="FORMAT",
help="format for history: raw, table, or dict")
def do_config(self, options, config_dict):
def do_config(self, options, config_dict, prompt):
if options.format is None:
fmt = 'table'
elif (options.format.lower() != 'raw' and
@@ -257,7 +257,6 @@ class TE923Configurator(weewx.abstractstation.DeviceConfigurator):
exit(1)
else:
fmt = options.format.lower()
prompt = False if options.noprompt else True
self.station = TE923Driver(**config_dict['TE923'])
if options.current:

View File

@@ -294,9 +294,7 @@ class WS23xxConfigurator(weewx.abstractstation.DeviceConfigurator):
parser.add_option("--clear-memory", dest="clear", action="store_true",
help="clear station memory")
def do_config(self, options, config_dict):
prompt = False if options.noprompt else True
def do_config(self, options, config_dict, prompt):
self.station = WS23xxDriver(**config_dict['WS23xx'])
if options.current:
self.show_current()

View File

@@ -924,6 +924,7 @@ Step 8. Go to step 1 to wait for state 0xde16 again.
from datetime import datetime
import StringIO
import sys
import syslog
import threading
import time
@@ -1002,9 +1003,9 @@ def get_next_index(idx):
def get_index(idx):
if idx < 0:
return idx + WS28xx.max_records
elif idx >= WS28xx.max_records:
return idx - WS28xx.max_records
return idx + WS28xxDriver.max_records
elif idx >= WS28xxDriver.max_records:
return idx - WS28xxDriver.max_records
return idx
def tstr_to_ts(tstr):
@@ -1021,10 +1022,200 @@ def addr_to_index(addr):
def index_to_addr(idx):
return 18 * idx + 416
def loader(config_dict, engine):
return WS28xx(**config_dict['WS28xx'])
class WS28xx(weewx.abstractstation.AbstractStation):
def print_dict(data):
for x in sorted(data.keys()):
if x == 'dateTime':
print '%s: %s' % (x, weeutil.weeutil.timestamp_to_string(data[x]))
else:
print '%s: %s' % (x, data[x])
def config_loader(config_dict):
return WS28xxConfigurator()
class WS28xxConfigurator(weewx.abstractstation.DeviceConfigurator):
@property
def version(self):
return DRIVER_VERSION
def add_options(self, parser):
super(WS28xxConfigurator, self).add_options(parser)
parser.add_option("--check-transceiver", dest="check",
action="store_true",
help="check USB transceiver")
parser.add_option("--pair", dest="pair", action="store_true",
help="pair the USB transceiver with station console")
parser.add_option("--info", dest="info", action="store_true",
help="display weather station configuration")
parser.add_option("--set-interval", dest="interval",
type=int, metavar="N",
help="set logging interval to N minutes")
parser.add_option("--current", dest="current", action="store_true",
help="get the current weather conditions")
parser.add_option("--history", dest="nrecords", type=int, metavar="N",
help="display N history records")
parser.add_option("--history-since", dest="recmin",
type=int, metavar="N",
help="display history records since N minutes ago")
parser.add_option("--maxtries", dest="maxtries", type=int,
help="maximum number of retries, 0 indicates no max")
def do_config(self, options, config_dict, prompt):
maxtries = 3 if options.maxtries is None else int(options.maxtries)
self.station = WS28xxDriver(**config_dict['WS28xx'])
if options.check:
self.check_transceiver(maxtries)
elif options.pair:
self.pair(maxtries)
elif options.interval is not None:
self.set_interval(maxtries, options.interval, prompt)
elif options.current:
self.show_current(maxtries)
elif options.nrecords is not None:
self.show_history(maxtries, count=options.nrecords)
elif options.recmin is not None:
ts = int(time.time()) - options.recmin * 60
self.show_history(maxtries, ts=ts)
else:
self.show_info(maxtries)
self.station.closePort()
def check_transceiver(self, maxtries):
"""See if the transceiver is installed and operational."""
print 'Checking for transceiver...'
ntries = 0
while ntries < maxtries:
ntries += 1
if self.station.transceiver_is_present():
print 'Transceiver is present'
sn = self.station.get_transceiver_serial()
print 'serial: %s' % sn
tid = self.station.get_transceiver_id()
print 'id: %d (0x%04x)' % (tid, tid)
break
print 'Not found (attempt %d of %d) ...' % (ntries, maxtries)
time.sleep(5)
else:
print 'Transceiver not responding.'
def pair(self, maxtries):
"""Pair the transceiver with the station console."""
print 'Pairing transceiver with console...'
maxwait = 90 # how long to wait between button presses, in seconds
ntries = 0
while ntries < maxtries or maxtries == 0:
if self.station.transceiver_is_paired():
print 'Transceiver is paired to console'
break
ntries += 1
msg = 'Press and hold the [v] key until "PC" appears'
if maxtries > 0:
msg += ' (attempt %d of %d)' % (ntries, maxtries)
else:
msg += ' (attempt %d)' % ntries
print msg
now = start_ts = int(time.time())
while (now - start_ts < maxwait and
not self.station.transceiver_is_paired()):
time.sleep(5)
now = int(time.time())
else:
print 'Transceiver not paired to console.'
def get_interval(self, maxtries):
cfg = self.get_config(maxtries)
if cfg is None:
return None
return weewx.drivers.ws28xx.getHistoryInterval(cfg['history_interval'])
def get_config(self, maxtries):
start_ts = None
ntries = 0
while ntries < maxtries or maxtries == 0:
cfg = self.station.get_config()
if cfg is not None:
return cfg
ntries += 1
if start_ts is None:
start_ts = int(time.time())
else:
dur = int(time.time()) - start_ts
print 'No data after %d seconds (press SET to sync)' % dur
time.sleep(30)
return None
def set_interval(self, maxtries, interval, prompt):
"""Set the station archive interval"""
print "This feature is not yet implemented"
def info(self, maxtries):
"""Query the station then display the settings."""
print 'Querying the station for the configuration...'
cfg = self.get_config(maxtries)
if cfg is not None:
print_dict(cfg)
def current(self, maxtries):
"""Get current weather observation."""
print 'Querying the station for current weather data...'
start_ts = None
ntries = 0
while ntries < maxtries or maxtries == 0:
packet = self.station.get_observation()
if packet is not None:
print_dict(packet)
break
ntries += 1
if start_ts is None:
start_ts = int(time.time())
else:
dur = int(time.time()) - start_ts
print 'No data after %d seconds (press SET to sync)' % dur
time.sleep(30)
def history(self, maxtries, ts=0, count=0):
"""Display the indicated number of records or the records since the
specified timestamp (local time, in seconds)"""
print "Querying the station for historical records..."
ntries = 0
last_n = n = nrem = None
last_ts = now = int(time.time())
self.station.start_caching_history(since_ts=ts, num_rec=count)
while nrem is None or nrem > 0:
if ntries >= maxtries:
print 'Giving up after %d tries' % ntries
break
time.sleep(30)
ntries += 1
now = int(time.time())
n = self.station.get_num_history_scanned()
if n == last_n:
dur = now - last_ts
print 'No data after %d seconds (press SET to sync)' % dur
else:
ntries = 0
last_ts = now
last_n = n
nrem = self.station.get_uncached_history_count()
ni = self.station.get_next_history_index()
li = self.station.get_latest_history_index()
msg = " scanned %s records: current=%s latest=%s remaining=%s\r" % (n, ni, li, nrem)
sys.stdout.write(msg)
sys.stdout.flush()
self.station.stop_caching_history()
records = self.station.get_history_cache_records()
self.station.clear_history_cache()
print
print 'Found %d records' % len(records)
for r in records:
print r
def loader(config_dict, engine):
return WS28xxDriver(**config_dict['WS28xx'])
class WS28xxDriver(weewx.abstractstation.AbstractStation):
"""Driver for LaCrosse WS28xx stations."""
max_records = 1797
@@ -3367,7 +3558,7 @@ class CCommunicationService(object):
hidx = self.history_cache.next_index
elif self.DataStore.getLastHistoryIndex() is not None:
hidx = self.DataStore.getLastHistoryIndex()
if hidx is None or hidx < 0 or hidx >= WS28xx.max_records:
if hidx is None or hidx < 0 or hidx >= WS28xxDriver.max_records:
haddr = 0xffffff
else:
haddr = index_to_addr(hidx)
@@ -3822,8 +4013,8 @@ class CCommunicationService(object):
def startCachingHistory(self, since_ts=0, num_rec=0):
self.history_cache.clear_records()
self.history_cache.since_ts = since_ts
if num_rec > WS28xx.max_records - 2:
num_rec = WS28xx.max_records - 2
if num_rec > WS28xxDriver.max_records - 2:
num_rec = WS28xxDriver.max_records - 2
self.history_cache.num_rec = num_rec
self.command = EAction.aGetHistory