Try this version of wxt5x0.py


On Mon, Oct 12, 2020 at 6:01 AM Tom Keffer <[email protected]> wrote:

> It's definitely the driver. It looks like Matthew never finished the port
> to Python 3.
>
> On Mon, Oct 12, 2020 at 2:36 AM Erik Finskas <[email protected]> wrote:
>
>> Hi all.
>>
>> Followed up this thread to fix the WXT5x0 driver from GitHub and got bit
>> forward but got jammed to another issue, apparently something to do with
>> python3 (?)
>> Running Ubuntu server 20.04.1 LTS
>>
>> Startup log looks like this;
>> Oct 12 12:30:51 PusulaServer systemd[1]: Starting LSB: weewx weather
>> system...
>> Oct 12 12:30:51 PusulaServer weewx[4672]:  * Starting weewx weather
>> system weewx
>> Oct 12 12:30:51 PusulaServer weewx[4684] INFO __main__: Initializing
>> weewx version 4.1.1
>> Oct 12 12:30:51 PusulaServer weewx[4684] INFO __main__: Using Python
>> 3.8.5 (default, Jul 28 2020, 12:59:40) #012[GCC 9.3.0]
>> Oct 12 12:30:51 PusulaServer weewx[4684] INFO __main__: Platform
>> Linux-5.4.0-48-generic-x86_64-with-glibc2.29
>> Oct 12 12:30:51 PusulaServer weewx[4684] INFO __main__: Locale is
>> 'en_US.UTF-8'
>> Oct 12 12:30:51 PusulaServer weewx[4684] INFO __main__: PID file is
>> /var/run/weewx.pid
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO __main__: Using
>> configuration file /etc/weewx/weewx.conf
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO __main__: Debug is 0
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO weewx.engine: Loading
>> station type WXT5x0 (user.wxt5x0)
>> Oct 12 12:30:51 PusulaServer weewx[4672]:    ...done.
>> Oct 12 12:30:51 PusulaServer systemd[1]: Started LSB: weewx weather
>> system.
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO user.wxt5x0: driver version
>> is 0.5
>> ...
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO weewx.engine: Starting main
>> packet loop.
>> Oct 12 12:30:51 PusulaServer weewx[4687] INFO weewx.engine: Main loop
>> exiting. Shutting engine down.
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__: Caught
>> unrecoverable exception:
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> unicode strings are not supported, please encode to bytes: '0R0\r\n'
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> Traceback (most recent call last):
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/weewxd", line 154, in main
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> engine.run()
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/weewx/engine.py", line 188, in run
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> for packet in self.console.genLoopPackets():
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/user/wxt5x0.py", line 483, in genLoopPackets
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> raw = self._station.get_composite()
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/user/wxt5x0.py", line 202, in get_composite
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> return self.get_data('R0')
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/user/wxt5x0.py", line 155, in get_data
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> self.send_cmd(cmd)
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/share/weewx/user/wxt5x0.py", line 149, in send_cmd
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> self.device.write(cmd)
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 532, in
>> write
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> d = to_bytes(data)
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> File "/usr/lib/python3/dist-packages/serial/serialutil.py", line 63, in
>> to_bytes
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> raise TypeError('unicode strings are not supported, please encode to bytes:
>> {!r}'.format(seq))
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> TypeError: unicode strings are not supported, please encode to bytes:
>> '0R0\r\n'
>> Oct 12 12:30:51 PusulaServer weewx[4687] CRITICAL __main__:     ****
>> Exiting.
>>
>> Any clues how to get forward? The system works OK with the simulation
>> driver so this might be still to do with the WXT5x0 driver.
>>
>> ..
>> Erik
>>
>> On Monday, 17 August 2020 at 14:13:21 UTC+3 [email protected] wrote:
>>
>>>
>>> Thanks!
>>> That got the driver running.
>>>
>>> I have another hurdle.
>>> I am seeing data from the device but it is in NMEA format.
>>> The driver is set up for serial format.  So my data is not being parsed.
>>>
>>> I see a command/method defined which would
>>> put it in serial mode as a side effect:   set_automatic_mode()
>>> but I don't see that method being called anywhere.
>>> (there is also set_polled_mode() but it has an error and I also don't
>>> see it being used either.)
>>>
>>> I can mangle this up myself... but I thought I should ask: is
>>> it generally considered kosher to do a re-config on the hardware when
>>> weewx starts up?  Or does the user have to sort that out
>>> himself using whatever config tools available to him?
>>>
>>>
>>>
>>>
>>> On Thursday, August 13, 2020 at 11:52:56 PM UTC-5, Glenn McKechnie wrote:
>>>>
>>>> Ah. Thanks Gary, that makes more sense.
>>>>
>>>> @Chris
>>>> Gary's correction has been applied to this attachment
>>>> wxt5x0-trimmed2.py
>>>> and has the following diff...
>>>>
>>>> --
>> 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/fdd27793-8376-40cd-90f0-5cc327cdd307n%40googlegroups.com
>> <https://groups.google.com/d/msgid/weewx-user/fdd27793-8376-40cd-90f0-5cc327cdd307n%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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/weewx-user/CAPq0zEAjRaRMzqaC1FO0B4ZrPW1ieePTwGR3UXWt4S1t4R%2Bidw%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

    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)

DRIVER_NAME = 'WXT5x0'
DRIVER_VERSION = '0.6'

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(byte_str):
    """ This will format raw bytes into a string of space-delimited hex. """
    try:
        # Python 2
        return ' '.join(["%0.2X" % ord(c) for c in byte_str])
    except TypeError:
        # Python 3
        return ' '.join(["%.2X" % c for c in byte_str])


class Station(object):
    def __init__(self, address, port, baud, use_crc=False):
        self.crc_prefix = None
        self.terminator = b''
        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 = b"%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(b'\x00', b'') # eliminate any NULL characters
        return line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @staticmethod
    def calc_crc(txt):
        # We need something that returns integers when iterated over.
        try:
            # Python 2
            byte_iter = [ord(x) for x in txt]
        except TypeError:
            # Python 3
            byte_iter = txt

        crc = 0
        for x in byte_iter:
            crc |= 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
        b'Dn': 'wind_dir_min',
        b'Dm': 'wind_dir_avg',
        b'Dx': 'wind_dir_max',
        b'Sn': 'wind_speed_min',
        b'Sm': 'wind_speed_avg',
        b'Sx': 'wind_speed_max',
        # aR2: pressure, temperature, humidity message
        b'Ta': 'temperature',
        b'Ua': 'humidity',
        b'Pa': 'pressure',
        # aR3: precipitation message
        b'Rc': 'rain',
        b'Rd': 'rain_duration',
        b'Ri': 'rain_intensity',
        b'Hc': 'hail',
        b'Hd': 'hail_duration',
        b'Hi': 'hail_intensity',
        b'Rp': 'rain_intensity_peak',
        b'Hp': 'hail_intensity_peak',
        # dR5: supervisor message
        b'Th': 'heating_temperature',
        b'Vh': 'heating_voltage',
        b'Vs': 'supply_voltage',
        b'Vr': 'reference_voltage',
        b'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(b','):
            cnt = part.count(b'=')
            if cnt == 0:
                # skip the leading identifier 0R0/0R1
                continue
            elif cnt == 1:
                abbr, vstr = part.split(b'=')
                if abbr == b'Id': # skip the information field
                    continue
                obs = Station.OBSERVATIONS.get(abbr)
                if obs:
                    value = None
                    unit = None
                    try:
                        # Get the last character as a byte-string
                        unit = vstr[-1:]
                        if unit != b'#': # '#' 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 units
        obs: a string, such as 'heating_temperature'
        value: float
        unit: a one character long byte-string
        """
        # convert from the indicated units to the weewx METRICWX unit system
        if 'temperature' in obs:
            # [T] temperature C=celsius F=fahrenheit
            if unit == b'C':
                pass # already C
            elif unit == b'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 == b'M':
                pass # already m/s
            elif unit == b'K':
                value *= MPS_PER_KPH
            elif unit == b'S':
                value *= MPS_PER_MPH
            elif unit == b'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 == b'H':
                pass # already hPa/mbar
            elif unit == b'P':
                value *= MBAR_PER_PASCAL
            elif unit == b'B':
                value *= MBAR_PER_BAR
            elif unit == b'M':
                value *= MBAR_PER_MMHG
            elif unit == b'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 == b'M':
                pass # already mm
            elif unit == b'I':
                if 'duration' not in obs:
                    value *= MM_PER_INCH
            elif unit == b'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 == b'M':
                pass # already cm^2
            elif unit == b'I':
                if 'duration' not in obs:
                    value *= CM2_PER_IN2
            elif unit == b'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 = b'\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 = b'\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 = b'!'
        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