Using https://github.com/matthewwall/weewx-wxt5x0/blob/master/bin/user/wxt5x0.py

In the absence of Mathews expertise, try the attached copy of that
original file.
Save yours, replace it with the attached wxt5x0-trimmed.py file (yes, rename it)
Restart weewx.


I'm no expert, so fingers crossed ;-)


It removes the duplicate syslog code between lines 114 and 126
It also imports the threading module to satisfy pyflakes.

ie:-
01:04 PM $ diff -u2r wxt5x0.py wxt5x0-trimmed.py
--- wxt5x0.py   2020-08-14 13:00:54.026076071 +1000
+++ wxt5x0-trimmed.py   2020-08-14 12:50:56.169586563 +1000
@@ -78,4 +78,5 @@
     # Old-style weewx logging
     import syslog
+    import threading

     def logmsg(level, msg):
@@ -112,18 +113,4 @@
     return WXT5x0ConfigurationEditor()

-
-def logmsg(level, msg):
-    syslog.syslog(level, 'wxt5x0: %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 _fmt(x):
     """
@@ -491,5 +478,5 @@
     def hardware_name(self):
         return self._model
-
+
     def genLoopPackets(self):
         while True:




On 14/08/2020, Chris Howard <[email protected]> wrote:
> The driver author is Matthew Wall.  His github site was pretty active until
>
> June and then it has gone quiet.
> With the state of the world these days, it's hard to know what that means.
>
> I hope the guy is healthy
> and lounging on a beach somewhere.
>
> Until his return, is there a document or someone who can help me sort out
> the 3v-4v differences?
>
>
>
> --
> 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 [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/weewx-user/2dbdebb0-3fb8-458c-a2ad-17ae19931a5do%40googlegroups.com.
>


-- 


Cheers
 Glenn

rorpi - read only raspberry pi & various weewx addons
https://github.com/glennmckechnie

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/weewx-user/CAAraAziD_omLWo%3DhpGJgbF4gdScvsiBJzJqTx-3_jP2b9t2Qvg%40mail.gmail.com.
#!/usr/bin/env python
# Copyright 2017 Matthew Wall, all rights reserved
"""
Collect data from Vaisala WXT510 or WXT520 station.

Thanks to Antonis Katsonis for providing a Vaisala WXT520 for development.

http://www.vaisala.com/Vaisala%20Documents/User%20Guides%20and%20Quick%20Ref%20Guides/M210906EN-C.pdf

The WXT520 is available with the following serial communications:
 - RS232: ASCII automatic and polled; NMEA0183 v3; SDI12 v1.3
 - RS485: ASCII automatic and polled; NMEA0183 v3; SDI12 v1.3
 - RS422: ASCII automatic and polled; NMEA0183 v3; SDI12 v1.3
 - SDI12: v1.3 and v1.3 continuous

This driver supports only ASCII communications protocol.

The precipitation sensor measures both rain and hail.  Rain is measured as a
length, e.g., mm or inch (so the area is implied).  Hail is measured as hits
per area, e.g., hits per square cm or hits per square inch.

The precipitation sensor has three modes: precipitation on/off, tipping bucket,
and time based.  In precipitation on/off, the transmitter ends a precipitation
message 10 seconds after the first recognition of precipitation.  Rain duration
increases in 10 second steps.  Precipitation has ended when Ri=0.  This mode is
used for indication of the start and the end of precipitation.

In tipping bucket, the transmitter sends a precipitation message at each unit
increment (0.1mm/0.01 in).  This simulates conventional tipping bucket method.

In time based mode, the transmitter sends a precipitation message in the
intervals defined in the [I] field.  However, in polled protocols the autosend
mode tipping bucket should not be used as in it the resolution of the output
is decreased (quantized to tipping bucket tips).

The precipitation sensor can also operate in polled mode - it sends a precip
message when requested.

The rain counter reset can be manual, automatic, immediate, or limited.

The supervisor message controls error messaging and heater.
"""

# FIXME: test with and without error messages
# FIXME: test with and without crc

# FIXME: need to fix units of introduced observations:
#  rain_total
#  rain_duration
#  rain_intensity_peak
#  hail_duration
#  hail_intensity_peak
# these do not get converted, so LOOP and REC contain mixed units!
# also, REC does not know that rain_total is cumulative, not delta
# note that 'hail' (hits/area) is not cumulative like 'rain_total' (length)

from __future__ import with_statement
import time

import weewx.drivers

try:
    # New-style weewx logging
    import weeutil.logger
    import logging
    log = logging.getLogger(__name__)

    def logdbg(msg):
        log.debug(msg)

    def loginf(msg):
        log.info(msg)

    def logerr(msg):
        log.error(msg)

except ImportError:
    # Old-style weewx logging
    import syslog
    import threading

    def logmsg(level, msg):
        syslog.syslog(level, 'sdr: %s: %s' %
                      (threading.currentThread().getName(), 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)

DRIVER_NAME = 'WXT5x0'
DRIVER_VERSION = '0.5'

MPS_PER_KPH = 0.277778
MPS_PER_MPH = 0.44704
MPS_PER_KNOT = 0.514444
MBAR_PER_PASCAL = 0.01
MBAR_PER_BAR = 1000.0
MBAR_PER_MMHG = 1.33322387415
MBAR_PER_INHG = 33.8639
MM_PER_INCH = 25.4
CM2_PER_IN2 = 6.4516


def loader(config_dict, _):
    return WXT5x0Driver(**config_dict[DRIVER_NAME])

def confeditor_loader():
    return WXT5x0ConfigurationEditor()

def _fmt(x):
    """
    This will format raw bytes into a string of space-delimited hex.  You can
    convert this back to the raw string like this at a python prompt:

    x = "XX XX XX ..."
    ''.join([chr(int(y,16)) for y in x.split(' ')])
    """
    return ' '.join(["%0.2X" % ord(c) for c in x])


class Station(object):
    def __init__(self, address, port, baud, use_crc=False):
        self.crc_prefix = None
        self.terminator = ''
        self.address = address
        self.port = port
        self.baudrate = baud
        self.timeout = 3 # seconds
        self.device = None

    def open(self):
        pass

    def close(self):
        pass

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, _, value, traceback):
        self.close()

    def send_cmd(self, cmd):
        cmd = "%d%s%s" % (self.address, cmd, self.terminator)
        self.device.write(cmd)

    def get_data(self, cmd):
#        if self.crc_prefix:
#            cmd = cmd.replace('R', 'r')
#            cmd = "%sxxx" % self.crc_prefix
        self.send_cmd(cmd)
        line = self.device.readline()
        if line:
            line.replace('\x00', '') # eliminate any NULL characters
        return line

    def get_address(self):
        self.device.write('?%s' % self.terminator)
        return self.device.readline()

    def set_address(self, addr):
        self.send_cmd('A%d' % addr)

    def get_ack(self):
        return self.get_data('')

    def reset(self):
        self.send_cmd('XZ')

    def precip_counter_reset(self):
        self.send_cmd('XZRU')

    def precip_intensity_reset(self):
        self.send_cmd('XZRI')

    def measurement_reset(self):
        self.send_cmd('XZM')

    def set_automatic_mode(self):
        self.send_cmd('XU,M=R')

    def set_polled_mode(self):
        self.send_cmd('XU,M=P')

    def get_wind(self):
        return self.get_data('R1')

    def get_pth(self):
        return self.get_data('R2')

    def get_precip(self):
        return self.get_data('R3')

    def get_supervisor(self):
        return self.get_data('R5')

    def get_composite(self):
        return self.get_data('R0')

    @staticmethod
    def calc_crc(txt):
        crc = 0
        for x in txt:
            crc |= ord(x)
            for cnt in range(1, 9):
                if crc << 16 == 1:
                    crc >>= 1
                    crc |= 0xa001
                else:
                    crc >>= 1
        a = 0x40 | (crc >> 12)
        b = 0x40 | ((crc >> 6) & 0x3f)
        c = 0x40 | (crc & 0x3f)
        return a + b + c

    OBSERVATIONS = {
        # aR1: wind message
        'Dn': 'wind_dir_min',
        'Dm': 'wind_dir_avg',
        'Dx': 'wind_dir_max',
        'Sn': 'wind_speed_min',
        'Sm': 'wind_speed_avg',
        'Sx': 'wind_speed_max',
        # aR2: pressure, temperature, humidity message
        'Ta': 'temperature',
        'Ua': 'humidity',
        'Pa': 'pressure',
        # aR3: precipitation message
        'Rc': 'rain',
        'Rd': 'rain_duration',
        'Ri': 'rain_intensity',
        'Hc': 'hail',
        'Hd': 'hail_duration',
        'Hi': 'hail_intensity',
        'Rp': 'rain_intensity_peak',
        'Hp': 'hail_intensity_peak',
        # dR5: supervisor message
        'Th': 'heating_temperature',
        'Vh': 'heating_voltage',
        'Vs': 'supply_voltage',
        'Vr': 'reference_voltage',
        'Id': 'information',
        }

    @staticmethod
    def parse(raw):
        # 0R0,Dn=000#,Dm=106#,Dx=182#,Sn=1.1#,Sm=4.0#,Sx=6.6#,Ta=16.0C,Ua=50.0P,Pa=1018.1H,Rc=0.00M,Rd=0s,Ri=0.0M,Hc=0.0M,Hd=0s,Hi=0.0M,Rp=0.0M,Hp=0.0M,Th=15.6C,Vh=0.0N,Vs=15.2V,Vr=3.498V,Id=Ant
        # 0R0,Dm=051D,Sm=0.1M,Ta=27.9C,Ua=39.4P,Pa=1003.2H,Rc=0.00M,Th=28.1C,Vh=0.0N
        # here is an unexpected result: no value for Dn!
        # 0R1,Dn=0m=032D,Sm=0.1M,Ta=27.9C,Ua=39.4P,Pa=1003.2H,Rc=0.00M,Th=28.3C,Vh=0.0N

        parsed = dict()
        for part in raw.strip().split(','):
            cnt = part.count('=')
            if cnt == 0:
                # skip the leading identifier 0R0/0R1
                continue
            elif cnt == 1:
                abbr, vstr = part.split('=')
                if abbr == 'Id': # skip the information field
                    continue
                obs = Station.OBSERVATIONS.get(abbr)
                if obs:
                    value = None
                    unit = None
                    try:
                        unit = vstr[-1]
                        if unit != '#': # '#' indicates invalid data
                            value = float(vstr[:-1])
                            value = Station.convert(obs, value, unit)
                    except ValueError as e:
                        logerr("parse failed for %s (%s):%s" % (abbr, vstr, e))
                    parsed[obs] = value
                else:
                    logdbg("unknown sensor %s: %s" % (abbr, vstr))
            else:
                logdbg("skip observation: '%s'" % part)
        return parsed

    @staticmethod
    def convert(obs, value, unit):
        # convert from the indicated units to the weewx METRICWX unit system
        if 'temperature' in obs:
            # [T] temperature C=celsius F=fahrenheit
            if unit == 'C':
                pass # already C
            elif unit == 'F':
                value = (value - 32.0) * 5.0 / 9.0
            else:
                loginf("unknown unit '%s' for %s" % (unit, obs))
        elif 'wind_speed' in obs:
            # [U] speed M=m/s K=km/h S=mph N=knots
            if unit == 'M':
                pass # already m/s
            elif unit == 'K':
                value *= MPS_PER_KPH
            elif unit == 'S':
                value *= MPS_PER_MPH
            elif unit == 'N':
                value *= MPS_PER_KNOT
            else:
                loginf("unknown unit '%s' for %s" % (unit, obs))
        elif 'pressure' in obs:
            # [P] pressure H=hPa P=pascal B=bar M=mmHg I=inHg
            if unit == 'H':
                pass # already hPa/mbar
            elif unit == 'P':
                value *= MBAR_PER_PASCAL
            elif unit == 'B':
                value *= MBAR_PER_BAR
            elif unit == 'M':
                value *= MBAR_PER_MMHG
            elif unit == 'I':
                value *= MBAR_PER_INHG
            else:
                loginf("unknown unit '%s' for %s" % (unit, obs))
        elif 'rain' in obs:
            # rain: accumulation duration intensity intensity_peak
            # [U] precip M=(mm s mm/h) I=(in s in/h)
            if unit == 'M':
                pass # already mm
            elif unit == 'I':
                if 'duration' not in obs:
                    value *= MM_PER_INCH
            elif unit == 's':
                pass # already seconds
            else:
                loginf("unknown unit '%s' for %s" % (unit, obs))
        elif 'hail' in obs:
            # hail: accumulation duration intensity intensity_peak
            # [S] hail M=(hits/cm^2 s hits/cm^2h) I=(hits/in^2 s hits/in^2h)
            #          H=hits
            if unit == 'M':
                pass # already cm^2
            elif unit == 'I':
                if 'duration' not in obs:
                    value *= CM2_PER_IN2
            elif unit == 's':
                pass # already seconds
            else:
                loginf("unknown unit '%s' for %s" % (unit, obs))
        return value


class StationSerial(Station):
    # ASCII over RS232, RS485, and RS422 defaults to 19200, 8, N, 1
    DEFAULT_BAUD = 19200

    def __init__(self, address, port, baud=DEFAULT_BAUD):
        super(StationSerial, self).__init__(address, port, baud)
        self.terminator = '\r\n'
        self.device = None

    def open(self):
        import serial
        logdbg("open serial port %s" % self.port)
        self.device = serial.Serial(
            self.port, self.baudrate, timeout=self.timeout)

    def close(self):
        if self.device is not None:
            logdbg("close serial port %s" % self.port)
            self.device.close()
            self.device = None


class StationNMEA(Station):
    # RS422 NMEA defaults to 4800, 8, N, 1
    DEFAULT_BAUD = 4800

    def __init__(self, address, port, baud=DEFAULT_BAUD):
        super(StationNMEA, self).__init__(address, port, baud)
        self.terminator = '\r\n'
        raise NotImplementedError("NMEA support not implemented")


class StationSDI12(Station):
    # SDI12 defaults to 1200, 7, E, 1
    DEFAULT_BAUD = 1200

    def __init__(self, address, port, baud=DEFAULT_BAUD):
        super(StationSDI12, self).__init__(address, port, baud)
        self.terminator = '!'
        raise NotImplementedError("SDI12 support not implemented")


class WXT5x0ConfigurationEditor(weewx.drivers.AbstractConfEditor):
    @property
    def default_stanza(self):
        return """
[WXT5x0]
    # This section is for Vaisala WXT5x0 stations

    # The station model such as WXT510 or WXT520
    model = WXT520

    # The communication protocol to use, one of serial, nmea, or sdi12
    protocol = serial

    # The port to which the station is connected
    port = /dev/ttyUSB0

    # The device address
    address = 0

    # The driver to use
    driver = user.wxt5x0
"""

    def prompt_for_settings(self):
        print("Specify the model")
        model = self._prompt('model', 'WXT520')
        print("Specify the protocol (serial, nmea, or sdi12)")
        protocol = self._prompt('protocol', 'serial', ['serial', 'nmea', 'sdi12'])
        print("Specify the serial port on which the station is connected, for")
        print("example /dev/ttyUSB0 or /dev/ttyS0.")
        port = self._prompt('port', '/dev/ttyUSB0')
        print("Specify the device address")
        address = self._prompt('address', 0)
        return {'protocol': protocol, 'port': port, 'address': address}


class WXT5x0Driver(weewx.drivers.AbstractDevice):
    STATION = {
        'sdi12': StationSDI12,
        'nmea': StationNMEA,
        'serial': StationSerial,
    }
    DEFAULT_PORT = '/dev/ttyUSB0'

    # map sensor names to schema names
    DEFAULT_MAP = {
        'windDir': 'wind_dir_avg',
        'windSpeed': 'wind_speed_avg',
        'windGustDir': 'wind_dir_max',
        'windGust': 'wind_speed_max',
        'outTemp': 'temperature',
        'outHumidity': 'humidity',
        'pressure': 'pressure',
        'rain_total': 'rain',
        'rainRate': 'rain_intensity',
        'hail': 'hail',
        'hailRate': 'hail_intensity',
        'heatingTemp': 'heating_temperature',
        'heatingVoltage': 'heating_voltage',
        'supplyVoltage': 'supply_voltage',
        'referenceVoltage': 'reference_voltage',
        }

    def __init__(self, **stn_dict):
        loginf('driver version is %s' % DRIVER_VERSION)
        self._model = stn_dict.get('model', 'WXT520')
        self._max_tries = int(stn_dict.get('max_tries', 5))
        self._retry_wait = int(stn_dict.get('retry_wait', 10))
        self._poll_interval = int(stn_dict.get('poll_interval', 1))
        self._sensor_map = dict(WXT5x0Driver.DEFAULT_MAP)
        address = int(stn_dict.get('address', 0))
        protocol = stn_dict.get('protocol', 'serial').lower()
        if protocol not in WXT5x0Driver.STATION:
            raise ValueError("unknown protocol '%s'" % protocol)
        baud = WXT5x0Driver.STATION[protocol].DEFAULT_BAUD
        baud = int(stn_dict.get('baud', baud))
        port = stn_dict.get('port', WXT5x0Driver.DEFAULT_PORT)
        self.last_rain_total = None
        self._station = WXT5x0Driver.STATION.get(protocol)(address, port, baud)
        self._station.open()

    def closePort(self):
        self._station.close()

    @property
    def hardware_name(self):
        return self._model

    def genLoopPackets(self):
        while True:
            for cnt in range(self._max_tries):
                try:
                    raw = self._station.get_composite()
                    logdbg("raw: %s" % _fmt(raw))
                    data = Station.parse(raw)
                    logdbg("parsed: %s" % data)
                    packet = self._data_to_packet(data)
                    logdbg("mapped: %s" % packet)
                    if packet:
                        yield packet
                    break
                except IOError as e:
                    logerr("Failed attempt %d of %d to read data: %s" %
                           (cnt + 1, self._max_tries, e))
                    logdbg("Waiting %d seconds" % self._retry_wait)
                    time.sleep(self._retry_wait)
            else:
                raise weewx.RetriesExceeded("Read failed after %d tries" %
                                            self._max_tries)
            if self._poll_interval:
                time.sleep(self._poll_interval)

    def _data_to_packet(self, data):
        # if there is a mapping to a schema name, use it.  otherwise use the
        # sensor naming native to the hardware.
        packet = dict()
        for name in data:
            obs = name
            for field in self._sensor_map:
                if self._sensor_map[field] == name:
                    obs = field
                    break
            packet[obs] = data[name]
        if packet:
            packet['dateTime'] = int(time.time() + 0.5)
            packet['usUnits'] = weewx.METRICWX
        if 'rain_total' in packet:
            packet['rain'] = self._delta_rain(
                packet['rain_total'], self.last_rain_total)
            self.last_rain_total = packet['rain_total']
        return packet

    @staticmethod
    def _delta_rain(rain, last_rain):
        if last_rain is None:
            loginf("skipping rain measurement of %s: no last rain" % rain)
            return None
        if rain < last_rain:
            loginf("rain counter wraparound detected: new=%s last=%s" %
                   (rain, last_rain))
            return rain
        return rain - last_rain


# 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/user/wxt5x0.py

if __name__ == '__main__':
    import optparse
    import syslog
    usage = """%prog [options] [--debug] [--help]"""
    syslog.openlog('wxt5x0', syslog.LOG_PID | syslog.LOG_CONS)
    syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
    parser = optparse.OptionParser(usage=usage)
    parser.add_option('--version', action='store_true',
                      help='display driver version')
    parser.add_option('--debug', action='store_true',
                      help='display diagnostic information while running')
    parser.add_option('--protocol',
                      help='serial, nmea, or sdi12', default='serial')
    parser.add_option('--port',
                      help='serial port to which the station is connected',
                      default=WXT5x0Driver.DEFAULT_PORT)
    parser.add_option('--baud', type=int,
                      help='baud rate', default=19200)
    parser.add_option('--address', type=int,
                      help='device address', default=0)
    parser.add_option('--poll-interval', metavar='POLL', type=int,
                      help='poll interval, in seconds', default=3)
    parser.add_option('--get-wind',
                      help='get a single wind message')
    parser.add_option('--get-pth',
                      help='get a pressure/temperature/humidity message')
    parser.add_option('--get-precip',
                      help='get a single precipitation message')
    parser.add_option('--get-supervisor',
                      help='get a single supervisor message')
    parser.add_option('--get-composite',
                      help='get a single composite message')
    parser.add_option('--test-crc', metavar='STRING',
                      help='verify the CRC calculation')
    (options, args) = parser.parse_args()

    if options.version:
        print("%s driver version %s" % (DRIVER_NAME, DRIVER_VERSION))
        exit(1)

    if options.debug:
        syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))

    if options.test_crc:
        print("string: '%s'" % options.test_crc)
        print("crc: '%s'" % Station.calc_crc(options.test_crc))
        exit(0)

    if options.protocol == 'serial':
        cls = StationSerial
    elif options.protocol == 'nmea':
        cls = StationNMEA
    elif options.protocol == 'sdi12':
        cls = StationSDI12
    else:
        print("unknown protocol '%s'" % options.protocol)
        exit(1)

    with cls(options.address, options.port, options.baud) as s:
        if options.get_wind:
            print("%s" % s.get_wind().strip())
        elif options.get_pth:
            print("%s" % s.get_pth().strip())
        elif options.get_precip:
            print("%s" % s.get_precipitation().strip())
        elif options.get_supervisor:
            print("%s" % s.get_supervisor().strip())
        elif options.get_composite:
            print("%s" % s.get_composite().strip())
        else:
            while True:
                data = s.get_composite().strip()
                print("%s %s" % (int(time.time()), data))
                parsed = Station.parse(data)
                print("%s" % parsed)
                time.sleep(options.poll_interval)

Reply via email to