Too many years using Python 2, not enough on Python 3. Try this version of ultimeter.py.
On Sat, Apr 10, 2021 at 1:35 AM Chris Thompstone <chris.thompst...@gmail.com> wrote: > So I manually deleted all the files from the remote ftp server to make it > upload a fresh. > it tries to ftp ALL files twice, but fails on the first NOAA txt file, the > partial file from 2014-10 when station started up. > because this fails (although I can see it on the ftp server), it tries a > 2nd time to upload everything, then quits on this file again. > the whole process takes 3 minutes to upload as it tries to upload > everything (twice on every 5min cycle) because that 1 file fails. > any ideas on a fix for this? > > On Saturday, 10 April 2021 at 08:51:23 UTC+1 Chris Thompstone wrote: > >> Also why when ftp happens 1 of the files gives this: >> Uploaded file /var/www/daybarometer.png to /daybarometer.png >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: ftpgenerator: >> (2): caught exception '<class 'ftplib.error_perm'>': 553 Prohibited >> directory name >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> Traceback (most recent call last): >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/home/weewx/bin/weewx/reportengine.py", line 331, in run >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> n = ftp_data.run() >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/home/weewx/bin/weeutil/ftpupload.py", line 154, in run >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> _make_remote_dir(ftp_server, remote_dir_path) >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/home/weewx/bin/weeutil/ftpupload.py", line 269, in _make_remote_dir >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> ftp_server.mkd(remote_dir_path) >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/usr/lib/python3.7/ftplib.py", line 643, in mkd >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> resp = self.voidcmd('MKD ' + dirname) >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/usr/lib/python3.7/ftplib.py", line 278, in voidcmd >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> return self.voidresp() >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/usr/lib/python3.7/ftplib.py", line 251, in voidresp >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> resp = self.getresp() >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> File "/usr/lib/python3.7/ftplib.py", line 246, in getresp >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> raise error_perm(resp) >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: **** >> ftplib.error_perm: 553 Prohibited directory name >> Apr 10 08:45:37 weewx weewx[5576] ERROR weewx.reportengine: ftpgenerator: >> Upload failed >> >> the file seems to be on the ftp server (I tried deleting everything on >> the ftp to see if it uploads it) >> only happens for this 1 file. >> I notice that all my files in /var/www that upload are set with root >> permissions, does that have anything to do with it? >> Not sure how to make weewx change set these to www-data permissions >> >> On Saturday, 10 April 2021 at 08:35:17 UTC+1 Chris Thompstone wrote: >> >>> Sorry it doesn't like that one: >>> Apr 10 08:32:33 weewx weewx[5327] DEBUG weewx.drivers.ultimeter: Decode >>> failed for 'b'----'': invalid literal for int() with base 16: b'----' >>> it prefers mine, lol >>> Chris >>> >>> On Friday, 9 April 2021 at 23:05:13 UTC+1 tke...@gmail.com wrote: >>> >>>> You had the right idea, but we have to be careful about values that are >>>> encoded in only two bytes. >>>> >>>> Try this version. >>>> >>>> -tk >>>> >>>> On Fri, Apr 9, 2021 at 8:50 AM Chris Thompstone <chris.th...@gmail.com> >>>> wrote: >>>> >>>>> Don't worry, I think I may have patched it. I'm not really much of a >>>>> coder (although can do some stuff)... >>>>> Modded your file. see line 345 onwards. >>>>> See attached >>>>> >>>>> On Friday, 9 April 2021 at 16:36:35 UTC+1 tke...@gmail.com wrote: >>>>> >>>>>> Sorry. I’ll try to get an update out later today. >>>>>> >>>>>> On Fri, Apr 9, 2021 at 8:18 AM Chris Thompstone < >>>>>> chris.th...@gmail.com> wrote: >>>>>> >>>>>>> >>>>>>> just tried that and get: >>>>>>> Apr 9 16:17:18 weewx weewx[1719] DEBUG weewx.drivers.ultimeter: >>>>>>> Close serial port /dev/ttyUSB0 >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: Caught >>>>>>> unrecoverable exception: >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> startswith first arg must be bytes or a tuple of bytes, not str >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> Traceback (most recent call last): >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewx/drivers/ultimeter.py", line 346, in _decode >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** v >>>>>>> = int(s, 16) >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> ValueError: invalid literal for int() with base 16: b'----' >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> During handling of the above exception, another exception occurred: >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> Traceback (most recent call last): >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewxd", line 157, in main >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> engine.run() >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewx/engine.py", line 208, in run >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> for packet in self.console.genLoopPackets(): >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewx/drivers/ultimeter.py", line 132, in >>>>>>> genLoopPackets >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> data = Station.parse_readings(readings) >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewx/drivers/ultimeter.py", line 317, in >>>>>>> parse_readings >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> data['inHumidity'] = Station._decode(buf[28:32], 0.1) # percent >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> File "/home/weewx/bin/weewx/drivers/ultimeter.py", line 354, in _decode >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> if not s. startswith('--'): >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> TypeError: startswith first arg must be bytes or a tuple of bytes, not >>>>>>> str >>>>>>> Apr 9 16:17:19 weewx weewx[1719] CRITICAL __main__: **** >>>>>>> Exiting. >>>>>>> >>>>>>> On Friday, 9 April 2021 at 16:03:24 UTC+1 tke...@gmail.com wrote: >>>>>>> >>>>>>>> The driver should work fine with Python 3. >>>>>>>> >>>>>>>> However, the driver has a small, non-functional bug that causes it >>>>>>>> to emit that error message when it encounters a "dash" value. It's >>>>>>>> non-functional because it emits the right value ("None"). It just >>>>>>>> shouldn't >>>>>>>> log an error. >>>>>>>> >>>>>>>> Try this version. I've taken the occasion of having a real, live >>>>>>>> Ultimeter user to also fix some ambiguities around byte array types. >>>>>>>> Let me >>>>>>>> know if it works. >>>>>>>> >>>>>>>> -tk >>>>>>>> >>>>>>>> On Fri, Apr 9, 2021 at 7:26 AM Chris Thompstone < >>>>>>>> chris.th...@gmail.com> wrote: >>>>>>>> >>>>>>>>> Oh, the ultimeter isn't supported on Python 3 and ver 4 weewx ? >>>>>>>>> Is that what your saying? >>>>>>>>> Oh, had not planned on that. >>>>>>>>> I found some sort of error in my template, which I've kind of >>>>>>>>> resolved. >>>>>>>>> But the Ultimeter driver... not sure on this one. >>>>>>>>> Thanks >>>>>>>>> Chris >>>>>>>>> >>>>>>>>> On Friday, 9 April 2021 at 15:20:41 UTC+1 peterq...@gmail.com >>>>>>>>> wrote: >>>>>>>>> >>>>>>>>>> Looks like you switched to Python 3 from Python 2.7 and the >>>>>>>>>> driver isn't compatible, based on the error message. >>>>>>>>>> >>>>>>>>>> On Fri, Apr 9, 2021 at 6:56 AM Chris Thompstone < >>>>>>>>>> chris.th...@gmail.com> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Guys, >>>>>>>>>>> I have been trying to upgrade to version 4 since my Pi decided >>>>>>>>>>> to throw a strop. >>>>>>>>>>> Anyway, it's been fairly hardwork. >>>>>>>>>>> >>>>>>>>>>> I have this message continuous in the logs: >>>>>>>>>>> DEBUG weewx.drivers.ultimeter: Decode failed for '----': invalid >>>>>>>>>>> literal for int() with base 16: '----' >>>>>>>>>>> just coming every 1s or so. >>>>>>>>>>> >>>>>>>>>>> I also have some other issue which I can't seem to find: >>>>>>>>>>> ERROR weewx.cheetahgenerator: Generate failed with exception >>>>>>>>>>> '<class 'TypeError'>' >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** Ignoring template /home/weewx/skins/Standard/index.html.tmpl >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** Reason: '>' not supported between instances of 'NoneType' and >>>>>>>>>>> 'float' >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** Traceback (most recent call last): >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** File "/home/weewx/bin/weewx/cheetahgenerator.py", line 326, >>>>>>>>>>> in >>>>>>>>>>> generate >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** unicode_string = compiled_template.respond() >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** File "_home_weewx_skins_Standard_index_html_tmpl.py", line >>>>>>>>>>> 1378, in >>>>>>>>>>> respond >>>>>>>>>>> Apr 9 14:45:16 weewx weewx[868] ERROR weewx.cheetahgenerator: >>>>>>>>>>> **** TypeError: '>' not supported between instances of 'NoneType' >>>>>>>>>>> and >>>>>>>>>>> 'float' >>>>>>>>>>> >>>>>>>>>>> My wind vein has blown off the roof, so wind direction and speed >>>>>>>>>>> are probably both None. >>>>>>>>>>> Whether this is something to do with it I don't know, but I >>>>>>>>>>> can't get the index template generated. >>>>>>>>>>> Thanks >>>>>>>>>>> Chris >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> You received this message because you are subscribed to the >>>>>>>>>>> Google Groups "weewx-user" group. >>>>>>>>>>> To unsubscribe from this group and stop receiving emails from >>>>>>>>>>> it, send an email to weewx-user+...@googlegroups.com. >>>>>>>>>>> To view this discussion on the web visit >>>>>>>>>>> https://groups.google.com/d/msgid/weewx-user/dd2cd2fd-515b-47b3-a8c5-cb1692508eb8n%40googlegroups.com >>>>>>>>>>> <https://groups.google.com/d/msgid/weewx-user/dd2cd2fd-515b-47b3-a8c5-cb1692508eb8n%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>>>>>> . >>>>>>>>>>> >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> Peter Quinn >>>>>>>>>> (415)794-2264 <(415)%20794-2264> >>>>>>>>>> >>>>>>>>> -- >>>>>>>>> You received this message because you are subscribed to the Google >>>>>>>>> Groups "weewx-user" group. >>>>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>>>> send an email to weewx-user+...@googlegroups.com. >>>>>>>>> >>>>>>>> To view this discussion on the web visit >>>>>>>>> https://groups.google.com/d/msgid/weewx-user/c4ef6c55-ddee-4c9e-a7d6-23cc1f5beb2an%40googlegroups.com >>>>>>>>> <https://groups.google.com/d/msgid/weewx-user/c4ef6c55-ddee-4c9e-a7d6-23cc1f5beb2an%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>>>> . >>>>>>>>> >>>>>>>> -- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "weewx-user" group. >>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>> send an email to weewx-user+...@googlegroups.com. >>>>>>> >>>>>> To view this discussion on the web visit >>>>>>> https://groups.google.com/d/msgid/weewx-user/1d9b03d2-037f-4519-88db-292d0dff8d76n%40googlegroups.com >>>>>>> <https://groups.google.com/d/msgid/weewx-user/1d9b03d2-037f-4519-88db-292d0dff8d76n%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>> . >>>>>>> >>>>>> -- >>>>>> -tk >>>>>> >>>>> -- >>>>> You received this message because you are subscribed to the Google >>>>> Groups "weewx-user" group. >>>>> To unsubscribe from this group and stop receiving emails from it, send >>>>> an email to weewx-user+...@googlegroups.com. >>>>> >>>> To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/weewx-user/cfde7b17-3890-467f-a3ab-e612ae625facn%40googlegroups.com >>>>> <https://groups.google.com/d/msgid/weewx-user/cfde7b17-3890-467f-a3ab-e612ae625facn%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>> . >>>>> >>>> -- > You received this message because you are subscribed to the Google Groups > "weewx-user" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to weewx-user+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/weewx-user/b8b762e9-298d-4228-99e0-cae912cc8404n%40googlegroups.com > <https://groups.google.com/d/msgid/weewx-user/b8b762e9-298d-4228-99e0-cae912cc8404n%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- You received this message because you are subscribed to the Google Groups "weewx-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to weewx-user+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-user/CAPq0zEDHXmKXByCWzeJf3WmRprqeTETgJ%3Drzsa2UzbP70irv1w%40mail.gmail.com.
#!/usr/bin/env python # # Copyright 2014-2020 Matthew Wall # Copyright 2014 Nate Bargmann <n...@n0nb.us> # See the file LICENSE.txt for your rights. # # Credit to and contributions from: # Jay Nugent (WB8TKL) and KRK6 for weather-2.kr6k-V2.1 # http://server1.nuge.com/~weather/ # Steve (sesykes71) for testing the first implementations of this driver # Garret Power for improved decoding and proper handling of negative values # Chris Thompstone for testing the fast-read implementation # # Thanks to PeetBros for publishing the communication protocols and details # about each model they manufacture. """Driver for Peet Bros Ultimeter weather stations except the Ultimeter II This driver assumes the Ultimeter is emitting data in Peet Bros Data Logger mode format. This driver will set the mode automatically on stations manufactured after 2004. Stations manufactured before 2004 must be set to data logger mode using the buttons on the console. Resources for the Ultimeter stations Ultimeter Models 2100, 2000, 800, & 100 serial specifications: http://www.peetbros.com/shop/custom.aspx?recid=29 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. The Ultimeter hardware supports several "modes" for providing station data to the serial port. This driver utilizes the "modem mode" to set the date 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) """ from __future__ import with_statement from __future__ import absolute_import from __future__ import print_function import logging import time import serial import weewx.drivers import weewx.wxformulas from weewx.units import INHG_PER_MBAR, MILE_PER_KM from weeutil.weeutil import timestamp_to_string log = logging.getLogger(__name__) DRIVER_NAME = 'Ultimeter' DRIVER_VERSION = '0.5' def loader(config_dict, _): return UltimeterDriver(**config_dict[DRIVER_NAME]) def confeditor_loader(): return UltimeterConfEditor() def _fmt(x): return ' '.join(["%0.2X" % c for c in x]) class UltimeterDriver(weewx.drivers.AbstractDevice): """weewx driver that communicates with a Peet Bros Ultimeter station model: station model, e.g., 'Ultimeter 2000' or 'Ultimeter 100' [Optional. Default is 'Ultimeter'] port - serial port [Required. Default is /dev/ttyUSB0] max_tries - how often to retry serial communication before giving up [Optional. Default is 5] """ def __init__(self, **stn_dict): self.model = stn_dict.get('model', 'Ultimeter') self.port = stn_dict.get('port', Station.DEFAULT_PORT) self.max_tries = int(stn_dict.get('max_tries', 5)) self.retry_wait = int(stn_dict.get('retry_wait', 3)) debug_serial = int(stn_dict.get('debug_serial', 0)) self.last_rain = None log.info('Driver version is %s', DRIVER_VERSION) log.info('Using serial port %s', self.port) self.station = Station(self.port, debug_serial=debug_serial) self.station.open() def closePort(self): if self.station: self.station.close() self.station = None @property def hardware_name(self): return self.model def DISABLED_getTime(self): return self.station.get_time() def DISABLED_setTime(self): self.station.set_time(int(time.time())) def genLoopPackets(self): self.station.set_logger_mode() while True: packet = {'dateTime': int(time.time() + 0.5), 'usUnits': weewx.US} readings = self.station.get_readings_with_retry(self.max_tries, self.retry_wait) data = Station.parse_readings(readings) packet.update(data) self._augment_packet(packet) yield packet def _augment_packet(self, packet): packet['rain'] = weewx.wxformulas.calculate_rain(packet['rain_total'], self.last_rain) self.last_rain = packet['rain_total'] class Station(object): DEFAULT_PORT = '/dev/ttyUSB0' def __init__(self, port, debug_serial=0): self._debug_serial = debug_serial self.port = port self.baudrate = 2400 self.timeout = 3 # seconds self.serial_port = None # setting the year works only for models 2004 and later self.can_set_year = True # modem mode is available only on models 2004 and later # not available on pre-2004 models 50/100/500/700/800 self.has_modem_mode = True def __enter__(self): self.open() return self def __exit__(self, _, value, traceback): self.close() def open(self): log.debug("Open serial port %s", self.port) self.serial_port = serial.Serial(self.port, self.baudrate, timeout=self.timeout) self.serial_port.flushInput() def close(self): if self.serial_port: log.debug("Close serial port %s", self.port) self.serial_port.close() self.serial_port = None def get_time(self): try: self.set_logger_mode() buf = self.get_readings_with_retry() data = Station.parse_readings(buf) d = data['day_of_year'] # seems to start at 0 m = data['minute_of_day'] # 0 is midnight before start of day tt = time.localtime() y = tt.tm_year s = tt.tm_sec ts = time.mktime((y, 1, 1, 0, 0, s, 0, 0, -1)) + d * 86400 + m * 60 log.debug("Station time: day:%s min:%s (%s)", d, m, timestamp_to_string(ts)) return ts except (serial.serialutil.SerialException, weewx.WeeWxIOError) as e: log.error("get_time failed: %s", e) return int(time.time()) def set_time(self, ts): # go to modem mode so we do not get logger chatter self.set_modem_mode() # set time should work on all models tt = time.localtime(ts) cmd = b">A%04d%04d" % (tt.tm_yday - 1, tt.tm_min + tt.tm_hour * 60) log.debug("Set station time to %s (%s)", timestamp_to_string(ts), cmd) self.serial_port.write(b"%s\r" % cmd) # year works only for models 2004 and later if self.can_set_year: cmd = b">U%s" % tt.tm_year log.debug("Set station year to %s (%s)", tt.tm_year, cmd) self.serial_port.write(b"%s\r" % cmd) def set_logger_mode(self): # in logger mode, station sends logger mode records continuously if self._debug_serial: log.debug("Set station to logger mode") self.serial_port.write(b">I\r") def set_modem_mode(self): # setting to modem mode should stop data logger output if self.has_modem_mode: if self._debug_serial: log.debug("Set station to modem mode") self.serial_port.write(b">\r") def get_readings(self): """Read an Ultimeter sentence. Returns: bytearray: A bytearray containing the sentence. """ # Search for the character '!', which marks the beginning of a "sentence": while True: c = self.serial_port.read(1) if c == b'!': break # Save the first '!' ... buf = bytearray(c) # ... then read until we get to a '\r' or '\n' while True: c = self.serial_port.read(1) if c == b'\n' or c == b'\r': # We found a carriage return or newline, so we have the complete sentence. # NB: Because the Ultimeter terminates a sentence with a '\r\n', this will # leave a newline in the buffer. We don't care: it will get skipped over when # we search for the next sentence. break buf += c if self._debug_serial: log.debug("Station said: %s", _fmt(buf)) return buf @staticmethod def validate_string(buf): if len(buf) not in [42, 46, 50]: raise weewx.WeeWxIOError("Unexpected buffer length %d" % len(buf)) if buf[0:2] != b'!!': raise weewx.WeeWxIOError("Unexpected header bytes '%s'" % buf[0:2]) return buf def get_readings_with_retry(self, max_tries=5, retry_wait=3): for ntries in range(max_tries): try: buf = self.get_readings() self.validate_string(buf) return buf except (serial.serialutil.SerialException, weewx.WeeWxIOError) as e: log.info("Failed attempt %d of %d to get readings: %s", ntries + 1, max_tries, e) time.sleep(retry_wait) else: msg = "Max retries (%d) exceeded for readings" % max_tries log.error(msg) raise weewx.RetriesExceeded(msg) @staticmethod def parse_readings(raw): """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 SSSS - wind speed (0.1 kph) 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 kph) "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. Some stations may omit daily_rain or wind_average, so check for those. Args: raw (bytearray): A bytearray containing the sentence. Returns dict: A dictionary containing the data. """ # Convert from bytearray to bytes buf = bytes(raw[2:]) data = dict() data['windSpeed'] = Station._decode(buf[0:4], 0.1 * MILE_PER_KM) # mph data['windDir'] = Station._decode(buf[6:8], 1.411764) # compass deg data['outTemp'] = Station._decode(buf[8:12], 0.1, neg=True) # degree_F data['rain_total'] = Station._decode(buf[12:16], 0.01) # inch data['barometer'] = Station._decode(buf[16:20], 0.1 * INHG_PER_MBAR) # inHg data['inTemp'] = Station._decode(buf[20:24], 0.1, neg=True) # degree_F data['outHumidity'] = Station._decode(buf[24:28], 0.1) # percent data['inHumidity'] = Station._decode(buf[28:32], 0.1) # percent data['day_of_year'] = Station._decode(buf[32:36]) data['minute_of_day'] = Station._decode(buf[36:40]) if len(buf) > 40: data['daily_rain'] = Station._decode(buf[40:44], 0.01) # inch if len(buf) > 44: data['wind_average'] = Station._decode(buf[44:48], 0.1 * MILE_PER_KM) # mph return data @staticmethod def _decode(s, multiplier=None, neg=False): """Decode a byte string. Ultimeter puts dashes in the string when a sensor is not installed. When we get a dashes, or any other non-hex character, return None. Negative values are represented in twos complement format. Only do the check for negative values if requested, since some parameters use the full set of bits (e.g., wind direction) and some do not (e.g., temperature). Args: s (bytes): Encoded value as hexadecimal digits. multiplier (float): Multiply the results by this value. neg (bool): If True, calculate the twos-complement. Returns: float: The decoded value. """ # First check for all dashes. if s == len(s) * b'-': # All bytes are dash values, meaning a non-existent or broken sensor. Return None. return None # Decode the hexadecimal number try: v = int(s, 16) except ValueError as e: log.debug("Decode failed for '%s': %s", s, e) return None # If requested, calculate the twos-complement if neg: bits = 4 * len(s) if v & (1 << (bits - 1)) != 0: v -= (1 << bits) # If requested, scale the number if multiplier is not None: v *= multiplier return v class UltimeterConfEditor(weewx.drivers.AbstractConfEditor): @property def default_stanza(self): return """ [Ultimeter] # This section is for the PeetBros Ultimeter series of weather stations. # Serial port such as /dev/ttyS0, /dev/ttyUSB0, or /dev/cua0 port = %s # The station model, e.g., Ultimeter 2000, Ultimeter 100 model = Ultimeter # The driver to use: driver = weewx.drivers.ultimeter """ % Station.DEFAULT_PORT def prompt_for_settings(self): print("Specify the serial port on which the station is connected, for") print("example: /dev/ttyUSB0 or /dev/ttyS0 or /dev/cua0.") port = self._prompt('port', Station.DEFAULT_PORT) return {'port': port} # define a main entry point for basic testing of the station without weewx # engine and service overhead. invoke this as follows from the weewx root dir: # # PYTHONPATH=bin python bin/weewx/drivers/ultimeter.py if __name__ == '__main__': import optparse import weewx import weeutil.logger usage = """%prog [options] [--help]""" parser = optparse.OptionParser(usage=usage) parser.add_option('--version', dest='version', action='store_true', help='display driver version') parser.add_option('--debug', dest='debug', action='store_true', help='provide additional debug output in log') parser.add_option('--port', dest='port', metavar='PORT', help='serial port to which the station is connected', default=Station.DEFAULT_PORT) (options, args) = parser.parse_args() if options.version: print("ultimeter driver version %s" % DRIVER_VERSION) exit(0) if options.debug: weewx.debug = 1 weeutil.logger.setup('ultimeter', {}) with Station(options.port, debug_serial=options.debug) as station: station.set_logger_mode() while True: print(time.time(), _fmt(station.get_readings()))