Hi Tom, Thanks for your quick reaction (mine took a bit too long).
Good guess, you were right.. With a special treatment for Python2 like if six.PY2: ow.init(self.interface.encode()) else: ow.init(self.interface) It runs on all (Weewx4-python3; Weewx4-python2.7 and Weewx3.9-python2.7) Here is the new version (attached). Jaap Op dinsdag 24 december 2019 01:50:40 UTC+1 schreef Tom Keffer: > > I'm just guessing, but you could try to change line 488 in owfs.py from > this > > ow.init(self.interface) > > to this > > ow.init(self.interface.encode()) > > -tk > > > On Mon, Dec 23, 2019 at 3:46 PM Jaap de Munck <jaapd...@gmail.com > <javascript:>> wrote: > >> Hello, >> >> No problems with Vantage (Pro) and Weewx 4! >> But I also have a 1-wire sensor. OWFS isn't Python3 ready yet, so I tried >> to do that myself. >> The result seemed not so bad. It is running on Python3! >> It's also running on Python2.7: I copied it back to the 'production' >> system running weewx 3.9.2. >> Later the Weewx 4 test environment was switched back to Python2 resulting >> in: >> >> CRITICAL weewx.engine: Caught unrecoverable exception: >> CRITICAL weewx.engine: **** in method 'init', argument 1 of type >> 'char const *' >> CRITICAL weewx.engine: **** Traceback (most recent call last): >> CRITICAL weewx.engine: **** File >> "/home/weewx/bin/weewx/engine.py", line 886, in main >> CRITICAL weewx.engine: **** engine = StdEngine(config_dict) >> CRITICAL weewx.engine: **** File >> "/home/weewx/bin/weewx/engine.py", line 83, in __init__ >> CRITICAL weewx.engine: **** self.loadServices(config_dict) >> CRITICAL weewx.engine: **** File >> "/home/weewx/bin/weewx/engine.py", line 143, in loadServices >> CRITICAL weewx.engine: **** obj = >> weeutil.weeutil.get_object(svc)(self,config_dict) >> CRITICAL weewx.engine: **** File "/home/weewx/bin/user/owfs.py", >> line 488, in __init__ >> CRITICAL weewx.engine: **** ow.init(self.interface) >> CRITICAL weewx.engine: **** File >> "/usr/lib/python2.7/dist-packages/ow/__init__.py", line 220, in init >> CRITICAL weewx.engine: **** if not _OW.init( iface ): >> CRITICAL weewx.engine: **** TypeError: in method 'init', argument 1 >> of type 'char const *' >> CRITICAL weewx.engine: **** Exiting. >> >> The logging (attached) is from a (virtual) Debian10 environment and >> Weewx4 + Simulator driver. But it also occurs with Ubuntu 19.04 and Weewx4 >> + Vantage driver. >> >> Best wishes, >> Jaap >> >> -- >> You received this message because you are subscribed to the Google Groups >> "weewx-development" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to weewx-de...@googlegroups.com <javascript:>. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/weewx-development/a160e60b-57fd-444f-bfb2-7bb9fb139366%40googlegroups.com >> >> <https://groups.google.com/d/msgid/weewx-development/a160e60b-57fd-444f-bfb2-7bb9fb139366%40googlegroups.com?utm_medium=email&utm_source=footer> >> . >> > -- You received this message because you are subscribed to the Google Groups "weewx-development" group. To unsubscribe from this group and stop receiving emails from it, send an email to weewx-development+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-development/679a0365-7b70-4915-b7b7-0bc0f0ad8a83%40googlegroups.com.
#!/usr/bin/python # $Id: owfs.py 1700 2017-08-15 11:02:18Z mwall $ # # Copyright 2013 Matthew Wall # Thanks to Mark Cressey (onewireweewx) and Howard Walter (TAI code). # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # See http://www.gnu.org/licenses/ """Classes and functions for interfacing with one-wire sensors via owfs. This file contains both a weewx driver and a weewx service. Either will read raw data from owfs, then the weewx calibration can be used to adjust raw values as needed. Mapping from one-wire device and attribute to weewx database field is done in the OWFS section of weewx.conf. This module requires the python bindings for owfs. The owfs software itself can be installed if you like, but it is not necessary; the owftpd, owhttpd, and owserver services are not used and do not need to be running. In fact, it is safer to leave those services disabled to ensure that they do not conflict with weewx when it attempts to read one-wire devices. On debian systems, install the python bindings with something like this: sudo apt-get install python-ow Put this file, owfs.py, in the weewx 'user' directory, then modify weewx.conf with something like this: [OWFS] interface = u driver = user.owfs [[sensor_map]] inTemp = /uncached/28.8A071E050000/temperature To use as a driver: [Station] station_type = OWFS To use as a service: [Engine] [[Service]] data_services = user.owfs.OWFSService The service can be bound to LOOP or ARCHIVE. The default binding is archive; when a new archive record is created, this service is invoked to collect data from the one-wire bus. The other option is loop binding; when loop data are collected, this service is invoked to collect data from the one-wire bus. Beware that a slow one-wire bus can adversely affect the performance and behavior of weewx, especially when using loop binding. Use the binding parameter to indicate LOOP or ARCHIVE binding. For example, [OWFS] binding = loop Only numeric sensor values are supported. By default, data from each sensor are treated as gauge. Data types include: gauge - record the value as it is read from the sensor delta - difference between current and last reading average - time average by calculating the delta divided by time period counter - difference between current and last, always increasing By default, the units of the sensors is assumed to be metric. If the sensors have been configured to output in US units, use the unit_system option. For example, [OWFS] unit_system = US The interface indicates where the one-wire devices are attached. The default value is u, which is shorthand for 'usb'. This is the option to use for a DS9490R USB adaptor. Other options include a serial port such as /dev/ttyS0, or remote_system:3003 to get data from a remote host running owserver. For example, [OWFS] interface = /dev/ttyS0 The sensor map is simply a list of database field followed by full path to the desired sensor reading. Only sensor values that can be converted to float are supported at this time. Some devices support caching. To use raw, uncached values, preface the path with /uncached. To find out what devices are actually attached: sudo PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/owfs.py --sensors To display the names of data fields for each sensor, as well as actual data: sudo PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/owfs.py --readings To display the value from a single sensor: sudo PYTHONPATH=bin python bin/user/owfs.py --reading /path/to/sensor Details about the python bindings are at the owfs project on sourceforge: http://owfs.sourceforge.net/owpython.html Example Configurations AAG TAI8515 V3 weather station [OWFS] [[sensor_map]] outTemp = /uncached/XX.YYYYYYYYYYYY/temperature windDir = /uncached/XX.YYYYYYYYYYYY # volt.ALL windSpeed = /uncached/XX.YYYYYYYYYYYY # counters.A [[sensor_type]] windDir = aag_windvane windSpeed = aag_windspeed Hobby-Boards ADS wind instrument, lightning sensor, solar radiation sensor, and rainwise rain instrument [OWFS] [[sensor_map]] inTemp = /XX.YYYYYYYYYYYY/temperature UV = /XX.YYYYYYYYYYYY/UVI/UVI luminosity = /XX.YYYYYYYYYYYY/S3-R1-A/luminosity lightning = /XX.YYYYYYYYYYYY/counters.A rain = /XX.YYYYYYYYYYYY # counters.B windDir = /XX.YYYYYYYYYYYY # VAD windSpeed = /XX.YYYYYYYYYYYY # counters.A [[sensor_type]] lightning = counter rain = rainwise_bucket windDir = ads_windvane windSpeed = ads_windspeed [StdCalibrate] [[Corrections]] radiation = luminosity * 1.730463 Hobby-Boards with Inspeed wind instrument [OWFS] [[sensor_map]] windDir = /XX.YYYYYYYYYYYY # VAD / VDD windSpeed = /XX.YYYYYYYYYYYY # counters.A [[sensor_type]] windDir = inspeed_windvane windSpeed = inspeed_windspeed """ # FIXME: automatically detect each sensor type # FIXME: automatically detect per-sensor units import syslog import time import ow import six import weewx from weewx.drivers import AbstractDevice from weewx.engine import StdService DRIVER_NAME = 'OWFS' DRIVER_VERSION = "0.22" def logmsg(level, msg): syslog.syslog(level, 'owfs: %s' % msg) def logdbg(msg): logmsg(syslog.LOG_DEBUG, msg) def loginf(msg): logmsg(syslog.LOG_INFO, msg) def logerr(msg): logmsg(syslog.LOG_ERR, msg) def get_float(path): if six.PY2: sv = ow.owfs_get(path.encode()) else: sv = ow.owfs_get(path) sv = sv.replace(',','.') v = float(sv) return v def gauge(key, path, last_data, ts): return get_float(path) def delta(key, path, last_data, ts): v = get_float(path) if key in last_data: x = v - last_data.get(key) else: x = None last_data[key] = v return x def counter(key, path, last_data, ts): v = get_float(path) if key in last_data and v >= last_data.get(key): x = v - last_data.get(key) # if x < 0: # maxcnt = 0x10000 # 16-bit counter # if x + maxcnt < 0: # maxcnt = 0x100000000 # 32-bit counter # if x + maxcnt < 0: # maxcnt = 0x10000000000000000 # 64-bit counter # x += maxcnt else: x = None last_data[key] = v return x def average(key, path, last_data, ts): v = get_float(path) if key in last_data: (oldv, oldt) = last_data.get(key) if ts > oldt: x = (v - oldv)/(ts - oldt) else: x = None else: x = None last_data[key] = (v, ts) return x def rainwise_bucket(key, path, last_data, ts): cnt = counter(key, "%s%s" % (path, "/counters.B"), last_data, ts) if cnt is not None: cnt *= 0.0254 # rainwise bucket is 0.01 inches per tip, convert to cm return cnt def ads_windspeed(key, path, last_data, ts): ws = average(key, "%s%s" % (path, "/counters.A"), last_data, ts) if ws is not None: ws *= 2.01168 # convert to kph return ws def ads_winddir(key, path, last_data, ts): """Calculate wind direction for Hobby-Boards ADS wind instrument. Get wind direction from the VAD register fo the DS2438. Formula from the Hobby-Boards ADS anemometer user manual.""" v = get_float('%s%s' % (path, '/VAD')) wdir = None if 2.66 <= v <= 2.72: wdir = 0 # N elif 6.49 <= v <= 6.55: wdir = 22.5 # NNE elif 5.96 <= v <= 6.55: wdir = 45 # NE elif 9.35 <= v <= 9.41: wdir = 67.5 # ENE elif 9.27 <= v <= 9.33: wdir = 90 # E elif 9.50 <= v <= 9.56: wdir = 112.5 # ESE elif 8.48 <= v <= 8.54: wdir = 135 # SE elif 8.98 <= v <= 9.04: wdir = 157.5 # SSE elif 7.57 <= v <= 7.63: wdir = 180 # S elif 7.95 <= v <= 8.01: wdir = 202.5 # SSW elif 4.28 <= v <= 4.34: wdir = 225 # SW elif 4.59 <= v <= 4.65: wdir = 247.5 # WSW elif 0.89 <= v <= 0.95: wdir = 270 # W elif 2.20 <= v <= 2.26: wdir = 292.5 # WNW elif 1.54 <= v <= 1.60: wdir = 315 # NW elif 3.54 <= v <= 3.60: wdir = 337.5 # NNW return wdir def inspeed_windspeed(key, path, last_data, ts): ws = average(key, "%s%s" % (path, "/counters.A"), last_data, ts) if ws is not None: ws *= 4.02336 # convert to kph return ws def inspeed_winddir(key, path, last_data, ts): """Calculate wind direction for Hobby-Boards Inspeed wind instrument. Get wind direction from the VDD and VAD register of DS2438. Formula from the Hobby-Boards Inspeed anemometer user manual.""" vdd = get_float('%s%s' % (path, '/VDD')) vad = get_float('%s%s' % (path, '/VAD')) return (400*(vad-0.05*vdd))/vdd if vdd else None def aag_windspeed(key, path, last_data, ts): ws = average(key, "%s%s" % (path, "/counters.A"), last_data, ts) if ws is not None: ws *= 3.948 / 2 # speed in mph is 2.453 * cnt / 2; convert to kph return ws def aag_winddir(key, path, last_data, ts): """Calculate wind direction for AAG TAI8515 V3 wind instrument. Contributed by Howard Walter, based on oww C implementation.""" w = ow.owfs_get("%s%s" % (path, "/volt.ALL")) wd = w.split(',') wd = [float(x) for x in wd] mx = max(x for x in wd) wd = [x/mx for x in wd] if wd[0] < 0.26: if wd[1] < 0.505: wdir = 11 else: if wd[3] < 0.755: wdir = 13 else: wdir = 12 else: if wd[1] < 0.26: if wd[2] < 0.505: wdir = 9 else: wdir = 10 else: if wd[2] < 0.26: if wd[3] < 0.505: wdir = 7 else: wdir = 8 else: if wd[3] < 0.26: if wd[0] < 0.755: wdir = 5 else: wdir = 6 else: if wd[3] < 0.84: if wd[2] < 0.84: wdir = 15 else: wdir = 14 else: if wd[0] < 0.845: if wd[1] < 0.845: wdir = 3 else: wdir = 4 else: if wd[1] > 0.84: wdir = 0 else: if wd[2] > 0.845: wdir = 2 else: wdir = 1 return 22.5 * wdir SENSOR_TYPES = { 'gauge': gauge, 'delta': delta, 'average': average, 'counter': counter, 'ads_windvane': ads_winddir, 'ads_windspeed': ads_windspeed, 'inspeed_windvane': inspeed_winddir, 'inspeed_windspeed': inspeed_windspeed, 'aag_windvane': aag_winddir, 'aag_windspeed': aag_windspeed, 'rainwise_bucket': rainwise_bucket, } def loader(config_dict, engine): return OWFSDriver(**config_dict['OWFS']) class OWFSDriver(weewx.drivers.AbstractDevice): """Driver for one-wire sensors via owfs.""" def __init__(self, **stn_dict) : """Initialize the driver. interface: Where to find the one-wire sensors. Options include u, /dev/ttyS0 [Required. Default is u (usb)] sensor_map: Associate sensor values with database fields. [Required] sensor_type: Indicate how data should be processed before saving. [Optional. Default is gauge] polling_interval: How often to poll for data, in seconds. [Optional. Default is 10] unit_system: The unit system the data are assumed to be in. Can be one of 'METRIC' or 'US'. This assumes that all sensors are reporting data in the same unit system. [Optional. Default is METRIC] """ self.sensor_map = stn_dict['sensor_map'] self.sensor_type = stn_dict.get('sensor_type', {}) self.interface = stn_dict.get('interface', 'u') self.polling_interval = int(stn_dict.get('polling_interval', 10)) self.unit_system = stn_dict.get('unit_system', 'METRIC').lower() self.last_data = {} self.units = weewx.US if self.unit_system == 'us' else weewx.METRIC loginf('driver version is %s' % DRIVER_VERSION) loginf('interface is %s' % self.interface) loginf('sensor map is %s' % self.sensor_map) loginf('sensor type map is %s' % self.sensor_type) loginf('polling interval is %s' % str(self.polling_interval)) loginf('sensor unit system is %s' % self.unit_system) if six.PY2: ow.init(self.interface) else: ow.init(self.interface.encode()) # open all 1-wire channels on a Hobby Boards 4-channel hub. see: # http://owfs.org/index.php?page=4-channel-hub # ow.owfs_put("%s/hub/branch.BYTE" % hubpath, 15) @property def hardware_name(self): return 'OWFS' def genLoopPackets(self): while True: last_data = dict(self.last_data) p = {'usUnits': self.units, 'dateTime': int(time.time() + 0.5)} for s in self.sensor_map: p[s] = None st = 'gauge' if s in self.sensor_type: st = self.sensor_type[s] if st in SENSOR_TYPES: try: func = SENSOR_TYPES[st] p[s] = func(s, self.sensor_map[s], last_data, p['dateTime']) except (ow.exError, ValueError) as e: logerr("Failed to get sensor data for %s (%s): %s" % (s, st, e)) else: logerr("unknown sensor type '%s' for %s" % (st, s)) self.last_data.update(last_data) yield p time.sleep(self.polling_interval) def closePort(self): ow.finish() class OWFSService(weewx.engine.StdService): """Collect data from one-wire devices via owfs.""" def __init__(self, engine, config_dict): """ interface: Where to find the one-wire sensors. Options include u, /dev/ttyS0 [Required. Default is u (usb)] sensor_map: Associate sensor values with database fields. [Required] sensor_type: Indicates how data should be processed before saving. [Optional. Default is gauge] """ super(OWFSService, self).__init__(engine, config_dict) d = config_dict.get('OWFS', {}) self.sensor_map = d['sensor_map'] self.sensor_type = d.get('sensor_type', {}) self.interface = d.get('interface', 'u') self.unit_system = d.get('unit_system', 'METRIC').lower() self.binding = d.get('binding', 'archive') self.last_data = {} self.units = weewx.US if self.unit_system == 'us' else weewx.METRIC loginf('service version is %s' % DRIVER_VERSION) loginf('binding is %s' % self.binding) loginf('interface is %s' % self.interface) loginf('sensor map is %s' % self.sensor_map) loginf('sensor type map is %s' % self.sensor_type) loginf('sensor unit system is %s' % self.unit_system) if six.PY2: ow.init(self.interface.encode()) else: ow.init(self.interface) if self.binding == 'loop': self.bind(weewx.NEW_LOOP_PACKET, self.handle_new_loop) else: self.bind(weewx.NEW_ARCHIVE_RECORD, self.handle_new_archive) def shutDown(self): ow.finish() def handle_new_loop(self, event): data = self.getData(event.packet) event.packet.update(data) def handle_new_archive(self, event): delta = time.time() - event.record['dateTime'] if delta > event.record['interval'] * 60: logdbg("Skipping record: time difference %s too big" % delta) return data = self.getData(event.record) event.record.update(data) # this implementation assumes that data from one-wire sensors are metric. # if the packets to which we append are something other than metric, then # we do a conversion after we have all the data. def getData(self, packet): last_data = dict(self.last_data) p = {'usUnits': self.units} for s in self.sensor_map: p[s] = None st = 'gauge' if s in self.sensor_type: st = self.sensor_type[s] if st in SENSOR_TYPES: func = SENSOR_TYPES[st] try: p[s] = func(s, self.sensor_map[s], last_data, packet['dateTime']) except (ow.exError, ValueError) as e: logerr("Failed to get onewire data for %s (%s): %s" % (s, st, e)) else: logerr("unknown sensor type '%s' for %s" % (st, s)) self.last_data.update(last_data) if packet['usUnits'] != self.units: p['usUnits'] = self.units converter = weewx.units.StdUnitConverters[packet['usUnits']] p = converter.convertDict(p) if 'usUnits' in p: del p['usUnits'] return p # define a main entry point for basic testing without weewx engine and service # overhead. invoke this as follows from the weewx root dir: # # PYTHONPATH=bin python bin/user/owfs.py if __name__ == '__main__': usage = """%prog [options] [--debug] [--help]""" def main(): import optparse syslog.openlog('wee_owfs', syslog.LOG_PID | syslog.LOG_CONS) 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='display diagnostic information while running') parser.add_option("--iface", dest="iface", type=str, metavar="IFACE", help="specify the interface, e.g., u or /dev/ttyS0") parser.add_option('--sensors', dest='sensors', action='store_true', help='display list attached sensors') parser.add_option('--readings', dest='readings', action='store_true', help='display sensor readings') parser.add_option('--reading',dest='reading',type=str,metavar="SENSOR", help='display output of specified sensor') (options, args) = parser.parse_args() if options.version: print("owfs version %s" % DRIVER_VERSION) exit(1) # default to usb for the interface iface = options.iface if options.iface is not None else 'u' if options.debug is not None: syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) else: syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO)) if options.sensors: ow.init(iface) traverse(ow.Sensor('/'), identify_sensor) elif options.readings: ow.init(iface) traverse(ow.Sensor('/'), display_sensor_info) elif options.reading: ow.init(iface) print('%s: %s' % (options.reading, ow.owfs_get(options.reading))) def identify_sensor(s): print('%s: %s %s' % (s.id, s._path, s._type)) def display_sensor_info(s): print(s.id) display_dict(s.__dict__) def display_dict(d, level=0): for k in d: if isinstance(d[k], dict): display_dict(d[k], level=level+1) elif k == 'alias': pass elif k.startswith('_'): print('%s%s: %s' % (' '*level, k, d[k])) else: v = 'UNKNOWN' try: v = ow.owfs_get(d[k]) except ow.exError as e: v = 'FAIL: %s' % e print('%s%s: %s' % (' '*level, d[k], v)) def traverse(device, func): for s in device.sensors(): if s._type in ['DS2409']: traverse(s, func) else: func(s) if __name__ == '__main__': main()