From 5766f05dc1b2f23582e4ecbde13dd789e17e5ceb Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Sat, 14 Jun 2014 13:57:39 +0000 Subject: [PATCH 01/33] power cycle forever until fine offset station comes back online --- bin/weewx/drivers/fousb.py | 50 ++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/bin/weewx/drivers/fousb.py b/bin/weewx/drivers/fousb.py index 08a4fdb2..7eac71c2 100644 --- a/bin/weewx/drivers/fousb.py +++ b/bin/weewx/drivers/fousb.py @@ -410,9 +410,10 @@ def pywws2weewx(p, ts, pressure_offset, altitude, USB_RT_PORT = (usb.TYPE_CLASS | usb.RECIP_OTHER) USB_PORT_FEAT_POWER = 8 -def power_cycle_station(self, hub, port): +def power_cycle_station(hub, port): '''Power cycle the port on the specified hub. This works only with USB hubs that support per-port power switching such as the linksys USB2HUB4.''' + loginf("Attempting to power cycle") busses = usb.busses() if not busses: raise weewx.WeeWxIOError("Power cycle failed: cannot find USB busses") @@ -432,14 +433,18 @@ def power_cycle_station(self, hub, port): request=usb.REQ_CLEAR_FEATURE, value=USB_PORT_FEAT_POWER, index=port, buffer=None, timeout=1000) - time.sleep(10) + loginf("Waiting 30 seconds for station to power down") + time.sleep(30) loginf("Power on port %d on hub %s" % (port, hub)) handle.controlMsg(requestType=USB_RT_PORT, request=usb.REQ_SET_FEATURE, value=USB_PORT_FEAT_POWER, index=port, buffer=None, timeout=1000) + loginf("Waiting 60 seconds for station to power up") + time.sleep(60) finally: del handle + loginf("Power cycle complete") # decode weather station raw data formats def _signed_byte(raw, offset): @@ -658,6 +663,7 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): # minimum interval between polling for data change self.min_pause = 0.5 + self.devh = None self._arcint = None self._last_rain_loop = None self._last_rain_ts_loop = None @@ -702,27 +708,39 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): def archive_interval(self): return self._archive_interval_minutes() * 60 + # if power cycling is enabled, loop forever until we get a response from + # the weather station. def _archive_interval_minutes(self): - if self._arcint is None: - for i in range(self.max_tries): + if self._arcint is not None: + return self._arcint + if self.pc_hub is not None: + while True: try: - self._arcint = self.get_fixed_block(['read_period']) + self.openPort() + self._get_arcint() break - except usb.USBError, e: - logcrt("get archive interval failed attempt %d of %d: %s" - % (i+1, self.max_tries, e)) - else: - msg = "Unable to read archive interval after %d tries" % self.max_tries - if self.pc_hub is not None: - logerr(msg) - logerr("Attempting to power cycle") + except weewx.WeeWxIOError, e: + self.closePort() power_cycle_station(self.pc_hub, self.pc_port) - raise weewx.WeeWxIOError("Power cycle complete") - else: - raise weewx.WeeWxIOError(msg) + else: + self._get_arcint() return self._arcint + def _get_arcint(self): + for i in range(self.max_tries): + try: + self._arcint = self.get_fixed_block(['read_period']) + return + except usb.USBError, e: + logcrt("get archive interval failed attempt %d of %d: %s" + % (i+1, self.max_tries, e)) + else: + raise weewx.WeeWxIOError("Unable to read archive interval after %d tries" % self.max_tries) + def openPort(self): + if self.devh is not None: + return + dev = self._find_device() if not dev: logcrt("Cannot find USB device with Vendor=0x%04x ProdID=0x%04x Device=%s" % (self.vendor_id, self.product_id, self.device_id)) From 3a25e61ef4860b4137bfda676bf50cb31a76649c Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Sun, 15 Jun 2014 03:03:36 +0000 Subject: [PATCH 02/33] revert to ultimeter 0.9.3 until 0.11.x is tested --- bin/weewx/drivers/ultimeter.py | 322 ++++++++++++--------------------- weewx.conf | 15 +- 2 files changed, 122 insertions(+), 215 deletions(-) diff --git a/bin/weewx/drivers/ultimeter.py b/bin/weewx/drivers/ultimeter.py index 3ec60072..dc701ba7 100644 --- a/bin/weewx/drivers/ultimeter.py +++ b/bin/weewx/drivers/ultimeter.py @@ -14,10 +14,44 @@ # # See http://www.gnu.org/licenses/ -"""Driver for Peet Bros Ultimeter weather stations (except the Ultimeter II). +"""Driver for Peet Bros Ultimeter weather stations (based on the +ADS WS1 driver) except the Ultimeter II (now quite old from early 1990s). -This driver assumes the Ultimeter is emitting data in Peet Bros Data Logger -mode format. +Thanks to Steve (sesykes71) for the testing that made this driver possible. + +Thanks to Jay Nugent (WB8TKL) and KRK6 for weather-2.kr6k-V2.1 + + http://server1.nuge.com/~weather/ + +To use this driver, put this file in bin/user, then put this in weewx.conf: + +[Station] + ... + station_type = PeetBros + +[PeetBros] + port = /dev/ttyS0 + driver = user.peetbros + +The driver assumes the Ultimeter is emitting data in Peet Bros Data Logger +mode format: + +!!000000BE02EB000027700000023A023A0025005800000000 + SSSSXXDDTTTTLLLLPPPPttttHHHHhhhhddddmmmmRRRRWWWW + +SSSS - wind speed (0.1 km/h) +XX - wind direction calibration +DD - wind direction (0-255) +TTTT - outdoor temperature (0.1 F) +LLLL - long term rain (0.01 in) +PPPP - pressure (0.1 mbar) +tttt - indoor temperature (0.1 F) +HHHH - outdoor humidity (0.1 %) +hhhh - indoor humidity (0.1 %) +dddd - date (day of year) +mmmm - time (minute of day) +RRRR - daily rain (0.01 in) +WWWW - one minute wind average (0.1 km/h) Resources for the Ultimeter stations @@ -27,9 +61,6 @@ Ultimeter Models 2100, 2000, 800, & 100 serial specifications: Ultimeter 2000 Pinouts and Parsers: http://www.webaugur.com/ham-radio/52-ultimeter-2000-pinouts-and-parsers.html -Ultimeter II: - not supported by this driver - All models communicate over an RS-232 compatible serial port using three wires--RXD, TXD, and Ground (except Ultimeter II which omits TXD). Port parameters are 2400, 8N1, with no flow control. @@ -40,17 +71,16 @@ and time of the Ultimeter upon initialization and then sets it into Data Logger mode for continuous updates. Modem Mode commands used by the driver - >Addddmmmm Set Date and Time (decimal digits dddd = day of year, mmmm = minute of day; Jan 1 = 0000, Midnight = 0000) >I Set output mode to Data Logger Mode (continuous output) + """ -# FIXME: eliminate the weewx.XXX classes from the station level - from __future__ import with_statement +import optparse import serial import syslog import time @@ -61,16 +91,20 @@ import weewx.units import weewx.uwxutils import weewx.wxformulas -INHG_PER_MBAR = 0.0295333727 -METER_PER_FOOT = 0.3048 -MILE_PER_KM = 0.621371 - -DRIVER_VERSION = '0.11.0' +DRIVER_VERSION = '0.9.3' DEFAULT_PORT = '/dev/ttyS0' -DEBUG_READ = 1 +DEBUG_READ = 0 + +def _is_hex(c): + """Test character for a valid hexadecimal digit.""" + try: + int(c, 16) + return True + except ValueError: + return False def logmsg(level, msg): - syslog.syslog(level, 'ultimeter: %s' % msg) + syslog.syslog(level, 'peetbros: %s' % msg) def logdbg(msg): logmsg(syslog.LOG_DEBUG, msg) @@ -81,10 +115,14 @@ def loginf(msg): def logerr(msg): logmsg(syslog.LOG_ERR, msg) +def logcrt(msg): + logmsg(syslog.LOG_CRIT, msg) + def loader(config_dict, engine): """Get the altitude, in feet, from the Station section of the dict.""" altitude_m = weewx.units.getAltitudeM(config_dict) - altitude_ft = altitude_m / METER_PER_FOOT + altitude_vt = (altitude_m, 'meter', 'group_altitude') + altitude_ft = weewx.units.convert(altitude_vt, 'foot')[0] station = Ultimeter(altitude=altitude_ft, **config_dict['Ultimeter']) return station @@ -94,9 +132,6 @@ class Ultimeter(weewx.abstractstation.AbstractStation): port - serial port [Required. Default is /dev/ttyS0] - model - station model, e.g., 'Ultimeter 2000' or 'Ultimeter 100' - [Optional. Default is Ultimeter] - polling_interval - how often to query the serial interface, seconds [Optional. Default is 1] @@ -113,65 +148,15 @@ class Ultimeter(weewx.abstractstation.AbstractStation): self.polling_interval = float(stn_dict.get('polling_interval', 1)) self.max_tries = int(stn_dict.get('max_tries', 5)) self.pressure_offset = float(stn_dict.get('pressure_offset', 0)) - self.model = stn_dict.get('model', 'Ultimeter') self.last_rain = None loginf('driver version is %s' % DRIVER_VERSION) loginf('using serial port %s' % self.port) - loginf('polling interval is %s' % self.polling_interval) - loginf('pressure offset is %s' % self.pressure_offset) + loginf('polling interval is %s' % str(self.polling_interval)) global DEBUG_READ DEBUG_READ = int(stn_dict.get('debug_read', DEBUG_READ)) - def getTime(self): - with Station(self.port) as station: - return station.get_time() - - def setTime(self, ts): - with Station(self.port) as station: - station.set_time(ts) - def genLoopPackets(self): - return self.glp1() - - def glp1(self): - # this version of genLoopPackets does the maxtries at station level - # and keeps the serial port open for an extended time. - with Station(self.port) as station: - station.set_logger_mode() - while True: - buf = station.get_readings_with_retry() - data = Station.parse_readings(buf) - packet = {'dateTime': int(time.time()+0.5), - 'usUnits' : weewx.US } - packet.update(data) - self._augment_packet(packet) - yield packet - if self.polling_interval: - time.sleep(self.polling_interval) - - def glp2(self): - # this version of genLoopPackets does the maxtries at station level - # and opens the serial port for each data read. - with Station(self.port) as station: - station.set_logger_mode() - while True: - with Station(self.port) as station: - buf = station.get_readings_with_retry() - data = Station.parse_readings(buf) - packet = {'dateTime': int(time.time()+0.5), - 'usUnits' : weewx.US } - packet.update(data) - self._augment_packet(packet) - yield packet - if self.polling_interval: - time.sleep(self.polling_interval) - - def glp3(self): - # this version of genLoopPackets does the maxtries at driver level - # and opens the serial port for each data read. ntries = 0 - with Station(self.port) as station: - station.set_logger_mode() while ntries < self.max_tries: ntries += 1 try: @@ -179,8 +164,8 @@ class Ultimeter(weewx.abstractstation.AbstractStation): 'usUnits' : weewx.US } # open a new connection to the station for each reading with Station(self.port) as station: - buf = station.get_readings() - data = Station.parse_readings(buf) + bytes = station.get_readings() + data = Station.parse_readings(bytes) packet.update(data) self._augment_packet(packet) ntries = 0 @@ -197,17 +182,14 @@ class Ultimeter(weewx.abstractstation.AbstractStation): @property def hardware_name(self): - return self.model + return Station.getName() def _augment_packet(self, packet): """add derived metrics to a packet""" - # the ultimeter appears to report a (calculated) sea-level pressure - # rather than a raw station (sensor) pressure. so we must - # back-calculate to find the station pressure. adjp = packet['barometer'] if self.pressure_offset is not None and adjp is not None: - adjp += self.pressure_offset * INHG_PER_MBAR # convert to inHg - # FIXME: second temperature should be 12-hour mean temperature + adjp += self.pressure_offset * 0.0295333727 # convert to inHg + # FIXME: this is supposed to use mean temperature packet['pressure'] = weewx.uwxutils.TWxUtilsUS.SeaLevelToStationPressure(adjp, self.altitude, packet['outTemp'], packet['outTemp'], packet['outHumidity']) packet['altimeter'] = weewx.wxformulas.altimeter_pressure_US( packet['pressure'], self.altitude, algorithm='aaNOAA') @@ -225,39 +207,12 @@ class Ultimeter(weewx.abstractstation.AbstractStation): packet['rain'] = None self.last_rain = packet['long_term_rain'] - # no wind direction when wind speed is zero - if not packet['windSpeed']: - packet['windDir'] = None - -def _is_valid_char(c): - '''See whether a character is a valid hexadecimal digit or hyphen.''' - if c == '-': - return True - try: - int(c, 16) - return True - except ValueError: - return False - -def _hex2int(s, multiplier=None): - '''Ultimeter puts hyphens in the string when a sensor is not installed. - When we get a hyphen or any other non-hex character, return None.''' - v = None - try: - v = int(s, 16) - if multiplier is not None: - v *= multiplier - except ValueError: - pass - return v - class Station(object): def __init__(self, port): self.port = port self.baudrate = 2400 self.timeout = 30 self.serial_port = None - self.max_tries = 5 def __enter__(self): self.open() @@ -266,15 +221,30 @@ class Station(object): def __exit__(self, type, value, traceback): self.close() + @staticmethod + def getName(self): + return "Ultimeter" + def open(self): logdbg("open serial port %s" % self.port) self.serial_port = serial.Serial(self.port, self.baudrate, timeout=self.timeout) -# self.set_logger_mode() + + # Set date and time as internal clock skews. + self.serial_port.write(">A%04d%04d\r" + % (time.localtime().tm_yday - 1, time.localtime().tm_min + + time.localtime().tm_hour * 60)) + + # Set to Data Logger Mode + self.serial_port.write(">I\r") def close(self): if self.serial_port is not None: logdbg("close serial port %s" % self.port) + + # Set to Modem Mode (stops Data Logger output) + self.serial_port.write(">\r") + self.serial_port.close() self.serial_port = None @@ -285,7 +255,7 @@ class Station(object): raise weewx.WeeWxIOError(e) n = len(buf) if n != nchar: - if DEBUG_READ and n: + if DEBUG_READ: logdbg("partial buffer: '%s'" % ' '.join(["%0.2X" % ord(c) for c in buf])) raise weewx.WeeWxIOError("Read expected %d chars, got %d" % @@ -298,86 +268,37 @@ class Station(object): raise weewx.WeeWxIOError("Write expected %d chars, sent %d" % (len(data), n)) - def flush(self): - logdbg("flush serial buffer") - self.serial_port.flushInput() - - def get_time(self): - self.set_logger_mode() - buf = self.get_readings_with_retry() - data = Station.parse_readings(buf) - d = data['day_of_year'] - m = data['minute_of_day'] - tstr = time.localtime() - y = tstr.tm_year - s = tstr.tm_sec - ts = time.mktime((y,0,0,0,0,s,0,0,0)) + d * 86400 + m * 60 - return ts - - def set_time(self, ts): - self.set_modem_mode() - tstr = time.localtime(ts) - tcmd = ">A%04d%04d\r" % ( - tstr.tm_yday - 1, tstr.tm_min + tstr.tm_hour * 60) - logdbg("set station time to %d (%s)" % (ts, tcmd)) - self.write(tcmd) - - # year works only for models 2004 and later - y = tstr.tm_year - ycmd = ">U%s" % y - logdbg("set station year to %s (%s)" % (y, ycmd)) - self.write(ycmd) - - def set_logger_mode(self): - # in logger mode, station sends logger mode records continuously - logdbg("set station to logger mode") - self.write(">I\r") - - def set_modem_mode(self): - # modem mode is available only on models 2004 and later - # not available on pre-2004 models 50/100/500/700/800 - logdbg("set station to modem mode") - self.write(">\r") - - def get_readings_with_retry(self): - ntries = 0 - while ntries < self.max_tries: - ntries += 1 - try: - return self.get_readings() - except weewx.WeeWxIOError, e: - logerr("Failed attempt %d of %d to get readings: %s" % - (ntries, self.max_tries, e)) - else: - msg = "Max retries (%d) exceeded for readings" % self.max_tries - logerr(msg) - raise weewx.RetriesExceeded(msg) - return [] - def get_readings(self): - buf = [] + bytes = [] while True: c = self.read(1) if c == "\r" or c == "\n": break - elif c == '!' and len(buf) > 0: + elif c == '!' and len(bytes) > 0: break elif c == '!': - buf = [] - elif _is_valid_char(c): - buf.append(c) + bytes = [] + elif c == '-': + # Ultimeter may put hyphens in the string if a sensor + # is not installed. Make the reading zero instead. + bytes.append('0') + elif _is_hex(c) is True: + # Ultimeter uses hexadecimal characters for its values. + # Guard against garbage. + bytes.append(c) else: - raise weewx.WeeWxIOError("Invalid character %0.2X" % ord(c)) + bytes = [] if DEBUG_READ: - logdbg("bytes: '%s'" % ' '.join(["%0.2X" % ord(c) for c in buf])) - if len(buf) != 48: - raise weewx.WeeWxIOError("Got %d bytes, expected 48" % len(buf)) - return ''.join(buf) + logdbg("bytes: '%s'" % ' '.join(["%0.2X" % ord(c) for c in bytes])) + if len(bytes) != 48: + raise weewx.WeeWxIOError("Got %d bytes, expected 48" % len(bytes)) + return ''.join(bytes) @staticmethod - def parse_readings(buf): - '''Parse the bytes in data logger format. Each line has 2 header - bytes, 48 data bytes, and a carriage return and newline: + def parse_readings(bytes): + '''Ultimeter stations emit data in PeetBros format. Each line has 52 + characters - 2 header bytes, 48 data bytes, and a carriage return + and line feed (new line): !!000000BE02EB000027700000023A023A0025005800000000\r\n SSSSXXDDTTTTLLLLPPPPttttHHHHhhhhddddmmmmRRRRWWWW @@ -396,24 +317,30 @@ class Station(object): RRRR - daily rain (0.01 in) WWWW - one minute wind average (0.1 kph) + For date, time, and other non-standard readings use labels that + will not interfere with weewx/wview conventions. + "pressure" reported by the Ultimeter 2000 is correlated to the local official barometer reading as part of the setup of the station console so this value is assigned to the 'barometer' key and the pressure and altimeter values are calculated from it. + + My Ultimeter 2000 puts hyphens, '-', in the place of the indoor + humidity (hhhh) since there is no indoor humidty sensor installed. + The driver will identify the hyphens and replace them with the '0' + character. ''' data = {} - data['windSpeed'] = _hex2int(buf[0:4], 0.1 * MILE_PER_KM) # mph - data['windDir'] = _hex2int(buf[6:8], 1.411764) # compass degrees - data['outTemp'] = _hex2int(buf[8:12], 0.1) # degree_F - data['long_term_rain'] = _hex2int(buf[12:16], 0.01) # inch - data['barometer'] = _hex2int(buf[16:20], 0.1 * INHG_PER_MBAR) # inHg - data['inTemp'] = _hex2int(buf[20:24], 0.1) # degree_F - data['outHumidity'] = _hex2int(buf[24:28], 0.1) # percent - data['inHumidity'] = _hex2int(buf[28:32], 0.1) # percent - data['day_of_year'] = _hex2int(buf[32:36]) - data['minute_of_day'] = _hex2int(buf[36:40]) - data['daily_rain'] = _hex2int(buf[40:44], 0.01) # inch - data['wind_average'] = _hex2int(buf[44:48], 0.1 * MILE_PER_KM) # mph + data['windSpeed'] = int(bytes[0:4], 16) * 0.1 * 0.621371 # mph + data['windDir'] = int(bytes[6:8], 16) * 1.411764 # compass degrees + data['outTemp'] = int(bytes[8:12], 16) * 0.1 # degree_F + data['long_term_rain'] = int(bytes[12:16], 16) * 0.01 # inch + data['barometer'] = int(bytes[16:20], 16) * 0.1 * 0.0295333727 # inHg + data['inTemp'] = int(bytes[20:24], 16) * 0.1 # degree_F + data['outHumidity'] = int(bytes[24:28], 16) * 0.1 # percent + data['inHumidity'] = int(bytes[28:32], 16) * 0.1 # percent + data['daily_rain'] = int(bytes[40:44], 16) * 0.01 # inch + data['wind_average'] = int(bytes[44:48], 16) * 0.1 * 0.621371 # mph return data # define a main entry point for basic testing of the station without weewx @@ -422,7 +349,6 @@ class Station(object): # PYTHONPATH=bin python bin/weewx/drivers/ultimeter.py if __name__ == '__main__': - import optparse usage = """%prog [options] [--help]""" @@ -435,26 +361,14 @@ if __name__ == '__main__': parser.add_option('--port', dest='port', metavar='PORT', help='serial port to which the station is connected', default=DEFAULT_PORT) - parser.add_option('--get-current', dest='getcur', action='store_true', - help='display current readings') - parser.add_option('--get-time', dest='gettime', action='store_true', - help='display station time') (options, args) = parser.parse_args() if options.version: - print "PeetBros Ultimeter driver version %s" % DRIVER_VERSION + print "ultimeter driver version %s" % DRIVER_VERSION exit(0) with Station(options.port) as s: - if options.getcur: - s.set_logger_mode() - buf = s.get_readings_with_retry() - print buf - data = Station.parse_readings(buf) - print data - if options.gettime: - ts = s.get_time() - print ts + print s.get_readings() if __name__ == '__main__': main() diff --git a/weewx.conf b/weewx.conf index 3e2b5a99..9bea668e 100644 --- a/weewx.conf +++ b/weewx.conf @@ -48,17 +48,10 @@ version = 2.6.3 #station_url = http://www.example.com # Set to type of station hardware. Supported stations include: - # Vantage - # WMR100 - # WMR200 - # WMR9x8 - # FineOffsetUSB - # WS28xx - # WS23xx - # TE923 - # Ultimeter - # WS1 - # Simulator + # Vantage FineOffsetUSB Ultimeter + # WMR100 WS28xx WS1 + # WMR200 WS23xx CC3000 + # WMR9x8 TE923 Simulator station_type = Vantage ############################################################################## From 30b04bdbb11ab49a967001f1b8e5308315f35d4a Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Sun, 15 Jun 2014 14:46:38 +0000 Subject: [PATCH 03/33] backfilling stats now emits debug messages. --- bin/weewx/stats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/weewx/stats.py b/bin/weewx/stats.py index e575e67d..0c1dd583 100644 --- a/bin/weewx/stats.py +++ b/bin/weewx/stats.py @@ -464,6 +464,8 @@ class StatsDb(object): _lastTime = _rec['dateTime'] nrecs += 1 if nrecs%1000 == 0: + syslog.syslog(syslog.LOG_DEBUG, "stats: Records processed: %d; Last date: %s", + (nrecs, weeutil.weeutil.timestamp_to_string(_lastTime))) print >>sys.stdout, "Records processed: %d; Last date: %s\r" % (nrecs, weeutil.weeutil.timestamp_to_string(_lastTime)), sys.stdout.flush() From 3938fd27113a480de02d4c3962e5956fcb770da0 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Sun, 15 Jun 2014 20:29:33 +0000 Subject: [PATCH 04/33] Fixed syntax error. --- bin/weewx/stats.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/weewx/stats.py b/bin/weewx/stats.py index 0c1dd583..a06da0e3 100644 --- a/bin/weewx/stats.py +++ b/bin/weewx/stats.py @@ -463,10 +463,11 @@ class StatsDb(object): # Remember the timestamp for this record. _lastTime = _rec['dateTime'] nrecs += 1 - if nrecs%1000 == 0: - syslog.syslog(syslog.LOG_DEBUG, "stats: Records processed: %d; Last date: %s", + if nrecs % 1000 == 0: + syslog.syslog(syslog.LOG_DEBUG, "stats: Records processed: %d; Last date: %s" % \ (nrecs, weeutil.weeutil.timestamp_to_string(_lastTime))) - print >>sys.stdout, "Records processed: %d; Last date: %s\r" % (nrecs, weeutil.weeutil.timestamp_to_string(_lastTime)), + print >>sys.stdout, "Records processed: %d; Last date: %s\r" % \ + (nrecs, weeutil.weeutil.timestamp_to_string(_lastTime)), sys.stdout.flush() # We're done. Record the stats for the last day. From 3b3ca585955ff2419b9b551499265cb16b51763f Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 16 Jun 2014 11:24:36 +0000 Subject: [PATCH 05/33] update package changelogs for 2.6.4 release --- pkg/changelog.rpm | 3 +++ pkg/debian/changelog | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/pkg/changelog.rpm b/pkg/changelog.rpm index 2b507cc6..2ab36e80 100644 --- a/pkg/changelog.rpm +++ b/pkg/changelog.rpm @@ -1,3 +1,6 @@ +* Mon Jun 16 2014 Matthew Wall (weewx) - 2.6.4-1 +- new upstream release +- added cc3000, ultimeter, ws1 drivers * Thu Apr 10 2014 Matthew Wall (weewx) - 2.6.3-1 - new upstream release * Sun Feb 16 2014 Matthew Wall (weewx) - 2.6.2-1 diff --git a/pkg/debian/changelog b/pkg/debian/changelog index 4f7860aa..efd87f35 100644 --- a/pkg/debian/changelog +++ b/pkg/debian/changelog @@ -1,3 +1,8 @@ +weewx (2.6.4-1) unstable; urgency=low + * new upstream release + * added cc3000, ultimeter, ws1 drivers + * added option to specify display units during installation +-- Matthew Wall (weewx) Mon, 16 Jun 2014 07:22:26 -0400 weewx (2.6.3-1) unstable; urgency=low * new upstream release -- Matthew Wall (weewx) Thu, 10 Apr 2014 20:25:10 -0400 From bb225c66fa5e514e68eeb08f107845b2c7238797 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 16 Jun 2014 12:55:02 +0000 Subject: [PATCH 06/33] details vary for logrotate, even between debian derivatives --- util/logrotate.d/weewx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/logrotate.d/weewx b/util/logrotate.d/weewx index a92d32c5..869df6e3 100644 --- a/util/logrotate.d/weewx +++ b/util/logrotate.d/weewx @@ -9,7 +9,8 @@ create 644 syslog adm sharedscripts postrotate - reload rsyslog > /dev/null 2>&1 + service rsyslog restart > /dev/null +# reload rsyslog > /dev/null 2>&1 # /etc/init.d/rsyslog stop # /etc/init.d/rsyslog start endscript From ccbe2295675724d0f3364a775196f8d2bc4ac873 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Mon, 16 Jun 2014 14:17:10 +0000 Subject: [PATCH 07/33] Version 2.6.4 --- bin/weewx/__init__.py | 2 +- docs/changes.txt | 8 +++++--- docs/customizing.htm | 2 +- docs/upgrading.htm | 2 +- docs/usersguide.htm | 4 ++-- weewx.conf | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bin/weewx/__init__.py b/bin/weewx/__init__.py index c0476028..c80b3f65 100644 --- a/bin/weewx/__init__.py +++ b/bin/weewx/__init__.py @@ -8,7 +8,7 @@ """Package weewx, containing modules specific to the weewx runtime engine.""" import time -__version__="2.6.3" +__version__="2.6.4" # Holds the program launch time in unix epoch seconds: # Useful for calculating 'uptime.' diff --git a/docs/changes.txt b/docs/changes.txt index 1cc04680..2a5d0cb6 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -1,6 +1,8 @@ weewx change history -------------------- +2.6.4 06/16/14 + The WMR100 driver now calculates SLP in software. This fixes a problem with the WMRS200 station, which does not allow the user to set altitude. @@ -27,9 +29,9 @@ Prompt for metric/US units for debian installations. For WS28xx stations, return 0 for battery ok and 1 for battery failure. -If the console has successfully been opened up but then on subsequent opens -suffers an I/O error, weewx will now attempt a retry (before it would just -exit). +If a connection to the console has been successfully opened, but then on +subsequent connection attempts suffers an I/O error, weewx will now attempt +a retry (before it would just exit). 2.6.3 04/10/14 diff --git a/docs/customizing.htm b/docs/customizing.htm index 2d389373..7dd45987 100644 --- a/docs/customizing.htm +++ b/docs/customizing.htm @@ -23,7 +23,7 @@ table#stattypes td {

Customizing weewx
- Version: 2.6.3 + Version: 2.6.4

Table of Contents

diff --git a/docs/upgrading.htm b/docs/upgrading.htm index bb0b1da5..90c5d19e 100644 --- a/docs/upgrading.htm +++ b/docs/upgrading.htm @@ -15,7 +15,7 @@

Upgrading weewx
-Version: 2.6.3 +Version: 2.6.4

diff --git a/docs/usersguide.htm b/docs/usersguide.htm index 46959b93..29a4ebe4 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -33,7 +33,7 @@ function showtab(tab,id) {

User's Guide to the weewx Weather System
-Version: 2.6.3 +Version: 2.6.4

@@ -2512,7 +2512,7 @@ Mutating actions will request confirmation before proceeding. option.

$BIN_ROOT/wee_config_cc3000 --info 

This will result in something like this:

-
firmware:  Rainwise CC-3000 Version: 1.3 Build 002 Jun 05 2013
+    
firmware:  Rainwise CC-3000 Version: 2.6.4
 time:  2014/06/02 08:22:17
 units:  ENGLISH
 memory:  251372 bytes, 4334 records, 12%
diff --git a/weewx.conf b/weewx.conf
index 9bea668e..a76a2c47 100644
--- a/weewx.conf
+++ b/weewx.conf
@@ -18,7 +18,7 @@ WEEWX_ROOT = /home/weewx
 socket_timeout = 20
 
 # Do not modify this - it is used by setup.py when installing and updating.
-version = 2.6.3
+version = 2.6.4
 
 ##############################################################################
 

From eb0c7e4699978ce0ba9793b3e1cf82e487a1f960 Mon Sep 17 00:00:00 2001
From: Matthew Wall 
Date: Mon, 16 Jun 2014 14:41:04 +0000
Subject: [PATCH 08/33] debian package for 2.6.4

---
 MANIFEST             | 5 +++--
 pkg/debian/changelog | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/MANIFEST b/MANIFEST
index b54b485d..f95ffb25 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -50,15 +50,16 @@ bin/weewx/uwxutils.py
 bin/weewx/wxengine.py
 bin/weewx/wxformulas.py
 bin/weewx/drivers/__init__.py
-bin/weewx/drivers/ads.py
+bin/weewx/drivers/cc3000.py
 bin/weewx/drivers/fousb.py
-bin/weewx/drivers/peetbros.py
 bin/weewx/drivers/simulator.py
 bin/weewx/drivers/te923.py
+bin/weewx/drivers/ultimeter.py
 bin/weewx/drivers/vantage.py
 bin/weewx/drivers/wmr100.py
 bin/weewx/drivers/wmr200.py
 bin/weewx/drivers/wmr9x8.py
+bin/weewx/drivers/ws1.py
 bin/weewx/drivers/ws23xx.py
 bin/weewx/drivers/ws28xx.py
 docs/changes.txt
diff --git a/pkg/debian/changelog b/pkg/debian/changelog
index efd87f35..2e1b02e7 100644
--- a/pkg/debian/changelog
+++ b/pkg/debian/changelog
@@ -2,7 +2,7 @@ weewx (2.6.4-1) unstable; urgency=low
   * new upstream release
   * added cc3000, ultimeter, ws1 drivers
   * added option to specify display units during installation
--- Matthew Wall (weewx)   Mon, 16 Jun 2014 07:22:26 -0400
+ -- Matthew Wall (weewx)   Mon, 16 Jun 2014 07:22:26 -0400
 weewx (2.6.3-1) unstable; urgency=low
   * new upstream release
  -- Matthew Wall (weewx)   Thu, 10 Apr 2014 20:25:10 -0400

From 0c1575dc684cce4609f61fd313d7c618b4cbfbf8 Mon Sep 17 00:00:00 2001
From: Matthew Wall 
Date: Mon, 16 Jun 2014 15:13:26 +0000
Subject: [PATCH 09/33] added structure to backup section of userguide

---
 docs/usersguide.htm | 45 +++++++++++++++++++++++++--------------------
 1 file changed, 25 insertions(+), 20 deletions(-)

diff --git a/docs/usersguide.htm b/docs/usersguide.htm
index 29a4ebe4..85de52bb 100644
--- a/docs/usersguide.htm
+++ b/docs/usersguide.htm
@@ -1979,7 +1979,7 @@ report_services = weewx.wxengine.StdPrint, weewx.wxengine.StdReport
       configuration utilities supplied with weewx. Directions follow for

  • Davis Vantage stations
  • -
  • Fine Offset stations
  • +
  • Fine Offset 10xx, 20xx, and 30xx stations
  • La Crosse WS23xx stations
  • La Crosse WS28xx stations
  • RainWise CC3000 data logger
  • @@ -2137,7 +2137,7 @@ Davis Vantage EEPROM settings: Because the command dumps all data, it may result in many duplicate primary key errors. These can be ignored.

    -

    Fine Offset

    +

    Fine Offset 10xx, 20xx, 30xx

    The configuration utility wee_config_fousb is designed to diagnose and configure Fine Offset stations.

    Run it with --help as an option to see its usage:

    @@ -2797,21 +2797,27 @@ sudo /etc/init.d/apache2 restart

    weewx.

    - Configuration. + It is not necessary to backup the images and HTML files generated from + templates, since weewx will easily create those again. This includes + the NOAA reports in some skins. +

    +

    Configuration

    +

    Save the weewx.conf file.

    - + - +
    setup.pysetup.py: /home/weewx/weewx.conf
    DEB/RPMDEB/RPM: /etc/weewx/weewx.conf
    +

    Weather data

    - Weather data. Meteorological ata are saved in the + Meteorological ata are saved in the archive database. For a Sqlite configuration, simply save the weewx.sdb file. For a MySQL configuration, save a dump of the archive database. @@ -2820,49 +2826,48 @@ sudo /etc/init.d/apache2 restart

    - + - +
    setup.pysetup.py: /home/weewx/archive/weewx.sdb
    DEB/RPMDEB/RPM: /var/lib/weewx/weewx.sdb
    +

    Skins and templates

    - Skins and templates. - Save the contents of the skins directory. + Save the contents of the skins directory if you have modified the default + skin or if you have added any new skins or template files.

    - + - +
    setup.pysetup.py: /home/weewx/skins
    DEB/RPMDEB/RPM: /etc/weewx/skins
    +

    Other customizations

    - Other customizations. Save the contents of the + Save the contents of the user directory if you have modified the database schema or added any extensions. If the extensions save data to a database you should backup those databases as well.

    - + - +
    setup.pysetup.py: /home/weewx/bin/user
    DEB/RPMDEB/RPM: /usr/share/weewx/user
    -

    - It is not necessary to backup the images and HTML files generated from - templates, since weewx will easily create those again. This includes - the NOAA reports in some skins. -

    + +

    Restoring from backup

    To restore from backup, do a fresh install of weewx then replace the default files with From 52a5c46510c00f2c0697fa32d6b412ce70f817c4 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 16 Jun 2014 15:15:58 +0000 Subject: [PATCH 10/33] fix ws23xx details in hardware table --- docs/usersguide.htm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/usersguide.htm b/docs/usersguide.htm index 85de52bb..ba7d7ed7 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -342,7 +342,7 @@ Version: 2.6.4 WS-23XX Serial - pyserial + fcntl/select WS23xx6 Experimental @@ -554,23 +554,23 @@ Version: 2.6.4 TechnoLine WS-2300 - USB - pyserial + Serial + fcntl/select WS23xx6 Experimental WS-2350 - USB - pyserial + Serial + fcntl/select WS23xx6 Experimental TFA Matrix - USB - pyserial + Serial + fcntl/select WS23xx6 Experimental From 7338352734890a4e2d017edb46a39ad92b6b0112 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Sun, 22 Jun 2014 00:15:46 +0000 Subject: [PATCH 11/33] fix location of weewx.conf in debian, redhat, and suse guides --- docs/debian.htm | 2 +- docs/redhat.htm | 2 +- docs/suse.htm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/debian.htm b/docs/debian.htm index 7c4798c8..7408005a 100644 --- a/docs/debian.htm +++ b/docs/debian.htm @@ -89,7 +89,7 @@ sudo apt-get -f install

    Customize

    - To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx.conf. See the User Guide and Customization Guide for details. + To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx/weewx.conf. See the User Guide and Customization Guide for details.

    weewx must be restarted for configuration diff --git a/docs/redhat.htm b/docs/redhat.htm index 167c04d1..00c35f3a 100644 --- a/docs/redhat.htm +++ b/docs/redhat.htm @@ -123,7 +123,7 @@

    Customize

    - To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx.conf. See the User Guide and Customization Guide for details. + To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx/weewx.conf. See the User Guide and Customization Guide for details.

    weewx must be restarted for configuration file diff --git a/docs/suse.htm b/docs/suse.htm index 493936f5..bd29da22 100644 --- a/docs/suse.htm +++ b/docs/suse.htm @@ -121,7 +121,7 @@

    Customize

    - To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx.conf. See the User Guide and Customization Guide for details. + To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx/weewx.conf. See the User Guide and Customization Guide for details.

    weewx must be restarted for configuration file From 107e8230958b93cf90ed43dbcd9161fdcb17b3a6 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Sun, 22 Jun 2014 00:18:39 +0000 Subject: [PATCH 12/33] fix location of weewx.conf in setup.py guide --- docs/setup.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/setup.htm b/docs/setup.htm index 78bd7a05..fd12eb40 100644 --- a/docs/setup.htm +++ b/docs/setup.htm @@ -281,7 +281,7 @@ function showstartup(id) {

    Customize

    - To enable uploads such as Weather Underground or to customize reports, modify the configuration file /etc/weewx.conf. See the User Guide and Customization Guide for details. + To enable uploads such as Weather Underground or to customize reports, modify the configuration file /home/weewx/weewx.conf. See the User Guide and Customization Guide for details.

    weewx must be restarted for configuration file From 5eaecc75be0e3945511be0b3e63216151bda07ba Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 23 Jun 2014 14:31:11 +0000 Subject: [PATCH 13/33] minor logwatch adjustments --- util/logwatch/scripts/services/weewx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/logwatch/scripts/services/weewx b/util/logwatch/scripts/services/weewx index c657fb62..4bb6e5cf 100755 --- a/util/logwatch/scripts/services/weewx +++ b/util/logwatch/scripts/services/weewx @@ -285,7 +285,7 @@ while(defined($_ = )) { /ws28xx: MainThread: frequency is/ || /ws28xx: MainThread: altitude is/ || /ws28xx: MainThread: pressure offset is/ || - /ws28xx: MainThread: found transceiver on USB/ || + /ws28xx: MainThread: found transceiver/ || /ws28xx: MainThread: manufacturer: LA CROSSE TECHNOLOGY/ || /ws28xx: MainThread: product: Weather Direct Light Wireless/ || /ws28xx: MainThread: interface/ || @@ -399,6 +399,7 @@ while(defined($_ = )) { /owfs: polling interval is / || /owfs: sensor map is / || /cmon: cpuinfo: / || + /cmon: sysinfo: / || /cmon: Skipping record/ || /forecast: .* starting thread/ || /forecast: .* terminating thread/ || From d086a91fdbc0f5f42bd77a7f019b7f262e067fe8 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 23 Jun 2014 21:38:08 +0000 Subject: [PATCH 14/33] initial catchup implementation for ws28xx driver --- bin/wee_config_ws28xx | 256 +++++++------ bin/weewx/drivers/ws28xx.py | 695 ++++++++++++++++++++++++------------ docs/changes.txt | 2 + 3 files changed, 603 insertions(+), 350 deletions(-) diff --git a/bin/wee_config_ws28xx b/bin/wee_config_ws28xx index 618e319f..5ad52a96 100755 --- a/bin/wee_config_ws28xx +++ b/bin/wee_config_ws28xx @@ -23,6 +23,7 @@ issues between weather station console and transceiver. import optparse import syslog import time +import sys import weewx.drivers.ws28xx import weewx.units @@ -49,16 +50,18 @@ def main(): 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("--format", dest="format", type=str, metavar="FORMAT", - help="format for history, one of raw, table, or dict") 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") @@ -75,26 +78,25 @@ def main(): station = weewx.drivers.ws28xx.WS28xx(altitude=altitude_m, **config_dict['WS28xx']) - 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.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, fmt=options.format) + history(station, maxtries, count=options.nrecords) elif options.recmin is not None: ts = int(time.time()) - options.recmin * 60 - history(station, maxtries, ts=ts, fmt=options.format) + history(station, maxtries, ts=ts) else: info(station, maxtries) @@ -106,139 +108,157 @@ def check_transceiver(station, maxtries): """See if the transceiver is installed and operational.""" print 'Checking for transceiver...' ntries = 0 - try: - while ntries < maxtries: - ntries += 1 - if station.transceiver_is_present(): - print 'Transceiver is present' - sn = station.transceiver_serial() - print 'serial: ' % sn - tid = station.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.' - except Exception, e: - pass + 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 - try: - 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()) + 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: - print 'Transceiver not paired to console.' - except Exception, e: - pass + 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 'Querying the station...' + v = get_interval(station, maxtries) + if v is None: + return + ans = None + while ans not in ['y', 'n']: + print "Interval is", v + if prompt: + ans = raw_input("Set interval to %d minutes (y/n)? " % interval) + else: + print "Setting interval to %d minutes" % interval + ans = 'y' + if ans == 'y' : + station.set_interval(interval) + v = get_interval(station, maxtries) + if v is None: + print "Cannot confirm change of interval" + return + print "Interval is now", v + elif ans == 'n': + print "Set interval cancelled." def info(station, maxtries): """Query the station then display the settings.""" print 'Querying the station for the configuration...' - start_ts = None - ntries = 0 - try: - while ntries < maxtries or maxtries == 0: - config = station.get_config() - if config is not None: - print_dict(config) - break - 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) - except Exception: - pass + 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 - try: - while ntries < maxtries or maxtries == 0: - packet = station.get_observation() - if packet is not None: - print_dict(packet) - break - 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) - except Exception: - pass + 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, fmt='raw'): +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..." - records = [] - start_ts = None ntries = 0 - try: - while ntries < maxtries or maxtries == 0: - records = station.get_history(since_ts=ts, num_rec=count) - if records is not None: - break - 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) - except Exception: - pass - - 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) + last_n = n = None + last_ts = now = int(time.time()) + station.start_caching_history(since_ts=ts) + t = weewx.drivers.ws28xx.WS28xx.max_records + while n is None or n > 0: + if ntries >= maxtries: + print 'Giving up after %d tries' % ntries + break + time.sleep(30) + now = int(time.time()) + n = station.get_num_history_scanned() + if n == last_n: + ntries += 1 + dur = now - last_ts + print 'No data after %d seconds (press SET to sync)' % dur else: - print r['datetime'], r['data'] - -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: - print key, - print - print date, - for key in data: - print data[key], + ntries = 0 + last_ts = now + last_n = n + ni = station.get_next_history_index() + li = station.get_latest_history_index() + msg = " scanned %s of %s: current=%s latest=%s\r" % (n, t, ni, li) + sys.stdout.write(msg) + sys.stdout.flush() + station.stop_caching_history() + records = station.get_history_cache_records() + station.clear_history_cache() print + print 'Found a total of %d records' % len(records) + for r in records: + print r def print_dict(data): - for key in sorted(data, key=data.get): - print '%s: %s' % (key, data[key]) + 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__" : diff --git a/bin/weewx/drivers/ws28xx.py b/bin/weewx/drivers/ws28xx.py index 524b5def..91a18058 100644 --- a/bin/weewx/drivers/ws28xx.py +++ b/bin/weewx/drivers/ws28xx.py @@ -844,6 +844,79 @@ rcfi 000: dc 0a 01 f9 01 02 0a 0c 0c 01 2e ff ff ff ff ff ff ff ff ff transceiver ID: 302 (0x012e) transceiver serial: 01021012120146 +Program Logic + +The RF communication thread uses the following logic to communicate with the +weather station console: + +Step 1. Perform in a while loop getState commands until state 0xde16 + is received. + +Step 2. Perform a getFrame command to read the message data. + +Step 3. Handle the contents of the message. The type of message depends on + the response type: + + Response type (hex): + 20: WS SetTime / SetConfig - Data written + confirmation the setTime/setConfig setFrame message has been received + by the console + 40: GetConfig + save the contents of the configuration for later use (i.e. a setConfig + message with one ore more parameters changed) + 60: Current Weather + handle the weather data of the current weather message + 80: Actual / Outstanding History + ignore the data of the actual history record when there is no data gap; + handle the data of a (one) requested history record (note: in step 4 we + can decide to request another history record). + a1: Request First-Time Config + prepare a setFrame first time message + a2: Request SetConfig + prepare a setFrame setConfig message + a3: Request SetTime + prepare a setFrame setTime message + +Step 4. When you didn't receive the message in step 3 you asked for (see + step 5 how to request a certain type of message), decide if you want + to ignore or handle the received message. Then go to step 5 to + request for a certain type of message unless the received message + has response type a1, a2 or a3, then prepare first the setFrame + message the wireless console asked for. + +Step 5. Decide what kind of message you want to receive next time. The + request is done via a setFrame message (see step 6). It is + not guaranteed that you will receive that kind of message the next + time but setting the proper timing parameters of firstSleep and + nextSleep increase the chance you will get the requested type of + message. + +Step 6. The action parameter in the setFrame message sets the type of the + next to receive message. + + Action (hex): + 00: rtGetHistory - Ask for History message + setSleep(0.380,0.200) + 01: rtSetTime - Ask for Send Time to weather station message + setSleep(0.085,0.005) + 02: rtSetConfig - Ask for Send Config to weather station message + setSleep(0.420,0.005) + 03: rtGetConfig - Ask for Config message + setSleep(0.400,0.400) + 05: rtGetCurrent - Ask for Current Weather message + setSleep(0.380,0.200) + c0: Send Time - Send Time to WS + setSleep(0.085,0.005) + 40: Send Config - Send Config to WS + setSleep(0.085,0.005) + + Note: after the Request First-Time Config message (response type = 0xa1) + perform a rtGetConfig with setSleep(0.085,0.005) + +Step 7. Perform a setTX command + +Step 8. Go to step 1 to wait for state 0xde16 again. + """ # TODO: how often is currdat.lst modified with/without hi-speed mode? @@ -866,8 +939,9 @@ import usb import weewx.abstractstation import weewx.units import weewx.wxformulas +import weeutil.weeutil -DRIVER_VERSION = '0.27' +DRIVER_VERSION = '0.28' # flags for enabling/disabling debug verbosity DEBUG_WRITES = 0 @@ -930,6 +1004,32 @@ def calc_checksum(buf, start, end=None): cs += buf[0][i+start] return cs +def get_prev_index(idx): + prevIdx = idx - 1 + if prevIdx < 0: + prevIdx += WS28xx.max_records + return prevIdx + +def get_next_index(idx): + nextIdx = idx + 1 + if nextIdx >= WS28xx.max_records: + nextIdx -= WS28xx.max_records + return nextIdx + +def tstr_to_ts(tstr): + if tstr is None: + return None + return time.mktime(time.strptime(tstr, "%Y-%m-%d %H:%M:%S")) + +def buf_to_addr(a,b,c): + return ((((a & 0xF) << 8) | b) << 8) | c + +def addr_to_index(addr): + return (addr - 416) / 18 + +def index_to_addr(idx): + return 18 * idx + 416 + def loader(config_dict, engine): altitude_m = weewx.units.getAltitudeM(config_dict) station = WS28xx(altitude=altitude_m, **config_dict['WS28xx']) @@ -937,7 +1037,9 @@ def loader(config_dict, engine): class WS28xx(weewx.abstractstation.AbstractStation): """Driver for LaCrosse WS28xx stations.""" - + + max_records = 1797 + def __init__(self, **stn_dict) : """Initialize the station object. @@ -965,12 +1067,6 @@ class WS28xx(weewx.abstractstation.AbstractStation): comm_interval: Communications mode interval [Optional. Default is 3] - vendor_id: The USB vendor ID for the transceiver. - [Optional. Default is 6666] - - product_id: The USB product ID for the transceiver. - [Optional. Default is 5555] - device_id: The USB device ID for the transceiver. If there are multiple devices with the same vendor and product IDs on the bus, each will have a unique device identifier. Use this identifier @@ -990,14 +1086,15 @@ class WS28xx(weewx.abstractstation.AbstractStation): self.polling_interval = int(stn_dict.get('polling_interval', 30)) self.comm_interval = int(stn_dict.get('comm_interval', 3)) self.frequency = stn_dict.get('transceiver_frequency', 'US') - self.vendor_id = int(stn_dict.get('vendor_id', '0x6666'), 0) - self.product_id = int(stn_dict.get('product_id', '0x5555'), 0) self.device_id = stn_dict.get('device_id', None) self.serial = stn_dict.get('serial', None) self.pressure_offset = stn_dict.get('pressure_offset', None) if self.pressure_offset is not None: self.pressure_offset = float(self.pressure_offset) + self.vendor_id = 0x6666 + self.product_id = 0x5555 + now = int(time.time()) self._service = None self._last_rain = None @@ -1078,7 +1175,66 @@ class WS28xx(weewx.abstractstation.AbstractStation): yield packet time.sleep(self.polling_interval) + # FIXME: return records as they come in instead of all at the end + # FIXME: compare station timestamp with computer timestamp to ensure + # that timestamps on historical records are correct + def genStartupRecords(self, ts): + loginf('Scanning historical records') + maxtries = 10 + ntries = 0 + last_n = n = None + last_ts = now = int(time.time()) + self.start_caching_history(since_ts=ts) + t = WS28xx.max_records + while n is None or n > 0: + if ntries >= maxtries: + logerr('No historical data after %d tries' % ntries) + return + time.sleep(60) + now = int(time.time()) + n = self.get_num_history_scanned() + if n == last_n: + ntries += 1 + dur = now - last_ts + loginf('No data after %d seconds (press SET to sync)' % dur) + else: + ntries = 0 + last_ts = now + last_n = n + ni = self.get_next_history_index() + li = self.get_latest_history_index() + loginf("Scanned %s of %s records: current=%s latest=%s" % + (n, t, ni, li)) + self.stop_caching_history() + records = self.get_history_cache_records() + self.clear_history_cache() + loginf('Found %d historical records' % len(records)) + last_ts = None + for r in records: + if last_ts is not None: + r['usUnits'] = weewx.METRIC + r['dateTime'] = tstr_to_ts(r['time']) + r['interval'] = (r['dateTime'] - last_ts) / 60 + last_ts = r['dateTime'] + # FIXME: add barometer, altimeter, windchill, heatindex + yield r + +# FIXME: do not implement hardware record generation until we figure +# out how to query the historical records faster. # def genArchiveRecords(self, since_ts): +# pass + +# FIXME: implement retries for this so that rf thread has time to get +# configuration data from the station +# @property +# def archive_interval(self): +# cfg = self.get_config() +# return getHistoryInterval(cfg['history_interval']) * 60 + +# FIXME: implement set/get time +# def setTime(self, ts): +# pass +# def getTime(self): # pass def startUp(self): @@ -1096,10 +1252,19 @@ class WS28xx(weewx.abstractstation.AbstractStation): self._service = None def transceiver_is_present(self): - return self._service.transceiverIsPresent() + return self._service.DataStore.getTransceiverPresent() def transceiver_is_paired(self): - return self._service.transceiverIsRegistered() + return self._service.DataStore.getDeviceRegistered() + + def get_transceiver_serial(self): + return self._service.DataStore.getTransceiverSerNo() + + def get_transceiver_id(self): + return self._service.DataStore.getDeviceID() + + def get_last_contact(self): + return self._service.getLastStat().last_seen_ts def get_observation(self): data = self._service.getWeatherData() @@ -1110,7 +1275,7 @@ class WS28xx(weewx.abstractstation.AbstractStation): # add elements required for weewx LOOP packets packet = {} packet['usUnits'] = weewx.METRIC - packet['dateTime'] = int(ts + 0.5) + packet['dateTime'] = ts # data from the station sensors packet['inTemp'] = get_datum_diff(data._TempIndoor, @@ -1193,22 +1358,38 @@ class WS28xx(weewx.abstractstation.AbstractStation): def get_config(self): logdbg('get station configuration') cfg = self._service.getConfigData().asDict() - if cfg['checksum_device'] == 0: + cs = cfg.get('checksum_out') + if cs is None or cs == 0: return None return cfg - def get_history(self): - logdbg('get historical records') - return self._service.getHistoryData().asDict() + def start_caching_history(self, since_ts=0, num_rec=0): + self._service.startCachingHistory(since_ts) - def get_last_contact(self): - return self._service.getLastStat().last_seen_ts + def stop_caching_history(self): + self._service.stopCachingHistory() - def get_transceiver_serial(self): - return self._service.DataStore.getTransceiverSerNo() + def get_uncached_history_count(self): + return self._service.getUncachedHistoryCount() - def get_transceiver_id(self): - return self._service.DataStore.getDeviceID() + def get_next_history_index(self): + return self._service.getNextHistoryIndex() + + def get_latest_history_index(self): + return self._service.getLatestHistoryIndex() + + def get_num_history_scanned(self): + return self._service.getNumHistoryScanned() + + def get_history_cache_records(self): + return self._service.getHistoryCacheRecords() + + def clear_history_cache(self): + self._service.clearHistoryCache() + + def set_interval(self, interval): + # FIXME: set the archive interval + pass # The following classes and methods are adapted from the implementation by # eddie de pieri, which is in turn based on the HeavyWeather implementation. @@ -1428,6 +1609,26 @@ def getBatteryStatus(status, flag): return 1 return 0 +history_intervals = { + EHistoryInterval.hi01Min: 1, + EHistoryInterval.hi05Min: 5, + EHistoryInterval.hi10Min: 10, + EHistoryInterval.hi20Min: 20, + EHistoryInterval.hi30Min: 30, + EHistoryInterval.hi60Min: 60, + EHistoryInterval.hi02Std: 120, + EHistoryInterval.hi04Std: 240, + EHistoryInterval.hi06Std: 360, + EHistoryInterval.hi08Std: 480, + EHistoryInterval.hi12Std: 720, + EHistoryInterval.hi24Std: 1440, + } + +def getHistoryInterval(i): + return history_intervals.get(i) + +# NP - not present +# OFL - outside factory limits class CWeatherTraits(object): windDirMap = { 0:"N", 1:"NNE", 2:"NE", 3:"ENE", 4:"E", 5:"ESE", 6:"SE", 7:"SSE", @@ -1507,21 +1708,21 @@ class USBHardware(object): @staticmethod def isOFL2(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) == 15 \ + result = (buf[0][start+0] >> 4) == 15 \ or (buf[0][start+0] & 0xF) == 15 else: - result = (buf[0][start+0] & 0xF) == 15 \ + result = (buf[0][start+0] & 0xF) == 15 \ or (buf[0][start+1] >> 4) == 15 return result @staticmethod def isOFL3(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) == 15 \ + result = (buf[0][start+0] >> 4) == 15 \ or (buf[0][start+0] & 0xF) == 15 \ or (buf[0][start+1] >> 4) == 15 else: - result = (buf[0][start+0] & 0xF) == 15 \ + result = (buf[0][start+0] & 0xF) == 15 \ or (buf[0][start+1] >> 4) == 15 \ or (buf[0][start+1] & 0xF) == 15 return result @@ -1529,13 +1730,13 @@ class USBHardware(object): @staticmethod def isOFL5(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) == 15 \ + result = (buf[0][start+0] >> 4) == 15 \ or (buf[0][start+0] & 0xF) == 15 \ or (buf[0][start+1] >> 4) == 15 \ or (buf[0][start+1] & 0xF) == 15 \ or (buf[0][start+2] >> 4) == 15 else: - result = (buf[0][start+0] & 0xF) == 15 \ + result = (buf[0][start+0] & 0xF) == 15 \ or (buf[0][start+1] >> 4) == 15 \ or (buf[0][start+1] & 0xF) == 15 \ or (buf[0][start+2] >> 4) == 15 \ @@ -1545,12 +1746,12 @@ class USBHardware(object): @staticmethod def isErr2(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) >= 10 \ + result = (buf[0][start+0] >> 4) >= 10 \ and (buf[0][start+0] >> 4) != 15 \ or (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 else: - result = (buf[0][start+0] & 0xF) >= 10 \ + result = (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 \ or (buf[0][start+1] >> 4) >= 10 \ and (buf[0][start+1] >> 4) != 15 @@ -1559,14 +1760,14 @@ class USBHardware(object): @staticmethod def isErr3(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) >= 10 \ + result = (buf[0][start+0] >> 4) >= 10 \ and (buf[0][start+0] >> 4) != 15 \ or (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 \ or (buf[0][start+1] >> 4) >= 10 \ and (buf[0][start+1] >> 4) != 15 else: - result = (buf[0][start+0] & 0xF) >= 10 \ + result = (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 \ or (buf[0][start+1] >> 4) >= 10 \ and (buf[0][start+1] >> 4) != 15 \ @@ -1577,7 +1778,7 @@ class USBHardware(object): @staticmethod def isErr5(buf, start, StartOnHiNibble): if StartOnHiNibble : - result = (buf[0][start+0] >> 4) >= 10 \ + result = (buf[0][start+0] >> 4) >= 10 \ and (buf[0][start+0] >> 4) != 15 \ or (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 \ @@ -1588,7 +1789,7 @@ class USBHardware(object): or (buf[0][start+2] >> 4) >= 10 \ and (buf[0][start+2] >> 4) != 15 else: - result = (buf[0][start+0] & 0xF) >= 10 \ + result = (buf[0][start+0] & 0xF) >= 10 \ and (buf[0][start+0] & 0xF) != 15 \ or (buf[0][start+1] >> 4) >= 10 \ and (buf[0][start+1] >> 4) != 15 \ @@ -1628,10 +1829,10 @@ class USBHardware(object): def toRain_7_3(buf, start, StartOnHiNibble): '''read 7 nibbles, presentation with 3 decimals; units of mm''' if ( USBHardware.isErr2(buf, start+0, StartOnHiNibble) or - USBHardware.isErr5(buf, start+1, StartOnHiNibble)): + USBHardware.isErr5(buf, start+1, StartOnHiNibble)): result = CWeatherTraits.RainNP() elif ( USBHardware.isOFL2(buf, start+0, StartOnHiNibble) or - USBHardware.isOFL5(buf, start+1, StartOnHiNibble) ): + USBHardware.isOFL5(buf, start+1, StartOnHiNibble) ): result = CWeatherTraits.RainOFL() elif StartOnHiNibble: result = (buf[0][start+0] >> 4)* 1000 \ @@ -1655,12 +1856,12 @@ class USBHardware(object): def toRain_6_2(buf, start, StartOnHiNibble): '''read 6 nibbles, presentation with 2 decimals; units of mm''' if ( USBHardware.isErr2(buf, start+0, StartOnHiNibble) or - USBHardware.isErr2(buf, start+1, StartOnHiNibble) or - USBHardware.isErr2(buf, start+2, StartOnHiNibble) ): + USBHardware.isErr2(buf, start+1, StartOnHiNibble) or + USBHardware.isErr2(buf, start+2, StartOnHiNibble) ): result = CWeatherTraits.RainNP() elif ( USBHardware.isOFL2(buf, start+0, StartOnHiNibble) or - USBHardware.isOFL2(buf, start+1, StartOnHiNibble) or - USBHardware.isOFL2(buf, start+2, StartOnHiNibble) ): + USBHardware.isOFL2(buf, start+1, StartOnHiNibble) or + USBHardware.isOFL2(buf, start+2, StartOnHiNibble) ): result = CWeatherTraits.RainOFL() elif StartOnHiNibble: result = (buf[0][start+0] >> 4)* 1000 \ @@ -1933,7 +2134,7 @@ class CCurrentWeatherData(object): return self._checksum def read(self, buf): - self._timestamp = time.time() + self._timestamp = int(time.time() + 0.5) self._checksum = CCurrentWeatherData.calcChecksum(buf) nbuf = [0] @@ -2076,10 +2277,7 @@ class CCurrentWeatherData(object): (self._PressureRelative_hPaMinMax._Min._Value, self._PressureRelative_inHgMinMax._Min._Value) = USBHardware.readPressureShared(nbuf, 205, 1) (self._PressureRelative_hPa, self._PressureRelative_inHg) = USBHardware.readPressureShared(nbuf, 210, 1) - if DEBUG_WEATHER_DATA > 0: - self.logWeatherData() - - def logWeatherData(self): + def toLog(self): logdbg("_WeatherState=%s _WeatherTendency=%s _AlarmRingingFlags %04x" % (CWeatherTraits.forecastMap[self._WeatherState], CWeatherTraits.trendMap[self._WeatherTendency], self._AlarmRingingFlags)) logdbg("_TempIndoor= %8.3f _Min=%8.3f (%s) _Max=%8.3f (%s)" % (self._TempIndoor, self._TempIndoorMinMax._Min._Value, self._TempIndoorMinMax._Min._Time, self._TempIndoorMinMax._Max._Value, self._TempIndoorMinMax._Max._Time)) logdbg("_HumidityIndoor= %8.3f _Min=%8.3f (%s) _Max=%8.3f (%s)" % (self._HumidityIndoor, self._HumidityIndoorMinMax._Min._Value, self._HumidityIndoorMinMax._Min._Time, self._HumidityIndoorMinMax._Max._Value, self._HumidityIndoorMinMax._Max._Time)) @@ -2335,46 +2533,41 @@ class CWeatherStationConfig(object): self._ResetMinMaxFlags = (nbuf[0][43]) <<16 | (nbuf[0][44] << 8) | (nbuf[0][45]) self._InBufCS = (nbuf[0][46] << 8) | nbuf[0][47] self._OutBufCS = calc_checksum(buf, 4, end=39) + 7 - if DEBUG_CONFIG_DATA > 0: - self.logConfigData() - self.write() - ###self._ResetMinMaxFlags = 0x000000 - ###logdbg('set _ResetMinMaxFlags to %06x' % self._ResetMinMaxFlags) """ - #Reset DewpointMax 80 00 00 - #Reset DewpointMin 40 00 00 - #not used 20 00 00 - #Reset WindchillMin* 10 00 00 *Reset dateTime only; Min._Value is preserved + Reset DewpointMax 80 00 00 + Reset DewpointMin 40 00 00 + not used 20 00 00 + Reset WindchillMin* 10 00 00 *dateTime only; Min._Value is preserved - #Reset TempOutMax 08 00 00 - #Reset TempOutMin 04 00 00 - #Reset TempInMax 02 00 00 - #Reset TempInMin 01 00 00 + Reset TempOutMax 08 00 00 + Reset TempOutMin 04 00 00 + Reset TempInMax 02 00 00 + Reset TempInMin 01 00 00 - #Reset Gust 00 80 00 - #not used 00 40 00 - #not used 00 20 00 - #not used 00 10 00 + Reset Gust 00 80 00 + not used 00 40 00 + not used 00 20 00 + not used 00 10 00 - #Reset HumOutMax 00 08 00 - #Reset HumOutMin 00 04 00 - #Reset HumInMax 00 02 00 - #Reset HumInMin 00 01 00 + Reset HumOutMax 00 08 00 + Reset HumOutMin 00 04 00 + Reset HumInMax 00 02 00 + Reset HumInMin 00 01 00 - #not used 00 00 80 - #Reset Rain Total 00 00 40 - #Reset last month? 00 00 20 - #Reset last week? 00 00 10 + not used 00 00 80 + Reset Rain Total 00 00 40 + Reset last month? 00 00 20 + Reset last week? 00 00 10 - #Reset Rain24H 00 00 08 - #Reset Rain1H 00 00 04 - #Reset PresRelMax 00 00 02 - #Reset PresRelMin 00 00 01 + Reset Rain24H 00 00 08 + Reset Rain1H 00 00 04 + Reset PresRelMax 00 00 02 + Reset PresRelMin 00 00 01 """ + #self._ResetMinMaxFlags = 0x000000 + #logdbg('set _ResetMinMaxFlags to %06x' % self._ResetMinMaxFlags) - if DEBUG_CONFIG_DATA > 0: - logdbg('Preset Config data') """ setTemps(self,TempFormat,InTempLo,InTempHi,OutTempLo,OutTempHi) setHums(self,InHumLo,InHumHi,OutHumLo,OutHumHi) @@ -2389,12 +2582,14 @@ class CWeatherStationConfig(object): #self.setGust(EWindspeedFormat.wfKmh,040.0) #self.setRain24H(ERainFormat.rfMm,50.0) - # Preset historyInterval to 5 minutes (default: 2 hours) + # Set historyInterval to 5 minutes (default: 2 hours) self._HistoryInterval = EHistoryInterval.hi05Min # Clear all alarm flags, otherwise the datastream from the weather # station will pause during an alarm and connection will be lost. self._WindDirAlarmFlags = 0x0000 self._OtherAlarmFlags = 0x0000 + + self.write() return 1 def testConfigChanged(self,buf): @@ -2433,7 +2628,7 @@ class CWeatherStationConfig(object): nbuf[0][42] = (self._OutBufCS >> 8) & 0xFF nbuf[0][43] = (self._OutBufCS >> 0) & 0xFF buf[0] = nbuf[0] - if self._OutBufCS == self._InBufCS and self._ResetMinMaxFlags == 0: + if self._OutBufCS == self._InBufCS and self._ResetMinMaxFlags == 0: if DEBUG_CONFIG_DATA > 0: logdbg('testConfigChanged: checksum not changed: OutBufCS=%04x' % self._OutBufCS) changed = 0 @@ -2441,12 +2636,12 @@ class CWeatherStationConfig(object): if DEBUG_CONFIG_DATA > 0: logdbg('testConfigChanged: checksum or resetMinMaxFlags changed: OutBufCS=%04x InBufCS=%04x _ResetMinMaxFlags=%06x' % (self._OutBufCS, self._InBufCS, self._ResetMinMaxFlags)) if DEBUG_CONFIG_DATA > 1: - self.logConfigData() + self.toLog() self.write() changed = 1 return changed - def logConfigData(self): + def toLog(self): logdbg('OutBufCS= %04x' % self._OutBufCS) logdbg('InBufCS= %04x' % self._InBufCS) logdbg('ClockMode= %s' % self._ClockMode) @@ -2554,7 +2749,7 @@ class CWeatherStationConfig(object): } -class CHistoryDataSet(object): +class CHistoryData(object): def __init__(self): self.Time = None @@ -2585,11 +2780,9 @@ class CHistoryDataSet(object): self.PressureRelative = USBHardware.toPressure_hPa_5_1(nbuf, 19, 0) self.TempIndoor = USBHardware.toTemperature_3_1(nbuf, 23, 0) self.TempOutdoor = USBHardware.toTemperature_3_1(nbuf, 22, 1) - self.Time = USBHardware.toDateTime(nbuf, 25, 1, 'HistoryDataSet') - if DEBUG_HISTORY_DATA > 0: - self.logHistoryData() + self.Time = USBHardware.toDateTime(nbuf, 25, 1, 'HistoryData') - def logHistoryData(self): + def toLog(self): logdbg("Time %s" % self.Time) logdbg("TempIndoor= %7.1f" % self.TempIndoor) logdbg("HumidityIndoor= %7.0f" % self.HumidityIndoor) @@ -2603,7 +2796,7 @@ class CHistoryDataSet(object): def asDict(self): return { - 'time': self.Time, + 'time': str(self.Time), 'inTemp': self.TempIndoor, 'inHumidity': self.HumidityIndoor, 'outTemp': self.TempOutdoor, @@ -2615,6 +2808,16 @@ class CHistoryDataSet(object): 'windGust': self.Gust } +class HistoryCache: + def __init__(self): + self.clear_records() + def clear_records(self): + self.since_ts = 0 + self.start_index = None + self.next_index = None + self.records = [] + self.num_outstanding_records = None + self.num_scanned = 0 class CDataStore(object): @@ -2630,25 +2833,12 @@ class CDataStore(object): self.SerialNumber = None self.DeviceID = None - class TCommunicationSettings(object): - def __init__(self): - self.CommModeInterval = 3 - self.PreambleDuration = 5000 - self.RegisterWaitTime = 20000 - self.DeviceID = None - - class TRequest(object): - def __init__(self): - self.Type = ERequestType.rtINVALID - self.State = ERequestState.rsError - self.TTL = 90000 - class TLastStat(object): def __init__(self, cache_file): self.LastBatteryStatus = None self.LastLinkQuality = None - self.OutstandingHistorySets = -1 - self.LastHistoryIndex = 0xffff + self.LastHistoryIndex = None + self.LatestHistoryIndex = None self.last_seen_ts = None self.last_weather_ts = 0 self.last_history_ts = 0 @@ -2657,13 +2847,11 @@ class CDataStore(object): def __init__(self, cache_file): self.cache_file = cache_file self.transceiverPresent = False - - self.Request = CDataStore.TRequest() + self.commModeInterval = 3 + self.registeredDeviceID = None self.LastStat = CDataStore.TLastStat(self.cache_file) - self.CommunicationSettings = CDataStore.TCommunicationSettings() self.TransceiverSettings = CDataStore.TTransceiverSettings() self.StationConfig = CWeatherStationConfig(self.cache_file) - self.HistoryData = CHistoryDataSet() self.CurrentWeather = CCurrentWeatherData() def writeLastStat(self): @@ -2676,6 +2864,7 @@ class CDataStore(object): config['LastStat']['LinkQuality'] = str(self.LastStat.LastLinkQuality) config['LastStat']['BatteryStatus'] = str(self.LastStat.LastBatteryStatus) config['LastStat']['HistoryIndex'] = str(self.LastStat.LastHistoryIndex) + config['LastStat']['LatestHistoryIndex'] = str(self.LastStat.LatestHistoryIndex) config['LastStat']['CurrentWeatherTime'] = self.LastStat.last_weather_ts config['LastStat']['HistoryDataTime'] = self.LastStat.last_history_ts config['LastStat']['ConfigTime'] = self.LastStat.last_config_ts @@ -2714,12 +2903,12 @@ class CDataStore(object): self.writeTransceiverSettings() def getRegisteredDeviceID(self): - return self.CommunicationSettings.DeviceID + return self.registeredDeviceID def setRegisteredDeviceID(self, val): - if val != self.CommunicationSettings.DeviceID: + if val != self.registeredDeviceID: loginf("console is paired to device with ID %04x" % val) - self.CommunicationSettings.DeviceID = val + self.registeredDeviceID = val def getTransceiverPresent(self): return self.transceiverPresent @@ -2750,43 +2939,36 @@ class CDataStore(object): self.writeLastStat() def setLastHistoryIndex(self,val): - logdbg("setLastHistoryIndex to %i (0x%x)" % (val, val)) + logdbg("setLastHistoryIndex to %s" % val) self.LastStat.LastHistoryIndex = val self.writeLastStat() def getLastHistoryIndex(self): return self.LastStat.LastHistoryIndex + def setLatestHistoryIndex(self,val): + self.LastStat.LatestHistoryIndex = val + self.writeLastStat() + + def getLatestHistoryIndex(self): + return self.LastStat.LatestHistoryIndex + def setCurrentWeather(self, data): - if DEBUG_WEATHER_DATA > 0: - logdbg('setCurrentWeather') self.CurrentWeather = data - def setHistoryData(self, data): - logdbg('setHistoryData') - self.HistoryData = data - def getDeviceRegistered(self): - if ( self.CommunicationSettings.DeviceID is None + if ( self.registeredDeviceID is None or self.TransceiverSettings.DeviceID is None - or self.CommunicationSettings.DeviceID != self.TransceiverSettings.DeviceID ): + or self.registeredDeviceID != self.TransceiverSettings.DeviceID ): return False return True - def getRequestType(self): - return self.Request.Type - - def setRequestType(self, val): - if DEBUG_COMM > 0: - logdbg('setRequestType to %s' % val) - self.Request.Type = val - def getCommModeInterval(self): - return self.CommunicationSettings.CommModeInterval + return self.commModeInterval def setCommModeInterval(self,val): logdbg("setCommModeInterval to %x" % val) - self.CommunicationSettings.CommModeInterval = val + self.commModeInterval = val def setTransceiverSerNo(self,val): logdbg("setTransceiverSerialNumber to %s" % val) @@ -2857,7 +3039,7 @@ class sHID(object): try: self.devh.detachKernelDriver(interface) except Exception, e: - loginf('Detach kernel driver failed: %s' % e) + pass # attempt to claim the interface try: @@ -3033,7 +3215,7 @@ class sHID(object): index=0x0000000, timeout=self.timeout) - def execute(self,command): + def execute(self, command): buf = [0]*0x0f #*0x15 buf[0] = 0xd9 buf[1] = command @@ -3203,8 +3385,6 @@ class CCommunicationService(object): self.shid = sHID() self.DataStore = CDataStore(cache_file) - self.TimeDifSec = 0 - self.DifHis = 0 self.firstSleep = 1 self.nextSleep = 1 @@ -3214,6 +3394,9 @@ class CCommunicationService(object): self.child = None self.thread_wait = 60.0 # seconds + self.command = None + self.history_cache = HistoryCache() + def buildFirstConfigFrame(self, Buffer, cs): logdbg('buildFirstConfigFrame: cs=%04x' % cs) newBuffer = [0] @@ -3281,39 +3464,48 @@ class CCommunicationService(object): Length = 0x0c return Length - def buildACKFrame(self,Buffer, action, cs, historyIndex): - logdbg("buildACKFrame: action=%x cs=%04x historyIndex=%i" % - (action, cs, historyIndex)) + # the first time through, thisIndex will be latestIndex+2 + def buildACKFrame(self, Buffer, action, cs, hidx=None): + logdbg("buildACKFrame: action=%x cs=%04x historyIndex=%s" % + (action, cs, hidx)) newBuffer = [0] newBuffer[0] = [0]*9 for i in xrange(0,2): newBuffer[0][i] = Buffer[0][i] - # when last weather is stale, change action to get current weather - now = int(time.time()) - age = now - self.DataStore.LastStat.last_weather_ts - if action != EAction.aGetCurrent and age >= 30 and newBuffer[0][1] != 0xF0: - logdbg('morphing action from %d to 5 (age=%s)' % (action, age)) - action = EAction.aGetCurrent - # FIXME: for now, never ask for historical records - if action == EAction.aGetHistory: - logdbg('morphing action from %d to 5' % action) - action = EAction.aGetCurrent + # when last weather is stale, change action to get current weather, + # but only if not getting history + if self.command != EAction.aGetHistory: + now = int(time.time()) + age = now - self.DataStore.LastStat.last_weather_ts + if action != EAction.aGetCurrent and age >= 30 and newBuffer[0][1] != 0xF0: + logdbg('buildACKFrame: morphing action from %d to 5 (age=%s)' % (action, age)) + action = EAction.aGetCurrent comInt = self.DataStore.getCommModeInterval() - if historyIndex >= 1797: - historyAddress = 0xffffff + if hidx is None: + if self.command == EAction.aGetHistory: + hidx = self.history_cache.next_index + elif self.DataStore.getLastHistoryIndex() is not None: + hidx = get_next_index(self.DataStore.getLastHistoryIndex()) + if hidx is None or hidx < 0 or hidx >= WS28xx.max_records: + haddr = 0xffffff else: - historyAddress = 18 * historyIndex + 416 + # hidx is the previous history index, not the next desired one, + # so use the last to get the next + hidx = get_prev_index(hidx) + haddr = index_to_addr(hidx) + + logdbg('buildACKFrame: idx: %s addr: 0x%04x' % (hidx, haddr)) newBuffer[0][2] = action & 0xF newBuffer[0][3] = (cs >> 8) & 0xFF newBuffer[0][4] = (cs >> 0) & 0xFF newBuffer[0][5] = (comInt >> 4) & 0xFF - newBuffer[0][6] = (historyAddress >> 16) & 0x0F | 16 * (comInt & 0xF) - newBuffer[0][7] = (historyAddress >> 8 ) & 0xFF - newBuffer[0][8] = (historyAddress >> 0 ) & 0xFF + newBuffer[0][6] = (haddr >> 16) & 0x0F | 16 * (comInt & 0xF) + newBuffer[0][7] = (haddr >> 8 ) & 0xFF + newBuffer[0][8] = (haddr >> 0 ) & 0xFF #d5 00 09 f0 f0 03 00 32 00 3f ff ff Buffer[0]=newBuffer[0] @@ -3333,15 +3525,15 @@ class CCommunicationService(object): newLength = [0] now = int(time.time()) self.DataStore.StationConfig.read(newBuffer) + if DEBUG_CONFIG_DATA > 0: + self.DataStore.StationConfig.toLog() self.DataStore.setLastStatCache(seen_ts=now, quality=(Buffer[0][3] & 0x7f), battery=(Buffer[0][2] & 0xf), config_ts=now) - idx = self.DataStore.getLastHistoryIndex() cs = newBuffer[0][47] | (newBuffer[0][46] << 8) - self.DataStore.setRequestType(ERequestType.rtGetCurrent) self.setSleep(0.380,0.200) - newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs, idx) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs) Buffer[0] = newBuffer[0] Length[0] = newLength[0] @@ -3356,10 +3548,13 @@ class CCommunicationService(object): chksum = CCurrentWeatherData.calcChecksum(Buffer) age = now - self.DataStore.LastStat.last_weather_ts if age >= 10 or chksum != self.DataStore.CurrentWeather.checksum(): + if DEBUG_WEATHER_DATA > 1: + self.shid.dump('CurWea', Buffer[0], fmt='long') data = CCurrentWeatherData() data.read(Buffer) -# self.shid.dump('CurWea', Buffer[0], fmt='long') self.DataStore.setCurrentWeather(data) + if DEBUG_WEATHER_DATA > 0: + data.toLog() # update the connection cache self.DataStore.setLastStatCache(seen_ts=now, @@ -3375,83 +3570,107 @@ class CCommunicationService(object): cfgBuffer = [0] cfgBuffer[0] = [0]*44 - idx = self.DataStore.getLastHistoryIndex() changed = self.DataStore.StationConfig.testConfigChanged(cfgBuffer) inBufCS = self.DataStore.StationConfig.getInBufCS() if inBufCS == 0 or inBufCS != cs: logdbg('handleCurrentData: inBufCS of station does not match') - self.DataStore.setRequestType(ERequestType.rtGetConfig) self.setSleep(0.400,0.400) - newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetConfig, cs, idx) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetConfig, cs) elif changed: logdbg('handleCurrentData: outBufCS of station changed') - self.DataStore.setRequestType(ERequestType.rtSetConfig) self.setSleep(0.420,0.005) - newLength[0] = self.buildACKFrame(newBuffer, EAction.aReqSetConfig, cs, idx) - else: - self.DataStore.setRequestType(ERequestType.rtGetCurrent) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aReqSetConfig, cs) + elif self.command == EAction.aGetHistory: self.setSleep(0.380,0.200) - newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs, idx) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetHistory, cs) + else: + self.setSleep(0.380,0.200) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs) Length[0] = newLength[0] Buffer[0] = newBuffer[0] - def handleHistoryData(self,Buffer,Length): + def handleHistoryData(self, buf, buflen): logdbg('handleHistoryData: %s' % self.timing()) + now = int(time.time()) - newBuffer = [0] - newBuffer[0] = Buffer[0] - newLength = [0] - data = CHistoryDataSet() - data.read(newBuffer) - cs = newBuffer[0][5] | (newBuffer[0][4] << 8) - latestAddr = ((((Buffer[0][6] & 0xF) << 8) | Buffer[0][7]) << 8) | Buffer[0][8] - thisAddr = ((((Buffer[0][9] & 0xF) << 8) | Buffer[0][10]) << 8) | Buffer[0][11] - thisIndex = (thisAddr - 415) / 18 - latestIndex = (latestAddr - 415) / 18 - - if ( latestIndex >= thisIndex ): - self.DifHis = latestIndex - thisIndex - else: - self.DifHis = latestIndex + 1797 - thisIndex - - if self.DifHis > 0: - logdbg('handleHistoryData: Time=%s OutstandingHistorySets=%4i' % - (data.Time, self.DifHis)) - - if self.DifHis > 0: - # FIXME: for now skip the history records - thisIndex = latestIndex - self.DifHis = 0 - self.setSleep(0.300,0.020) - self.DataStore.setLastHistoryIndex(thisIndex) - else: - self.setSleep(0.380,0.200) - if thisIndex != self.DataStore.getLastHistoryIndex(): - self.DataStore.setHistoryData(data) - self.DataStore.setLastHistoryIndex(thisIndex) - self.DataStore.setLastStatCache(seen_ts=now, - quality=(Buffer[0][3] & 0x7f), - battery=(Buffer[0][2] & 0xf), + quality=(buf[0][3] & 0x7f), + battery=(buf[0][2] & 0xf), history_ts=now) - if thisIndex == latestIndex: - self.TimeDifSec = (data.Time - datetime.fromtimestamp(now)).seconds - if self.TimeDifSec > 43200: - self.TimeDifSec = self.TimeDifSec - 86400 + 1 - logdbg('handleHistoryData: timeDifSec=%4s Time=%s' % - (self.TimeDifSec, data.Time)) + newbuf = [0] + newbuf[0] = buf[0] + newlen = [0] + data = CHistoryData() + data.read(newbuf) + if DEBUG_HISTORY_DATA > 0: + data.toLog() + + cs = newbuf[0][5] | (newbuf[0][4] << 8) +# according to doc it should be 9-11 and 12-14, but 6-8 and 9-11 works better + latestAddr = buf_to_addr(buf[0][6], buf[0][7], buf[0][8]) + thisAddr = buf_to_addr(buf[0][9], buf[0][10], buf[0][11]) +# latestAddr = buf_to_addr(buf[0][9], buf[0][10], buf[0][11]) +# thisAddr = buf_to_addr(buf[0][12], buf[0][13], buf[0][14]) + latestIndex = addr_to_index(latestAddr) + thisIndex = addr_to_index(thisAddr) + + nrec = latestIndex - thisIndex + if latestIndex < thisIndex: + nrec += WS28xx.max_records + logdbg('handleHistoryData: time=%s' + ' this=%d (0x%04x) latest=%d (0x%04x) nrec=%d' % + (data.Time, thisIndex, thisAddr, latestIndex, latestAddr, nrec)) + + # track the latest history index + self.DataStore.setLastHistoryIndex(thisIndex) + self.DataStore.setLatestHistoryIndex(latestIndex) + + # the first time through, the next index will be latest+2 + nextIndex = get_next_index(thisIndex) + if self.command == EAction.aGetHistory: + if self.history_cache.start_index is None: + # if there is no start index, start from the record after the + # latest one. + # FIXME: figure out exactly which record we should start from. + # this is non-trivial since the station retains records after + # it has been power cycled. + idx = get_next_index(latestIndex+1) + self.history_cache.start_index = idx + self.history_cache.next_index = idx + logdbg('handleHistoryData: set start_index=%d' % idx) + nextIndex = idx + elif self.history_cache.next_index is not None: + if self.history_cache.next_index == thisIndex: + self.history_cache.num_scanned += 1 + # get the next history record + ts = tstr_to_ts(str(data.Time)) + if ts is not None and self.history_cache.since_ts < ts: + # append to the history if timestamp in desired range + logdbg('handleHistoryData: appending history record' + ' %s: %s' % (thisIndex, data.asDict())) + self.history_cache.records.append(data.asDict()) + self.history_cache.num_outstanding_records = nrec + else: + logdbg('handleHistoryData: skip record: since_ts=%s this_ts=%s' % (weeutil.weeutil.timestamp_to_string(self.history_cache.since_ts), weeutil.weeutil.timestamp_to_string(ts))) + self.history_cache.next_index = get_next_index(thisIndex) + else: + logdbg('handleHistoryData: index mismatch: %s != %s' % + (self.history_cache.next_index, thisIndex)) + nextIndex = self.history_cache.next_index + + logdbg('handleHistoryData: next=%s' % nextIndex) + + # if caching, request next history, otherwise request current weather + self.setSleep(0.380,0.200) + if self.command == EAction.aGetHistory: + newlen[0] = self.buildACKFrame(newbuf, EAction.aGetHistory, cs, nextIndex) else: - logdbg('handleHistoryData: no recent history data: Time=%s' % - data.Time) + newlen[0] = self.buildACKFrame(newbuf, EAction.aGetCurrent, cs) - self.DataStore.setRequestType(ERequestType.rtGetCurrent) - idx = thisIndex - newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs, idx) - - Length[0] = newLength[0] - Buffer[0] = newBuffer[0] + buflen[0] = newlen[0] + buf[0] = newbuf[0] def handleNextAction(self,Buffer,Length): logdbg('handleNextAction') @@ -3464,22 +3683,20 @@ class CCommunicationService(object): cs = newBuffer[0][5] | (newBuffer[0][4] << 8) if (Buffer[0][2] & 0xEF) == EResponseType.rtReqFirstConfig: logdbg('handleNextAction: a1 (first-time config)') - newLength[0] = self.buildFirstConfigFrame(newBuffer, cs) self.setSleep(0.085,0.005) + newLength[0] = self.buildFirstConfigFrame(newBuffer, cs) elif (Buffer[0][2] & 0xEF) == EResponseType.rtReqSetConfig: logdbg('handleNextAction: a2 (set config data)') - newLength[0] = self.buildConfigFrame(newBuffer) self.setSleep(0.085,0.005) + newLength[0] = self.buildConfigFrame(newBuffer) elif (Buffer[0][2] & 0xEF) == EResponseType.rtReqSetTime: logdbg('handleNextAction: a3 (set time data)') - newLength[0] = self.buildTimeFrame(newBuffer, cs) self.setSleep(0.085,0.005) + newLength[0] = self.buildTimeFrame(newBuffer, cs) else: logdbg('handleNextAction: %02x' % (Buffer[0][2] & 0xEF)) - idx = self.DataStore.getLastHistoryIndex() - self.DataStore.setRequestType(ERequestType.rtGetCurrent) self.setSleep(0.380,0.200) - newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs, idx) + newLength[0] = self.buildACKFrame(newBuffer, EAction.aGetCurrent, cs) Length[0] = newLength[0] Buffer[0] = newBuffer[0] @@ -3491,15 +3708,14 @@ class CCommunicationService(object): newBuffer[0] = Buffer[0] newLength = [0] newLength[0] = Length[0] - reqType = self.DataStore.getRequestType() if Length[0] == 0: - raise BadResponse('zero length for requestType=%x' % reqType) + raise BadResponse('zero length buffer') bufferID = (Buffer[0][0] <<8) | Buffer[0][1] respType = (Buffer[0][2] & 0xE0) if DEBUG_COMM > 0: - logdbg("generateResponse: id=%04x resp=%x req=%x length=%x" % - (bufferID, respType, reqType, Length[0])) + logdbg("generateResponse: id=%04x resp=%x length=%x" % + (bufferID, respType, Length[0])) deviceID = self.DataStore.getDeviceID() if bufferID != 0xF0F0: self.DataStore.setRegisteredDeviceID(bufferID) @@ -3548,7 +3764,7 @@ class CCommunicationService(object): # message is probably corrupt raise BadResponse('unknown response type %x' % respType) else: - msg = 'message from console contains unknown device ID (id=%04x resp=%x req=%x)' % (bufferID, respType, reqType) + msg = 'message from console contains unknown device ID (id=%04x resp=%x)' % (bufferID, respType) logdbg(msg) log_frame(Length[0],Buffer[0]) raise BadResponse(msg) @@ -3686,15 +3902,31 @@ class CCommunicationService(object): def getConfigData(self): return self.DataStore.StationConfig - # FIXME: make this thread-safe - def getHistoryData(self): - return self.DataStore.HistoryData + def startCachingHistory(self, since_ts=0, num_rec=0): + self.history_cache.clear_records() + self.history_cache.since_ts = since_ts + self.command = EAction.aGetHistory - def transceiverIsPresent(self): - return self.DataStore.getTransceiverPresent() + def stopCachingHistory(self): + self.command = None - def transceiverIsRegistered(self): - return self.DataStore.getDeviceRegistered() + def getUncachedHistoryCount(self): + return self.history_cache.num_outstanding_records + + def getNextHistoryIndex(self): + return self.history_cache.next_index + + def getNumHistoryScanned(self): + return self.history_cache.num_scanned + + def getLatestHistoryIndex(self): + return self.DataStore.LastStat.LatestHistoryIndex + + def getHistoryCacheRecords(self): + return self.history_cache.records + + def clearHistoryCache(self): + self.history_cache.clear_records() def startRFThread(self): if self.child is not None: @@ -3761,8 +3993,7 @@ class CCommunicationService(object): self.pollCount += 1 if StateBuffer[0][0] == 0x16: break - else: - time.sleep(self.nextSleep) + time.sleep(self.nextSleep) else: return diff --git a/docs/changes.txt b/docs/changes.txt index 2a5d0cb6..833c611c 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -1,6 +1,8 @@ weewx change history -------------------- +Added catchup to the WS28xx driver, but still no hardware record generation. + 2.6.4 06/16/14 The WMR100 driver now calculates SLP in software. This fixes a problem From 578a845a46014a915403a9c79ceb5eba9184091c Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 23 Jun 2014 21:41:15 +0000 Subject: [PATCH 15/33] previous commit was the wrong driver. here is the real one. --- bin/weewx/drivers/ws28xx.py | 40 +++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/bin/weewx/drivers/ws28xx.py b/bin/weewx/drivers/ws28xx.py index 91a18058..0d41c47d 100644 --- a/bin/weewx/drivers/ws28xx.py +++ b/bin/weewx/drivers/ws28xx.py @@ -1180,13 +1180,13 @@ class WS28xx(weewx.abstractstation.AbstractStation): # that timestamps on historical records are correct def genStartupRecords(self, ts): loginf('Scanning historical records') - maxtries = 10 + maxtries = 3 ntries = 0 - last_n = n = None + last_n = n = nrem = None last_ts = now = int(time.time()) self.start_caching_history(since_ts=ts) t = WS28xx.max_records - while n is None or n > 0: + while nrem is None or nrem > 0: if ntries >= maxtries: logerr('No historical data after %d tries' % ntries) return @@ -1201,23 +1201,46 @@ class WS28xx(weewx.abstractstation.AbstractStation): ntries = 0 last_ts = now last_n = n + nrem = self.get_uncached_history_count() ni = self.get_next_history_index() li = self.get_latest_history_index() - loginf("Scanned %s of %s records: current=%s latest=%s" % - (n, t, ni, li)) + loginf("Scanned %s of %s records: current=%s latest=%s rem=%s" % + (n, t, ni, li, nrem)) self.stop_caching_history() records = self.get_history_cache_records() self.clear_history_cache() loginf('Found %d historical records' % len(records)) last_ts = None + last_rain = None for r in records: + r['dateTime'] = tstr_to_ts(r['time']) if last_ts is not None: r['usUnits'] = weewx.METRIC - r['dateTime'] = tstr_to_ts(r['time']) r['interval'] = (r['dateTime'] - last_ts) / 60 - last_ts = r['dateTime'] - # FIXME: add barometer, altimeter, windchill, heatindex + # FIXME: put these into a separate function + rain_total = r['rainTotal'] + delta = weewx.wxformulas.calculate_rain(rain_total, last_rain) + last_rain = rain_total + r['rain'] = delta + if r['rain'] is not None: + r['rain'] /= 10 # weewx wants cm + r['heatindex'] = weewx.wxformulas.heatindexC( + r['outTemp'], r['outHumidity']) + r['dewpoint'] = weewx.wxformulas.dewpointC( + r['outTemp'], r['outHumidity']) + r['windchill'] = weewx.wxformulas.windchillC( + r['outTemp'], r['windSpeed']) + adjp = r['pressure'] + if self.pressure_offset is not None and adjp is not None: + adjp += self.pressure_offset + r['barometer'] = weewx.wxformulas.sealevel_pressure_Metric( + adjp, self.altitude, r['outTemp']) + r['altimeter'] = weewx.wxformulas.altimeter_pressure_Metric( + adjp, self.altitude, algorithm='aaNOAA') + del r['time'] yield r + last_ts = r['dateTime'] + last_rain = r['rainTotal'] # FIXME: do not implement hardware record generation until we figure # out how to query the historical records faster. @@ -3637,6 +3660,7 @@ class CCommunicationService(object): # this is non-trivial since the station retains records after # it has been power cycled. idx = get_next_index(latestIndex+1) +# idx = get_next_index(latestIndex-70) # for testing self.history_cache.start_index = idx self.history_cache.next_index = idx logdbg('handleHistoryData: set start_index=%d' % idx) From ca7cdf64f6ca99b755fe382f878f00ce8e4ddcf9 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 24 Jun 2014 11:33:42 +0000 Subject: [PATCH 16/33] added logwatch rules for ws28xx changes --- util/logwatch/scripts/services/weewx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/logwatch/scripts/services/weewx b/util/logwatch/scripts/services/weewx index 4bb6e5cf..283446bf 100755 --- a/util/logwatch/scripts/services/weewx +++ b/util/logwatch/scripts/services/weewx @@ -311,6 +311,9 @@ while(defined($_ = )) { /ws28xx: MainThread: release USB interface/ || /ws28xx: MainThread: claiming USB interface/ || /ws28xx: MainThread: CCommunicationService.init/ || + /ws28xx: MainThread: Scanning historical records/ || + /ws28xx: MainThread: Scanned/ || + /ws28xx: MainThread: Found/ || /ws28xx: RFComm: console is paired to device/ || /ws28xx: RFComm: starting rf communication/ || /ws28xx: RFComm: stopping rf communication/ || From a0769ca3de33201e9772c40ddc2a9d07276a4c6a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 27 Jun 2014 12:29:38 +0000 Subject: [PATCH 17/33] fix historical wind speed/gust scaling --- bin/weewx/drivers/ws28xx.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/bin/weewx/drivers/ws28xx.py b/bin/weewx/drivers/ws28xx.py index 0d41c47d..cf362571 100644 --- a/bin/weewx/drivers/ws28xx.py +++ b/bin/weewx/drivers/ws28xx.py @@ -1021,7 +1021,7 @@ def tstr_to_ts(tstr): return None return time.mktime(time.strptime(tstr, "%Y-%m-%d %H:%M:%S")) -def buf_to_addr(a,b,c): +def bytes_to_addr(a,b,c): return ((((a & 0xF) << 8) | b) << 8) | c def addr_to_index(addr): @@ -1917,7 +1917,7 @@ class USBHardware(object): result = CWeatherTraits.RainOFL() else: val = USBHardware.toFloat_3_1(buf, start, StartOnHiNibble) - result = val + result = val / 10.0 # mm return result @staticmethod @@ -1931,7 +1931,6 @@ class USBHardware(object): result = (buf[0][start+0] & 0xF)*16**2 \ + (buf[0][start+1] >> 4)* 16**1 \ + (buf[0][start+1] & 0xF)* 16**0 - result = result / 10.0 return result @staticmethod @@ -3631,11 +3630,8 @@ class CCommunicationService(object): data.toLog() cs = newbuf[0][5] | (newbuf[0][4] << 8) -# according to doc it should be 9-11 and 12-14, but 6-8 and 9-11 works better - latestAddr = buf_to_addr(buf[0][6], buf[0][7], buf[0][8]) - thisAddr = buf_to_addr(buf[0][9], buf[0][10], buf[0][11]) -# latestAddr = buf_to_addr(buf[0][9], buf[0][10], buf[0][11]) -# thisAddr = buf_to_addr(buf[0][12], buf[0][13], buf[0][14]) + latestAddr = bytes_to_addr(buf[0][6], buf[0][7], buf[0][8]) + thisAddr = bytes_to_addr(buf[0][9], buf[0][10], buf[0][11]) latestIndex = addr_to_index(latestAddr) thisIndex = addr_to_index(thisAddr) From 615e2d1e190757ae0d00024b4fc052ff9047cef3 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Sat, 28 Jun 2014 01:33:39 +0000 Subject: [PATCH 18/33] Patch from gjr80 that fixes start time for simulated rain --- bin/weewx/drivers/simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/weewx/drivers/simulator.py b/bin/weewx/drivers/simulator.py index d5798d1a..783b0bba 100644 --- a/bin/weewx/drivers/simulator.py +++ b/bin/weewx/drivers/simulator.py @@ -187,7 +187,7 @@ class Rain(object): n_rain_packets = total_rain / Rain.bucket_tip self.period = int(npackets/n_rain_packets) self.rain_start = 3600* rain_start - self.rain_end = rain_start + 3600 * rain_length + self.rain_end = self.rain_start + 3600 * rain_length self.packet_number = 0 def value_at(self, time_ts): From 1c63535df7003a9464e653700ea9b97bee3dad37 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 11 Jul 2014 04:55:14 +0000 Subject: [PATCH 19/33] minor typo fixes --- docs/customizing.htm | 10 +++++----- docs/usersguide.htm | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/customizing.htm b/docs/customizing.htm index 7dd45987..3699cfec 100644 --- a/docs/customizing.htm +++ b/docs/customizing.htm @@ -607,7 +607,7 @@ or in foobar units: $day.barometer.min.foobar Comment - (no tag) + (no tag) Value is returned as a string, formatted using an appropriate string format from skin.conf. A unit label (e.g., °F) from skin.conf is also attached @@ -644,7 +644,7 @@ or in foobar units: $day.barometer.min.foobar - .nolabel(string_format, NONE_string) + .nolabel(string_format, NONE_string) Value is returned as a string, using the string format specified with string_format. If the value is None, the string NONE_string will be @@ -677,7 +677,7 @@ or in foobar units: $day.barometer.min.foobar Returned Value - (no tag) + (no tag) From skin.conf From skin.conf From skin.conf @@ -843,8 +843,8 @@ or in foobar units: $day.barometer.min.foobar

    Note:

      -
    • Tags that take an argument (such as .string(NONE_string)) - do not require parenthesis if the argument is omitted.Thus, you can +
    • Tags that take an argument, such as .string(NONE_string), + do not require parenthesis if the argument is omitted. Thus, you can specify either $month.outTemp.string() or $month.outTemp.string, if you want the default value of NONE_string. They produce the same diff --git a/docs/usersguide.htm b/docs/usersguide.htm index ba7d7ed7..415fd89c 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -2817,7 +2817,7 @@ sudo /etc/init.d/apache2 restart

      Weather data

      - Meteorological ata are saved in the + Meteorological data are saved in the archive database. For a Sqlite configuration, simply save the weewx.sdb file. For a MySQL configuration, save a dump of the archive database. From 48c083879c964ff900776ab748f2bfb3275b32a2 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 11 Jul 2014 04:56:59 +0000 Subject: [PATCH 20/33] guard against non-printing characters. added support for uv and solar radiation. enable mapping via config to future-proof for additional sensors. --- bin/weewx/drivers/cc3000.py | 58 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/bin/weewx/drivers/cc3000.py b/bin/weewx/drivers/cc3000.py index 9a3978b1..31c130d6 100644 --- a/bin/weewx/drivers/cc3000.py +++ b/bin/weewx/drivers/cc3000.py @@ -24,6 +24,7 @@ catchup on startup. from __future__ import with_statement import serial +import string import syslog import time @@ -37,7 +38,7 @@ INHG_PER_MBAR = 0.0295333727 METER_PER_FOOT = 0.3048 MILE_PER_KM = 0.621371 -DRIVER_VERSION = '0.6' +DRIVER_VERSION = '0.7' DEFAULT_PORT = '/dev/ttyS0' def logmsg(level, msg): @@ -67,18 +68,20 @@ class CC3000(weewx.abstractstation.AbstractStation): '''weewx driver that communicates with a RainWise CC3000 data logger.''' # map rainwise names to weewx names - LABEL_MAP = { 'TIMESTAMP': 'TIMESTAMP', - 'TEMP OUT': 'outTemp', - 'HUMIDITY': 'outHumidity', - 'WIND DIRECTION': 'windDir', - 'WIND SPEED': 'windSpeed', - 'WIND GUST': 'windGust', - 'PRESSURE': 'pressure', - 'TEMP IN': 'inTemp', - 'RAIN': 'day_rain_total', - 'STATION BATTERY': 'consBatteryVoltage', - 'BATTERY BACKUP': 'bkupBatteryVoltage', - } + DEFAULT_LABEL_MAP = { 'TIMESTAMP': 'TIMESTAMP', + 'TEMP OUT': 'outTemp', + 'HUMIDITY': 'outHumidity', + 'WIND DIRECTION': 'windDir', + 'WIND SPEED': 'windSpeed', + 'WIND GUST': 'windGust', + 'PRESSURE': 'pressure', + 'TEMP IN': 'inTemp', + 'RAIN': 'day_rain_total', + 'STATION BATTERY': 'consBatteryVoltage', + 'BATTERY BACKUP': 'bkupBatteryVoltage', + 'SOLAR RADIATION': 'radiation', + 'UV INDEX': 'UV', + } def __init__(self, **stn_dict): self.altitude = stn_dict['altitude'] @@ -91,6 +94,7 @@ class CC3000(weewx.abstractstation.AbstractStation): self.use_station_time = stn_dict.get('use_station_time', True) self.max_tries = int(stn_dict.get('max_tries', 5)) self.retry_wait = int(stn_dict.get('retry_wait', 60)) + self.label_map = stn_dict.get('label_map', self.DEFAULT_LABEL_MAP) self.last_rain = None @@ -299,7 +303,7 @@ class CC3000(weewx.abstractstation.AbstractStation): for i,v in enumerate(values): if i >= len(self.header): continue - label = self.LABEL_MAP.get(self.header[i]) + label = self.label_map.get(self.header[i]) if label is None: continue if label == 'TIMESTAMP': @@ -323,6 +327,9 @@ def _to_ts(tstr, fmt="%Y/%m/%d %H:%M:%S"): def _format_bytes(buf): return ' '.join(["%0.2X" % ord(c) for c in buf]) +def _fmt(buf): + return filter(lambda x: x in string.printable, buf) + # calculate the crc for a string using CRC-16-CCITT # http://bytes.com/topic/python/insights/887357-python-check-crc-frame-crc-16-ccitt def _crc16(data): @@ -406,11 +413,10 @@ class Station(object): def command(self, cmd): self.write("%s\r" % cmd) data = self.get_data() - logdbg("station replied to command with '%s'" % data) data = data.strip() if data != cmd: - raise weewx.WeeWxIOError("Command failed: cmd='%s' data='%s'" % - (cmd, data)) + raise weewx.WeeWxIOError("Command failed: cmd='%s' reply='%s' (%s)" + % (cmd, _fmt(data), _format_bytes(data))) return self.get_data() def send_cmd(self, cmd): @@ -433,7 +439,10 @@ class Station(object): break else: raise weewx.WeeWxIOError("Unexpected byte 0x%0.2X" % ord(c)) - buf.append(c) + if c in string.printable: + buf.append(c) + else: + loginf("skipping unprintable character 0x%0.2X" % ord(c)) data = ''.join(buf) logdbg("got bytes: '%s'" % _format_bytes(data)) _check_crc(data) @@ -443,7 +452,7 @@ class Station(object): logdbg("set echo to %s" % cmd) data = self.command('ECHO=%s' % cmd) if data != 'OK': - raise weewx.WeeWxIOError("Set ECHO failed: %s" % data) + raise weewx.WeeWxIOError("Set ECHO failed: %s" % _fmt(data)) def get_header(self): data = self.command("HEADER") @@ -462,13 +471,13 @@ class Station(object): def get_memory_status(self): data = self.command("MEM=?") - logdbg("memory status: %s" % data) + logdbg("memory status: %s" % _fmt(data)) return data def clear_memory(self): data = self.command("MEM=CLEAR") if data != 'OK': - raise weewx.WeeWxIOError("Failed to clear memory: %s" % data) + raise weewx.WeeWxIOError("Failed to clear memory: %s" % _fmt(data)) def gen_records(self, nrec): """generator function for getting records from the device""" @@ -505,7 +514,8 @@ class Station(object): s = "TIME=%s" % tstr data = self.command(s) if data != 'OK': - raise weewx.WeeWxIOError("Failed to set time to %s: %s" % (s,data)) + raise weewx.WeeWxIOError("Failed to set time to %s: %s" % + (s, _fmt(data))) def get_units(self): data = self.command("UNITS=?") @@ -516,7 +526,7 @@ class Station(object): data = self.command("UNITS=%s" % units) if data != 'OK': raise weewx.WeeWxIOError("Failed to set units to %s: %s" % - (units, data)) + (units, _fmt(data))) def get_interval(self): data = self.command("LOGINT=?") @@ -527,7 +537,7 @@ class Station(object): data = self.command("LOGINT=%d" % interval) if data != 'OK': raise weewx.WeeWxIOError("Failed to set logging interval: %s" % - data) + _fmt(data)) def get_version(self): data = self.command("VERSION") From 548816680aabf09915522af37299b220b4092c5e Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 11 Jul 2014 16:21:52 +0000 Subject: [PATCH 21/33] use consistent terms for running weewx --- docs/setup.htm | 4 ++-- docs/usersguide.htm | 33 +++++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/setup.htm b/docs/setup.htm index fd12eb40..f901b05c 100644 --- a/docs/setup.htm +++ b/docs/setup.htm @@ -230,9 +230,9 @@ function showstartup(id) {

      Run

      -

      Run the main program from the command line:

      +

      Run the main program directly:

      cd /home/weewx - ./bin/weewxd weewx.conf

      + sudo ./bin/weewxd weewx.conf

      Or as a daemon automatically when the computer starts:

      Debian
      diff --git a/docs/usersguide.htm b/docs/usersguide.htm index 415fd89c..69066066 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -2566,14 +2566,13 @@ mysql> GRANT select, update, create, delete, insert ON stats.* TO weewx@local [StdArchive] for details.

      Running weewx

      -

      Weewx can be run either from the command line - (useful for diagnostic purposes because it will print out a summary of - every LOOP data), or as a daemon. When first trying - weewx, it is best to run it from the command - line because you will be able to see command line diagnostics, as well +

      Weewx can be run either directly, + or as a daemon. When first trying + weewx, it is best to run it directly + because you will be able to see sensor output and diagnostics, as well as log messages. Once everything is working properly, run it as a daemon.

      -

      Running from the command line

      +

      Running directly

      To run weewx directly, invoke the main program, weewxd, giving the configuration file as its only parameter:

      @@ -2586,7 +2585,8 @@ mysql> GRANT select, update, create, delete, insert ON stats.* TO weewx@local

      Weewx will then start monitoring live sensor data (also referrred to as 'LOOP' data), printing a short version of the received data on standard output, about - once every two seconds.

      + once every two seconds for a Vantage station, or considerably longer + for some other stations.

      You can tell a running instance of weewx to reread its configuration file by sending it the HUP signal. @@ -2888,13 +2888,13 @@ sudo /etc/init.d/apache2 restart

    • Look at the log file. I am always happy to take questions, but the first thing I will ask you is, "Did you look at the log file?" -

      tail -f /var/log/messages

      +

      sudo tail -f /var/log/syslog

    • -
    • Run from the command line. Generally, weewx - will catch and log any unrecoverable exceptions. But if you are getting - strange results, it is worth running from the command line and looking - for any clues. -

      $BIN_ROOT/weewxd $CONFIG_ROOT/weewx.conf

      +
    • Run directly rather than as a daemon. + Generally, weewx will catch and log any + unrecoverable exceptions. But if you are getting strange results, + it is worth running directly and looking for any clues. +

      sudo $BIN_ROOT/weewxd $CONFIG_ROOT/weewx.conf

    @@ -3180,7 +3180,8 @@ Jan 1 09:46:32 saga weewx[15292]: wxengine: pid file is /var/run/weewx.pid
configobj errors

These are errors in the configuration file. Two are very common. Incidentally, these errors are far easier to diagnose when - weewx is run from the command line.

+ weewx is run directly than when it is run + as a daemon.

configobj.DuplicateError exception

This error is caused by using an identifier more than once in the configuration file. For example, you may have inadvertently listed @@ -3536,8 +3537,8 @@ port = /dev/cuaU0

rate of V3.X this is unlikely to happen anytime soon. In any case, I doubt the transition will affect the average weewx user.

All writes to the databases are protected by transactions. You can kill the - program at any time (either Control-C if run from the command line or "/etc/init.d/weewx - stop" if a daemon) without fear of corrupting the databases.

+ program at any time (either Control-C if run directly or "/etc/init.d/weewx + stop" if run as a daemon) without fear of corrupting the databases.

The code makes ample use of exceptions to insure graceful recovery from problems such as network outages. It also monitors socket and console timeouts, restarting whatever it was working on several times before giving up. In the case of an From 47b081f5ddc0bd0e42d3175e21d9fbc1fce12e19 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Sun, 13 Jul 2014 14:37:53 +0000 Subject: [PATCH 22/33] added notes about templates. another attempt to answer units questions before they hit the forums. --- docs/customizing.htm | 69 +++++++++++++++++++++++++++----------------- docs/usersguide.htm | 55 +++++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 47 deletions(-) diff --git a/docs/customizing.htm b/docs/customizing.htm index 3699cfec..03d3b1c3 100644 --- a/docs/customizing.htm +++ b/docs/customizing.htm @@ -157,8 +157,9 @@ table#stattypes td { that it they do not actually generate anything. Instead, they use the reporting service engine to arrange for things to be transferred to a remote server.

+

Skins

-

Each report has a Skin associated with it. For most reports, +

Each report has a skin associated with it. For most reports, the relationship with the skin is an obvious one: it contains the templates, any auxiliary files such as background GIFs or CSS style sheets, and a skin configuration file, skin.conf. @@ -190,12 +191,22 @@ table#stattypes td { }); +

Templates

+

A template is a text file that is processed by + weewx to create a new file. A template may + be used to generate HTML, XML, CSV, javascript, or any other type of + text file. A template typically contains variables that are replaced + by when creating the new file. Templates may also contain programming + logic.

+

Each template file lives in the skin directory of the skin that uses + it. By convention, a template file ends with the .tmpl extension.

+

Generators

-

To create their output, skins rely on one or more Generators, code +

To create their output, skins rely on one or more Generators that actually create useful things such as HTML files or plot images. Generators can also copy files around or FTP/rsync them to remote - locations. The default install of weewx includes - the following generators:

+ locations. The default install of weewx + includes the following generators:

@@ -314,15 +325,22 @@ table#stattypes td { changing the template files. The former is generally easier, but occasionally the latter is necessary.

Changing options

-

Changing an option means either modifying the main configuration file weewx.conf, or the skin configuration file for the - standard skin that comes with the distribution (nominally, file $SKIN_ROOT/Standard/skin.conf).

+

Changing an option means either modifying the main configuration file + weewx.conf, or the skin configuration file + skin.conf.

+

Each skin will have a skin.conf that defines + its default configuration. The examples in this guide refer to the + standard skin that comes with the distribution.

Changing options in skin.conf

-

With this approach, the user edits the skin configuration file for the - standard skin that comes with weewx, located - in $SKIN_ROOT/Standard/skin.conf, - using a text editor. For example, suppose you wish to use metric units +

With this approach, edit the skin configuration file with a text + editor. Changes made in this way will be used by + weewx the next time it generates reports, + which is typically the next archive interval; there is no need to + restart weewx to see the results of the + changes.

+

For the standard skin that comes with weewx, + the file is $SKIN_ROOT/Standard/skin.conf.

+

For example, suppose you wish to use metric units in the presentation layer, instead of the default US Customary Units. The section that controls units is [Units][[Groups]]. It looks like this:

@@ -382,15 +400,17 @@ table#stattypes td { ...

Overriding options in skin.conf from weewx.conf

-

This approach is very similar, except that instead of changing the skin - configuration file directly, you override its options by editing the - main configuration file, weewx.conf. The +

This approach is very similar, except that instead of changing the + skin configuration file directly, you override its options by editing + the main configuration file, weewx.conf. The advantage of this approach is that you can use the same skin to produce - several different output, each with separate options.

+ several different output, each with separate options.

+

With this approach, you must restart weewx + to see the effects of any changes.

Revisiting our example, suppose you want two reports, one in US - Customary, the other in Metric. The former will go in the directory $HTML_ROOT, the latter in a directory, $HTML_ROOT/metric. + Customary, the other in Metric. The former will go in the directory + $HTML_ROOT, the latter in a directory, + $HTML_ROOT/metric. If you just simply modify skin.conf, you can get one, but not both at the same time. Alternatively, you could create a whole new skin by copying all the files to a new skin directory @@ -434,13 +454,10 @@ table#stattypes td { group_speed = meter_per_second group_speed2 = meter_per_second2 group_temperature = degree_C - - [[FTP]] - ... - ... (as before) -

We have done two things different from the stock reports. First (1), we - have renamed the first report from StandardReport to - USReport for clarity; and (2) we have + +

We have done two things different from the stock reports. First (1), + we have renamed the first report from StandardReport to + USReport for clarity; and second (2), we have introduced a new report MetricReport, just like the first, except it puts its results in a different spot and uses different units. Both use the same skin, the Standard diff --git a/docs/usersguide.htm b/docs/usersguide.htm index 69066066..486f74f9 100644 --- a/docs/usersguide.htm +++ b/docs/usersguide.htm @@ -1640,7 +1640,7 @@ longitude = -77.0366 It uses the skin "Standard", which generates four HTML pages ("day", "week", "month", and "year" observations), plot graphs for same, an RSS feed, and NOAA monthly and yearly - reports. Unless changed otherwise, it uses US Customary Units and puts the results + reports. The default configuration uses US Customary Units and puts the results in public_html and subdirectory public_html/NOAA.

[[FTP]]

@@ -1701,27 +1701,38 @@ longitude = -77.0366 on the remote server. Valid values are 1 to enable and 0 to disable. Required. Default is 0.

[StdConvert]

-

This section is for configuring the StdConvert - service. This service acts as a filter, converting the unit system coming off - your hardware to a target output unit system. Everything that follows, including - the archiving service, will use the target unit system. Hence, your data - will be stored using your chosen unit system.

-

Once chosen, it cannot be changed! Weewx does not allow you to mix - unit systems within the databases. You must chose one or the other and then - stick with it. This means that users coming from wview (which uses US Customary) - should not change the default setting. Having said this, there is a way of reconfiguring - the database to use another unit system. See the section +

This section is for configuring the StdConvert + service.

+

Note!
+ If you would like to change the units that are displayed + in plots or files, you should make changes to the skin as described in + the Customizing Guide, under section + Changing options. +

+

The StdConvert service acts as a filter, + converting the unit system coming off your hardware to a target output + unit system. Everything that follows, including the archiving service, + will use the target unit system. Hence, your data will be stored using + your chosen unit system.

+

Once chosen, it cannot be changed! Weewx does not allow you to + mix unit systems within the databases. You must chose one or the other + and then stick with it. This means that users coming from wview (which + uses US Customary) should not change the default setting. Having said + this, there is a way of reconfiguring the database to use another unit + system. See the section Changing the unit system in the Customizing Guide.

-

Note that whatever you choose here, it does not affect your options for the - unit system to be used for reporting. Because of this, unless you - have a special purpose application, there is really no good reason to change - from the default, which is US.

-

Warning!
- If, despite these precautions, you do - decide to change to Metric, be sure to read the sections - [StdCalibrate] and - [StdQC] below, and change the units there as well!

+

Note that whatever you choose here, it does not affect your options for + the unit system to be used for reporting. Because of this, unless + you have a special purpose application, there is really no good reason to + change from the default, which is US.

+

Warning!
+ If, despite these precautions, you do decide to change the units of data + stored in the database, be sure to read the sections + [StdCalibrate] and + [StdQC], and change the + units there as well!

+

target_unit

Set to either US, METRICWX, or METRIC. The difference between @@ -3669,7 +3680,9 @@ port = /dev/cuaU0 stations use an odd mix of US and metric. The Fine Offset stations are metric. The LaCrosse stations are metric. The Hideki stations are a mix of US and metric. One-wire devices - can be either US or metric. + can be either US or metric. RainWise data loggers can be configured + to use either US or metric. PeetBros devices use a mix of US and + metric.

  • In the database. Either US or Metric can be used.
  • In the presentation (i.e., html and image files).
  • From 70f0d0a29705ff0945a44f741df8b2454087babb Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 14 Jul 2014 02:15:54 +0000 Subject: [PATCH 23/33] prune skin conf files a bit --- skins/Ftp/skin.conf | 32 +++++++++----------------------- skins/Rsync/skin.conf | 40 +++++++++++++--------------------------- skins/Standard/skin.conf | 18 ++---------------- 3 files changed, 24 insertions(+), 66 deletions(-) diff --git a/skins/Ftp/skin.conf b/skins/Ftp/skin.conf index ccd584ec..00ec7759 100644 --- a/skins/Ftp/skin.conf +++ b/skins/Ftp/skin.conf @@ -1,26 +1,12 @@ -############################################################################################ -# # -# # -# FTP CONFIGURATION FILE # -# # -# # -############################################################################################ -# # -# Copyright (c) 2010 Tom Keffer # -# # -# See the file LICENSE.txt for your full rights. # -# # -############################################################################################ -# -# $Revision$ -# $Author$ -# $Date$ -# -############################################################################################ - -# This isn't really a "report". Instead, we use the report engine to run an FTP service. +############################################################################### +# $Id$ +# Copyright (c) 2010 Tom Keffer # +# # +# FTP CONFIGURATION FILE # +# This 'report' does not generate any files. Instead, we use the report # +# engine to invoke FTP, which copies files to another location. # +############################################################################### [Generators] - # The list of generators that are part of this report: - generator_list = weewx.reportengine.FtpGenerator + generator_list = weewx.reportengine.FtpGenerator diff --git a/skins/Rsync/skin.conf b/skins/Rsync/skin.conf index 9fdb052c..00b1dd71 100644 --- a/skins/Rsync/skin.conf +++ b/skins/Rsync/skin.conf @@ -1,27 +1,13 @@ -############################################################################################ -# # -# # -# RSYNC CONFIGURATION FILE # -# # -# # -############################################################################################ -# # -# Copyright (c) 2012 Will Page # -# With credit to Tom Keffer # -# # -# See the file LICENSE.txt for your full rights. # -# # -############################################################################################ -# -# $Revision$ -# $Author$ -# $Date$ -# -############################################################################################ - -# This isn't really a "report". Instead, we use the report engine to run an rsync service. - -[Generators] - # The list of generators that are part of this report: - generator_list = weewx.reportengine.RsyncGenerator - +############################################################################### +# $Id$ +# Copyright (c) 2012 Will Page # +# With credit to Tom Keffer # +# # +# RSYNC CONFIGURATION FILE # +# This 'report' does not generate any files. Instead, we use the report # +# engine to invoke rsync, which synchronizes files between two locations. # +############################################################################### + +[Generators] + generator_list = weewx.reportengine.RsyncGenerator + diff --git a/skins/Standard/skin.conf b/skins/Standard/skin.conf index 0248ccf3..52a0126e 100644 --- a/skins/Standard/skin.conf +++ b/skins/Standard/skin.conf @@ -1,21 +1,7 @@ ############################################################################### -# # -# # -# STANDARD SKIN CONFIGURATION FILE # -# # -# # -############################################################################### -# # +# $Id$ # Copyright (c) 2010 Tom Keffer # -# # -# See the file LICENSE.txt for your full rights. # -# # -############################################################################### -# -# $Revision$ -# $Author$ -# $Date$ -# +# STANDARD SKIN CONFIGURATION FILE # ############################################################################### [Extras] From 65641f66d4f32b9a71fd9328182da6452d176b58 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 14 Jul 2014 02:16:32 +0000 Subject: [PATCH 24/33] use per-skin HTML_ROOT for rsync configurations --- bin/weewx/reportengine.py | 8 +++++--- docs/changes.txt | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/weewx/reportengine.py b/bin/weewx/reportengine.py index 46681a01..ce8545a6 100644 --- a/bin/weewx/reportengine.py +++ b/bin/weewx/reportengine.py @@ -213,10 +213,12 @@ class RsyncGenerator(ReportGenerator): # We don't try to collect performance statistics about rsync, because rsync # will report them for us. Check the debug log messages. try: + if self.skin_dict.has_key('HTML_ROOT'): + html_root = self.skin_dict['HTML_ROOT'] + else: + html_root = self.config_dict['StdReport']['HTML_ROOT'] rsyncData = weeutil.rsyncupload.RsyncUpload( - local_root = os.path.join( - self.config_dict['WEEWX_ROOT'], - self.config_dict['StdReport']['HTML_ROOT']), + local_root = os.path.join(self.config_dict['WEEWX_ROOT'], html_root), remote_root = self.skin_dict['path'], server = self.skin_dict['server'], user = self.skin_dict.get('user', None), diff --git a/docs/changes.txt b/docs/changes.txt index 833c611c..286f8c78 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -1,6 +1,10 @@ weewx change history -------------------- +X.X.X XX/XX/XX + +Enabled multiple rsync instances for a single weewx instance. + Added catchup to the WS28xx driver, but still no hardware record generation. 2.6.4 06/16/14 From 57a51d6e2019b218f75d8530f6e74f7dd527c0fa Mon Sep 17 00:00:00 2001 From: Denny Page Date: Tue, 15 Jul 2014 03:40:27 +0000 Subject: [PATCH 25/33] Change clock_check and max_drift configuration from the Sation section to the StdTimeSynch section to match documentation and default config file --- bin/weewx/wxengine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/weewx/wxengine.py b/bin/weewx/wxengine.py index bea5fa2e..e85d37a8 100644 --- a/bin/weewx/wxengine.py +++ b/bin/weewx/wxengine.py @@ -655,8 +655,8 @@ class StdTimeSynch(StdService): # Zero out the time of last synch, and get the time between synchs. self.last_synch_ts = 0 - self.clock_check = int(config_dict['Station'].get('clock_check', 14400)) - self.max_drift = int(config_dict['Station'].get('max_drift', 5)) + self.clock_check = int(config_dict['StdTimeSynch'].get('clock_check', 14400)) + self.max_drift = int(config_dict['StdTimeSynch'].get('max_drift', 5)) self.bind(weewx.STARTUP, self.startup) self.bind(weewx.PRE_LOOP, self.pre_loop) From 7ff977684e19c10d0028e363115b221ecd621d7f Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 21 Jul 2014 19:05:42 +0000 Subject: [PATCH 26/33] added luc's hwp history timings --- bin/weewx/drivers/ws28xx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/weewx/drivers/ws28xx.py b/bin/weewx/drivers/ws28xx.py index cf362571..7f6c19af 100644 --- a/bin/weewx/drivers/ws28xx.py +++ b/bin/weewx/drivers/ws28xx.py @@ -132,6 +132,9 @@ console clock. The console can record up to 1797 history records. Reading 1795 history records took about 110 minutes on a raspberry pi, for an average of 3.6 seconds per history record. +Reading 1750 history records took 19 minutes using HeavyWeatherPro on a +Windows 7 64-bit laptop. + Message Types The first byte of a message determines the message type. From 85959c98d95f1f3c9ce7375492ecf13295bc230e Mon Sep 17 00:00:00 2001 From: Denny Page Date: Mon, 21 Jul 2014 21:57:44 +0000 Subject: [PATCH 27/33] Note location change of clock synchronization parameters --- docs/changes.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changes.txt b/docs/changes.txt index 286f8c78..91cf7217 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -4,9 +4,13 @@ weewx change history X.X.X XX/XX/XX Enabled multiple rsync instances for a single weewx instance. - + Added catchup to the WS28xx driver, but still no hardware record generation. +Moved clock synchronization options clock_check and max_drift back to +section [StdTimeSynch]. + + 2.6.4 06/16/14 The WMR100 driver now calculates SLP in software. This fixes a problem From ea52f7ad0b8acf3d6e40c51b5364859aff6ddab4 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 23 Jul 2014 13:55:12 +0000 Subject: [PATCH 28/33] fix bugs in radar display in standard skin --- skins/Standard/index.html.tmpl | 7 ++++++- skins/Standard/mobile.html.tmpl | 15 +++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/skins/Standard/index.html.tmpl b/skins/Standard/index.html.tmpl index 970605fa..8de33a36 100644 --- a/skins/Standard/index.html.tmpl +++ b/skins/Standard/index.html.tmpl @@ -283,9 +283,14 @@ #if $Extras.has_key('radar_img')
    + #if $Extras.has_key('radar_url') - Radar + #end if + Radar + #if $Extras.has_key('radar_url') +

    Click image for expanded radar loop

    + #end if
    #end if diff --git a/skins/Standard/mobile.html.tmpl b/skins/Standard/mobile.html.tmpl index 9fd7a153..225a9839 100644 --- a/skins/Standard/mobile.html.tmpl +++ b/skins/Standard/mobile.html.tmpl @@ -73,11 +73,18 @@
    -#if $Extras.has_key('radar_url') - + +#if $Extras.has_key('radar_img')
    - -

    $current.dateTime

    + #if $Extras.has_key('radar_url') + + #end if + Radar + #if $Extras.has_key('radar_url') + + #end if + +

    $current.dateTime

    #end if From 4669902aebf43daea711705fed7af634659663bb Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 24 Jul 2014 15:30:40 +0000 Subject: [PATCH 29/33] added rain rate calculation to ultimeter driver --- bin/weewx/drivers/ultimeter.py | 8 +++++++- docs/changes.txt | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bin/weewx/drivers/ultimeter.py b/bin/weewx/drivers/ultimeter.py index dc701ba7..e55c31ec 100644 --- a/bin/weewx/drivers/ultimeter.py +++ b/bin/weewx/drivers/ultimeter.py @@ -91,7 +91,7 @@ import weewx.units import weewx.uwxutils import weewx.wxformulas -DRIVER_VERSION = '0.9.3' +DRIVER_VERSION = '0.9.4' DEFAULT_PORT = '/dev/ttyS0' DEBUG_READ = 0 @@ -149,6 +149,7 @@ class Ultimeter(weewx.abstractstation.AbstractStation): self.max_tries = int(stn_dict.get('max_tries', 5)) self.pressure_offset = float(stn_dict.get('pressure_offset', 0)) self.last_rain = None + self.last_rain_ts = None loginf('driver version is %s' % DRIVER_VERSION) loginf('using serial port %s' % self.port) loginf('polling interval is %s' % str(self.polling_interval)) @@ -207,6 +208,11 @@ class Ultimeter(weewx.abstractstation.AbstractStation): packet['rain'] = None self.last_rain = packet['long_term_rain'] + # calculate the rain rate + packet['rainRate'] = weewx.wxformulas.calculate_rain_rate( + packet['rain'], packet['dateTime'], self.last_rain_ts) + self.last_rain_ts = packet['dateTime'] + class Station(object): def __init__(self, port): self.port = port diff --git a/docs/changes.txt b/docs/changes.txt index 91cf7217..0eac312a 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -10,6 +10,10 @@ Added catchup to the WS28xx driver, but still no hardware record generation. Moved clock synchronization options clock_check and max_drift back to section [StdTimeSynch]. +Changed lux-to-W/m^2 conversion factor in the fine offset driver. + +Added rain rate calculation to Ultimeter driver. + 2.6.4 06/16/14 From 870497d690e325c9234e020d0ce2463b394c8103 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 24 Jul 2014 15:31:38 +0000 Subject: [PATCH 30/33] use better multiplier for fine offset illuminance-to-radiation conversion --- bin/weewx/drivers/fousb.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bin/weewx/drivers/fousb.py b/bin/weewx/drivers/fousb.py index 7eac71c2..4b24f04a 100644 --- a/bin/weewx/drivers/fousb.py +++ b/bin/weewx/drivers/fousb.py @@ -151,6 +151,20 @@ specified in weewx.conf, and 'temperature' is read from the sensors. The 'barometer' value is reported to wunderground, cwop, etc. +Illuminance and Radiation + +The 30xx stations include a sensor that reports illuminance (lux). The +conversion from lux to radiation is a function of the angle of the sun and +altitude, but this driver uses a single multiplier as an approximation. + +Apparently the display on fine offset stations is incorrect. The display +reports radiation with a lux-to-W/m^2 multiplier of 0.001464. Apparently +Cumulus and WeatherDisplay use a multiplier of 0.0079. The multiplier for +sea level with sun directly overhead is 0.01075. + +This driver uses the sea level multiplier of 0.01075. Use an entry in +StdCalibrate to adjust this for your location and altitude. + From Jim Easterbrook: The weather station memory has two parts: a "fixed block" of 256 bytes @@ -260,7 +274,7 @@ keymap = { 'windDir' : ('wind_dir', 22.5), # station is 0-15, weewx wants deg 'windGustDir' : ('wind_dir', 22.5), # station is 0-15, weewx wants deg 'rain' : ('rain', 0.1), # station is mm, weewx wants cm - 'radiation' : ('illuminance', 0.001464), # lux, weewx wants W/m^2 + 'radiation' : ('illuminance', 0.01075), # lux, weewx wants W/m^2 'UV' : ('uv', 1.0), 'dewpoint' : ('dewpoint', 1.0), 'heatindex' : ('heatindex', 1.0), From 3985fdaa04458bb58bc0283cef110e6ceaffb28b Mon Sep 17 00:00:00 2001 From: Denny Page Date: Fri, 25 Jul 2014 03:22:50 +0000 Subject: [PATCH 31/33] For setTime, move retrieval of system time from the engine into the setTime method itself. This greatly improves the accuracy of time synchronization with the station. --- bin/wee_config_vantage | 2 +- bin/wee_config_ws23xx | 8 +++----- bin/weewx/abstractstation.py | 2 +- bin/weewx/drivers/cc3000.py | 10 +++++----- bin/weewx/drivers/fousb.py | 4 ++-- bin/weewx/drivers/vantage.py | 28 +++++++++++++++------------- bin/weewx/drivers/ws23xx.py | 4 ++-- bin/weewx/drivers/ws28xx.py | 2 +- bin/weewx/wxengine.py | 7 ++++--- docs/changes.txt | 4 ++++ 10 files changed, 38 insertions(+), 33 deletions(-) diff --git a/bin/wee_config_vantage b/bin/wee_config_vantage index ca2ab343..61b27f78 100755 --- a/bin/wee_config_vantage +++ b/bin/wee_config_vantage @@ -330,7 +330,7 @@ def set_rain_year_start(station, rain_year_start): def set_time(station): print "Setting time on console..." - station.setTime(time.time()) + station.setTime() newtime_ts = station.getTime() print "Current console time is %s" % weeutil.weeutil.timestamp_to_string(newtime_ts) diff --git a/bin/wee_config_ws23xx b/bin/wee_config_ws23xx index 9fbc65f7..7a3cd748 100755 --- a/bin/wee_config_ws23xx +++ b/bin/wee_config_ws23xx @@ -132,15 +132,13 @@ def setclock(station, prompt): v = station.getTime() vstr = weeutil.weeutil.timestamp_to_string(v) print "Station clock is", vstr - now = int(time.time() + 0.5) - nstr = weeutil.weeutil.timestamp_to_string(now) if prompt: - ans = raw_input("Set station clock to %s (y/n)? " % nstr) + ans = raw_input("Set station clock (y/n)? ") else: - print "Setting station clock to %s" % nstr + print "Setting station clock" ans = 'y' if ans == 'y' : - station.setTime(now) + station.setTime() v = station.getTime() vstr = weeutil.weeutil.timestamp_to_string(v) print "Station clock is now", vstr diff --git a/bin/weewx/abstractstation.py b/bin/weewx/abstractstation.py index a54e6555..4bfffbcf 100644 --- a/bin/weewx/abstractstation.py +++ b/bin/weewx/abstractstation.py @@ -32,7 +32,7 @@ class AbstractStation(object): def getTime(self): raise NotImplementedError("Method 'getTime' not implemented") - def setTime(self, newtime_ts): + def setTime(self): raise NotImplementedError("Method 'setTime' not implemented") def closePort(self): diff --git a/bin/weewx/drivers/cc3000.py b/bin/weewx/drivers/cc3000.py index 31c130d6..2c620da0 100644 --- a/bin/weewx/drivers/cc3000.py +++ b/bin/weewx/drivers/cc3000.py @@ -169,9 +169,9 @@ class CC3000(weewx.abstractstation.AbstractStation): v = station.get_time() return _to_ts(v) - def setTime(self, ts): + def setTime(self): with Station(self.port) as station: - station.set_time(ts) + station.set_time() def get_current(self): with Station(self.port) as station: @@ -508,8 +508,8 @@ class Station(object): data = self.command("TIME=?") return data - def set_time(self, ts): - tstr = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(ts)) + def set_time(self): + tstr = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time())) logdbg("set time to %s (%s)" % (tstr, ts)) s = "TIME=%s" % tstr data = self.command(s) @@ -617,7 +617,7 @@ if __name__ == '__main__': if options.gettime: print s.get_time() if options.settime: - s.set_time(time.time()) + s.set_time() if options.getint: print s.get_interval() if options.setint: diff --git a/bin/weewx/drivers/fousb.py b/bin/weewx/drivers/fousb.py index 4b24f04a..9d2e4540 100644 --- a/bin/weewx/drivers/fousb.py +++ b/bin/weewx/drivers/fousb.py @@ -802,8 +802,8 @@ class FineOffsetUSB(weewx.abstractstation.AbstractStation): # def getTime(self): # return self.get_clock() -# def setTime(self, ts): -# self.set_clock(ts) +# def setTime(self): +# self.set_clock() def genLoopPackets(self): """Generator function that continuously returns decoded packets.""" diff --git a/bin/weewx/drivers/vantage.py b/bin/weewx/drivers/vantage.py index f7f36d0e..90788385 100644 --- a/bin/weewx/drivers/vantage.py +++ b/bin/weewx/drivers/vantage.py @@ -637,24 +637,26 @@ class Vantage(weewx.abstractstation.AbstractStation): syslog.syslog(syslog.LOG_ERR, "vantage: Max retries exceeded while getting time") raise weewx.RetriesExceeded("While getting console time") - def setTime(self, newtime_ts): - """Set the clock on the Davis Vantage console + def setTime(self): + """Set the clock on the Davis Vantage console""" - newtime_ts: The time the internal clock should be set to in unix epoch time.""" - - # Unfortunately, this algorithm takes a little while to execute, so the clock - # usually ends up a few hundred milliseconds slow - newtime_tt = time.localtime(int(newtime_ts + 0.5)) - - # The Davis expects the time in reversed order, and the year is since 1900 - _buffer = struct.pack(" self.max_drift: + if abs(diff) > self.max_drift: try: - self.engine.console.setTime(now_ts) + self.engine.console.setTime() except NotImplementedError: syslog.syslog(syslog.LOG_DEBUG, "wxengine: Station does not support setting the time") except NotImplementedError: diff --git a/docs/changes.txt b/docs/changes.txt index 0eac312a..841acf0b 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -14,6 +14,10 @@ Changed lux-to-W/m^2 conversion factor in the fine offset driver. Added rain rate calculation to Ultimeter driver. +Changed setTime to retrieve system time directly rather than using a value +passed by the engine. This greatly improves the accuracy of StdTimeSync, +particularly in network based implementations. + 2.6.4 06/16/14 From 47182760642b318c069dd328c3777b5c982bc1f3 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 30 Jul 2014 13:44:43 +0000 Subject: [PATCH 32/33] fixed usb endpoint in te923 driver --- bin/weewx/drivers/te923.py | 4 ++-- docs/changes.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/weewx/drivers/te923.py b/bin/weewx/drivers/te923.py index 027c9e2d..0e964b4f 100644 --- a/bin/weewx/drivers/te923.py +++ b/bin/weewx/drivers/te923.py @@ -161,7 +161,7 @@ import weewx.abstractstation import weewx.units import weewx.wxformulas -DRIVER_VERSION = '0.9' +DRIVER_VERSION = '0.10' DEBUG_READ = 0 DEBUG_DECODE = 0 DEBUG_PRESSURE = 0 @@ -674,7 +674,7 @@ class BadRead(weewx.WeeWxIOError): """Bogus data length, CRC, header block, or other read failure""" class Station(object): - ENDPOINT_IN = 0x01 + ENDPOINT_IN = 0x81 READ_LENGTH = 0x8 def __init__(self, vendor_id=0x1130, product_id=0x6801, diff --git a/docs/changes.txt b/docs/changes.txt index 841acf0b..a7d96273 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -18,6 +18,9 @@ Changed setTime to retrieve system time directly rather than using a value passed by the engine. This greatly improves the accuracy of StdTimeSync, particularly in network based implementations. +Fixed ENDPOINT_IN in the te923 driver. This should provide better +compatibility with a wider range of pyusb versions. + 2.6.4 06/16/14 From a7886d59ca382d78792e62f43eb746758d1d0dd9 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Thu, 31 Jul 2014 01:12:02 +0000 Subject: [PATCH 33/33] Fixed a typo in the cc3000 driver. Changed from tabs to spaces. --- TODO.txt | 27 +++++++++++++++++++++++++++ bin/weewx/drivers/cc3000.py | 2 +- bin/weewx/drivers/vantage.py | 4 ++-- bin/weewx/wxengine.py | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/TODO.txt b/TODO.txt index 02549a02..0372a5bf 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,3 +4,30 @@ Figure out why the heartbeat doesn't work on the WMR200. add documentation for the extension installer and extension packaging. +Guard against database locks: + +Jul 30 08:55:22 hummingbird weewx[959]: wxengine: Caught unrecoverable exception in wxengine: +Jul 30 08:55:22 hummingbird weewx[959]: **** database is locked +Jul 30 08:55:22 hummingbird weewx[959]: **** Traceback (most recent call last): +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 962, in main +Jul 30 08:55:22 hummingbird weewx[959]: **** engine.run() +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 189, in run +Jul 30 08:55:22 hummingbird weewx[959]: **** self.dispatchEvent(weewx.Event(weewx.POST_LOOP)) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 210, in dispatchEvent +Jul 30 08:55:22 hummingbird weewx[959]: **** callback(event) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 562, in post_loop +Jul 30 08:55:22 hummingbird weewx[959]: **** self._catchup(self.engine.console.genArchiveRecords) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 625, in _catchup +Jul 30 08:55:22 hummingbird weewx[959]: **** origin='hardware')) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 210, in dispatchEvent +Jul 30 08:55:22 hummingbird weewx[959]: **** callback(event) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/wxengine.py", line 574, in new_archive_record +Jul 30 08:55:22 hummingbird weewx[959]: **** self.archive.addRecord(event.record) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weewx/archive.py", line 192, in addRecord +Jul 30 08:55:22 hummingbird weewx[959]: **** e)) +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weedb/__init__.py", line 130, in __exit__ +Jul 30 08:55:22 hummingbird weewx[959]: **** self.connection.commit() +Jul 30 08:55:22 hummingbird weewx[959]: **** File "/home/weewx/bin/weedb/__init__.py", line 97, in commit +Jul 30 08:55:22 hummingbird weewx[959]: **** self.connection.commit() +Jul 30 08:55:22 hummingbird weewx[959]: **** OperationalError: database is locked +Jul 30 08:55:22 hummingbird weewx[959]: **** Exiting. diff --git a/bin/weewx/drivers/cc3000.py b/bin/weewx/drivers/cc3000.py index 2c620da0..49bdb1e3 100644 --- a/bin/weewx/drivers/cc3000.py +++ b/bin/weewx/drivers/cc3000.py @@ -510,7 +510,7 @@ class Station(object): def set_time(self): tstr = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time())) - logdbg("set time to %s (%s)" % (tstr, ts)) + logdbg("set time to %s (%s)" % (tstr, tstr)) s = "TIME=%s" % tstr data = self.command(s) if data != 'OK': diff --git a/bin/weewx/drivers/vantage.py b/bin/weewx/drivers/vantage.py index 90788385..47c974d1 100644 --- a/bin/weewx/drivers/vantage.py +++ b/bin/weewx/drivers/vantage.py @@ -642,7 +642,7 @@ class Vantage(weewx.abstractstation.AbstractStation): for unused_count in xrange(self.max_tries) : try : - # Wake the console and begin the settime command + # Wake the console and begin the setTime command self.port.wakeup_console(max_tries=self.max_tries, wait_before_retry=self.wait_before_retry) self.port.send_data('SETTIME\n') @@ -655,7 +655,7 @@ class Vantage(weewx.abstractstation.AbstractStation): _buffer = struct.pack("