mirror of
https://github.com/weewx/weewx.git
synced 2026-04-19 17:16:56 -04:00
463 lines
18 KiB
Python
Executable File
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()
|