Files
weewx/bin/wee_config_fousb

463 lines
18 KiB
Python
Executable File

#!/usr/bin/env python
# $Id$
#
# Copyright 2012 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 Fine Offset weather stations
Many thanks to Jim Easterbrook, author of pywws upon which this is based.
The station model, version, and id are supposed to be reported by these
instruments, but so far (04jan2013) my testing shows bogus values for these
fields. The documentation indicates that the clock should be settable, but
so far (14feb2013) my testing shows this is not the case.
"""
import datetime
import optparse
import shutil
import sys
import syslog
import tempfile
import time
import weewx.drivers.fousb
import weewx.units
import weeutil.weeutil
description = """Configuration utility for Fine Offset weather stations."""
usage = """%prog [config_file] [options] [--help]"""
epilog = """Mutating actions will request confirmation before proceeding.
Attempts to set the station clock will probably fail."""
def main():
syslog.openlog('wee_config_fousb', 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="use configuration file FILE")
parser.add_option("--info", dest="info", action="store_true",
help="display weather station configuration")
parser.add_option("--check-pressures", dest="chkpres", action="store_true",
help="query station for pressure sensor data")
parser.add_option("--check-units", dest="chkunits", action="store_true",
help="compare raw and converted LOOP packets")
parser.add_option("--check-usb", dest="chkusb", action="store_true",
help="test the quality of the USB connection")
parser.add_option("--check-fixed-block", dest="chkfb", action="store_true",
help="monitor the contents of the fixed block")
parser.add_option("--fixed-block", dest="showfb", action="store_true",
help="display the contents of the fixed block")
parser.add_option("--live", dest="live", action="store_true",
help="display live readings from the station")
parser.add_option("--logged", dest="logged", action="store_true",
help="display logged readings from the station")
parser.add_option("--history-since", dest="recmin", type=int, metavar="N",
help="display records since N minutes ago")
parser.add_option("--history", dest="nrecords", type=int, metavar="N",
help="display N records")
parser.add_option("--format", dest="format", type=str, metavar="FORMAT",
help="format for output, one of raw, table, or dict")
parser.add_option("--set-clock", dest="clock", action="store_true",
help="set station clock to computer time")
parser.add_option("--set-pressure", dest="pressure",type=float,metavar="P",
help="set relative pressure to P hPa (mbar)")
parser.add_option("--set-interval", dest="interval", type=int, metavar="N",
help="set logging interval to N minutes")
parser.add_option("--clear-memory", dest="clear", action="store_true",
help="clear station memory")
parser.add_option("--slp", dest="slp", type=float, metavar="SLP",
help="calculate pressure offset from sea level pressure SLP")
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
altitude_m = weewx.drivers.fousb.getaltitudeM(config_dict)
station = weewx.drivers.fousb.FineOffsetUSB(altitude=altitude_m,
**config_dict['FineOffsetUSB'])
if options.noprompt:
prompt = False
else:
prompt = True
if options.format is None:
options.format = 'table'
elif (options.format.lower() != 'raw' and
options.format.lower() != 'table' and
options.format.lower() != 'dict'):
print "Unknown format '%s'. Known formats include 'raw', 'table', and 'dict'." % options.format
exit(1)
if options.nrecords is not None:
showrecords(station, 0, options.nrecords, options.format)
elif options.recmin is not None:
showrecords(station, time.time()-options.recmin*60, 0, options.format)
elif options.live:
showreadings(station, False)
elif options.logged:
showreadings(station, True)
elif options.showfb:
showfixedblock(station)
elif options.chkunits:
checkunits(station, config_dict['StdConvert']['target_unit'])
elif options.chkpres:
checkpressures(station, config_dict['StdConvert']['target_unit'])
elif options.chkusb:
checkusb(station)
elif options.chkfb:
checkfixedblock(station)
elif options.clock:
setclock(station, prompt)
elif options.pressure is not None:
setpressure(station, options.pressure, prompt)
elif options.interval is not None:
setinterval(station, options.interval, prompt)
elif options.clear:
clearhistory(station, prompt)
elif options.slp is not None:
calcoffset(station, options.slp, config_dict, config_fn, prompt)
else:
info(station)
station.closePort()
def info(station):
"""Query the station then display the settings."""
print "Querying the station..."
val = getvalues(station, '', weewx.drivers.fousb.fixed_format)
print 'Fine Offset station settings:'
print '%s: %s' % ('local time'.rjust(30),
time.strftime('%Y.%m.%d %H:%M:%S %Z', time.localtime()))
print '%s: %s' % ('polling mode'.rjust(30), station.polling_mode)
slist = {'values':[], 'minmax_values':[],
'settings':[], 'display_settings':[], 'alarm_settings':[]}
for x in sorted(val.keys()):
if type(val[x]) is dict:
for y in val[x].keys():
label = x + '.' + y
s = fmtparam(label, val[x][y])
slist = stash(slist, s)
else:
s = fmtparam(x, val[x])
slist = stash(slist, s)
for k in ('values','minmax_values','settings','display_settings','alarm_settings'):
print ''
for s in slist[k]:
print s
def calcoffset(station, slp, config_dict, config_fn, prompt):
"""Calculate the pressure offset given a confirmed sea-level pressure.
Provide option of saving the offset to the configuration file."""
print "Querying the station..."
for packet in station.get_observations():
sp = packet['abs_pressure']
st = packet['temp_out']
break
if weewx.debug:
print "altitude=", station.altitude
print "abs_pressure=", sp
print "temp_out=", st
slp = float(slp)
pt = weewx.drivers.fousb.etterm(station.altitude, st)
newoffset = slp * pt - sp
oldoffset = int(config_dict['FineOffsetUSB'].get('pressure_offset', 0))
ans = None
while ans not in ['y', 'n']:
print "Pressure offset is", oldoffset
if prompt:
ans = raw_input("Set pressure offset to %f hPa (y/n)? " % newoffset)
else:
print "Setting pressure offset to %f hPa" % newoffset
ans = 'y'
if ans == 'y' :
config_dict['FineOffsetUSB']['pressure_offset'] = newoffset
tmpfile = tempfile.NamedTemporaryFile("w", delete=False)
config_dict.write(tmpfile)
tmpfile.close()
config_bak = config_fn + time.strftime(".%Y%m%d%H%M%S")
shutil.move(config_fn, config_bak)
shutil.move(tmpfile.name, config_fn)
print "Pressure offset is now", newoffset
elif ans == 'n':
print "Pressure offset unchanged."
def checkpressures(station, target_unit_name):
"""Query the station then display pressure-related sensor readings."""
target_unit = weewx.units.unit_constants[target_unit_name.upper()]
converter = weewx.units.StdUnitConverters[target_unit]
print "Querying the station..."
for packet in station.genLoopPackets():
sp = packet['pressure']
ap = station.get_fixed_block(['abs_pressure'])
rp = station.get_fixed_block(['rel_pressure'])
ap1 = weewx.wxformulas.altimeter_pressure_Metric(sp, station.altitude)
ap2 = weewx.drivers.fousb.sp2ap(sp, station.altitude)
bp2 = weewx.drivers.fousb.sp2bp(sp, station.altitude, packet['outTemp'])
print 'altitude: %s meters' % station.altitude
print 'pressure_offset: %s' % str(station.pressure_offset)
print 'station pressure (sensor): %s' % sp
print 'absolute pressure (fixed_block): %s' % ap
print 'relative pressure (fixed_block): %s' % rp
print 'altimeter pressure (davis algorithm): %s' % ap1
print 'altimeter pressure (noaa algorithm): %s' % ap2
print 'barometer pressure (wview algorithm): %s' % bp2
print 'raw LOOP packet:'
print packet
cpkt = converter.convertDict(packet)
print 'converted LOOP packet:'
print cpkt
break
def checkunits(station, target_unit_name):
"""Query the station then display raw and converted sensor readings."""
target_unit = weewx.units.unit_constants[target_unit_name.upper()]
converter = weewx.units.StdUnitConverters[target_unit]
print "Querying the station..."
for packet in station.genLoopPackets():
print 'target_unit: %s' % target_unit_name
print 'raw LOOP packet:'
print packet
print 'converted LOOP packet:'
print converter.convertDict(packet)
break
def checkusb(station):
"""Run diagnostics on the USB connection."""
print "This will read from the station console repeatedly to see if there"
print "are errors in the USB communications. Leave this running for an"
print "hour or two to see if any bad reads are encountered. Bad reads"
print "will be reported in the system log. A few bad reads per hour is"
print "usually acceptable."
ptr = weewx.drivers.fousb.data_start
total_count = 0
bad_count = 0
while True:
if total_count % 1000 == 0:
active = station.current_pos()
while True:
ptr += 0x20
if ptr >= 0x10000:
ptr = weewx.drivers.fousb.data_start
if active < ptr - 0x10 or active >= ptr + 0x20:
break
result_1 = station._read_block(ptr, retry=False)
result_2 = station._read_block(ptr, retry=False)
if result_1 != result_2:
syslog.syslog(syslog.LOG_INFO, 'read_block changing %06x' % ptr)
syslog.syslog(syslog.LOG_INFO, ' %s' % str(result_1))
syslog.syslog(syslog.LOG_INFO, ' %s' % str(result_2))
bad_count += 1
total_count += 1
print "\rbad/total: %d/%d " % (bad_count, total_count),
sys.stdout.flush()
def checkfixedblock(station):
"""Display changes to fixed block as they occur."""
print 'This will read the fixed block then display changes as they occur.'
print 'Typically the most common change is the incrementing of the data'
print 'pointer, which happens whenever readings are saved to the station'
print 'memory. For example, if the logging interval is set to 5 minutes,'
print 'the fixed block should change at least every 5 minutes.'
raw_fixed = station.get_raw_fixed_block()
while True:
new_fixed = station.get_raw_fixed_block(unbuffered=True)
for ptr in range(len(new_fixed)):
if new_fixed[ptr] != raw_fixed[ptr]:
print datetime.datetime.now().strftime('%H:%M:%S'),
print ' %04x (%d) %02x -> %02x' % (
ptr, ptr, raw_fixed[ptr], new_fixed[ptr])
raw_fixed = new_fixed
time.sleep(0.5)
def showfixedblock(station):
"""Display the raw fixed block contents."""
fb = station.get_raw_fixed_block(unbuffered=True)
for i, ptr in enumerate(range(len(fb))):
print '%02x' % fb[ptr],
if (i+1) % 16 == 0:
print
def showrecords(station, ts=0, count=0, fmt='raw'):
"""Display the indicated number of records or the records since the
specified timestamp (local time, in seconds)"""
records = station.get_records(since_ts=ts, num_rec=count)
for i,r in enumerate(records):
if fmt.lower() == 'raw':
raw_dump(r['datetime'], r['ptr'], r['raw_data'])
elif fmt.lower() == 'table':
table_dump(r['datetime'], r['data'], i==0)
else:
print r['datetime'], r['data']
def showreadings(station, logged_only):
"""Display live readings from the station."""
for data,ptr,_ in station.live_data(logged_only):
print '%04x' % ptr,
print data['idx'].strftime('%H:%M:%S'),
del data['idx']
print data
def clearhistory(station, prompt):
ans = None
while ans not in ['y', 'n']:
v = station.get_fixed_block(['data_count'], True)
print "Records in memory:", v
if prompt:
ans = raw_input("Clear console memory (y/n)? ")
else:
print 'Clearing console memory'
ans = 'y'
if ans == 'y' :
station.clear_history()
v = station.get_fixed_block(['data_count'], True)
print "Records in memory:", v
elif ans == 'n':
print "Clear memory cancelled."
def setpressure(station, pressure, prompt):
v = station.get_fixed_block(['rel_pressure'], True)
ans = None
while ans not in ['y', 'n']:
print "Relative pressure is", v
if prompt:
ans = raw_input("Set pressure to %f hPa (y/n)? " % pressure)
else:
print "Setting pressure to %f hPa" % pressure
ans = 'y'
if ans == 'y' :
station.set_pressure(pressure)
v = station.get_fixed_block(['rel_pressure'], True)
print "Relative pressure is now", v
elif ans == 'n':
print "Set pressure cancelled."
def setinterval(station, read_period, prompt):
v = station.get_fixed_block(['read_period'], True)
ans = None
while ans not in ['y', 'n']:
print "Interval is", v
if prompt:
ans = raw_input("Set interval to %d minutes (y/n)? " % read_period)
else:
print "Setting interval to %d minutes" % read_period
ans = 'y'
if ans == 'y' :
station.set_read_period(read_period)
v = station.get_fixed_block(['read_period'], True)
print "Interval is now", v
elif ans == 'n':
print "Set interval cancelled."
# fine offset documentation indicates that this should work, but so far it
# has not worked on any ambient weather WS2080 or WS1090 station i have tried.
# it looks like the station clock is set, but at some point the fixed block
# reverts to the previous clock value. also unclear is the behavior when the
# station attempts to sync with radio clock signal from sensor.
# -- mwall 14feb2013
def setclock(station, prompt):
ans = None
while ans not in ['y', 'n']:
v = station.get_fixed_block(['date_time'], True)
print "Station clock is", v
now = datetime.datetime.now()
if prompt:
ans = raw_input("Set station clock to %s (y/n)? " % now)
else:
print "Setting station clock to %s" % now
ans = 'y'
if ans == 'y' :
station.set_clock()
v = station.get_fixed_block(['date_time'], True)
print "Station clock is now", v
elif ans == 'n':
print "Set clock cancelled."
def stash(slist, s):
if s.find('settings') != -1:
slist['settings'].append(s)
elif s.find('display') != -1:
slist['display_settings'].append(s)
elif s.find('alarm') != -1:
slist['alarm_settings'].append(s)
elif s.find('min.') != -1 or s.find('max.') != -1:
slist['minmax_values'].append(s)
else:
slist['values'].append(s)
return slist
def fmtparam(label, value):
fmt = '%s'
if label in weewx.drivers.fousb.datum_display_formats.keys():
fmt = weewx.drivers.fousb.datum_display_formats[label]
fmt = '%s: ' + fmt
return fmt % (label.rjust(30), value)
def getvalues(station, name, value):
values = {}
if type(value) is tuple:
values[name] = station.get_fixed_block(name.split('.'))
elif type(value) is dict:
for x in value.keys():
n = x
if len(name) > 0:
n = name + '.' + x
values.update(getvalues(station, n, value[x]))
return values
def raw_dump(date, pos, data):
print date,
print "%04x" % pos,
for item in data:
print "%02x" % item,
print
def table_dump(date, data, showlabels=False):
if showlabels:
print '# date time',
for key in data.keys():
print key,
print
print date,
for key in data.keys():
print data[key],
print
if __name__=="__main__" :
main()