Grrrr - too long since I wrote the code and I made a stupid mistake. The 
two 'loginf' lines should read

loginf('Bad data: length = {0}'.format(len(rxData)))
loginf('Bad data: "{0}"'.format(rxData))

I've attached your file with the correction in it.

Sorry about that.

Susan

-- 
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].
For more options, visit https://groups.google.com/d/optout.
#
#    Copyright (c) 2017 Susan Mackay
#       acknowledging that this driver code originated from and 
#       is structured in a similar way to the Weewx 'Simulator' driver code
#       that carries the following line:
#    Copyright (c) 2009-2015 Tom Keffer <[email protected]>
#
#
"""hp1000 driver for the weewx weather system

This driver communicates with the HP1000/WS1001/XC0422 and whatever other clone
weather stations respond over the LAN to the 'easyweather' protocol.

Communication starts with a UDP IP broadcast on the local subnet to port 6000 to
see if the weather station responds. The broadcast packet is structured as:

Offset  Value           Structure       Comment
0x00    PC2000          8 byte string   Identifies the calling station
0x08    SEARCH          8 byte string   Command
0x10    nulls           24 null bytes   I think there could be structure here but
                                        sending nulls works!
    
(NB: All strings are null padded if required)


The weather station returns a packet structured as:

Offset  Value           Structure       Comment
0x00    HP2000          8 byte string   Name of the seather station
0x08    SEARCH          8 byte string   Command
0x10                    8 byte string   Unknown
0x18                    16 bytes        Not yet deciphered
0x28    text            24 byte string  MAC address of the weather station
0x40    text            16 byte string  IP address of the weather station


The last item is the one that we are after as then then make an TCP connection
to port 6500 on that address for all further communications.

All packets have the same basick structure: the 8-byte (null terminated) sending
device name, the 8 byte command (READ - sent to the weather station requesting
data; WRITE - the response from the wreather station with the requested data)
and a 12-byte sub-command with the type of data to be read/sent. This is followed
by what appears to be random characters but sending nulls does not seem to affect
things.

READ packets are always 40 bytes and WRITE packets vary in length depending on the
data but has a 32-byte header (structured as above).

The weather station will send data using the unit selection in the 'set up' screen
(e.g temperature in degrees Fahrenheit or Celsius). Therefore you need to know what
conversions (if any) are needed to build up the LOOP record. 

The driver follows the (nearly) universal convention and uses the Metric (METRICWX) 
system for the LOOP packet and so will convert al values to SI units.

To find out what units are being sent by the weather staiton, send a "SETUP" command and 
interpret the response as follows (after the 32-byte header):

Offset  Value       Structure   Comment
0x20    unknown     16 bytes    Yet to be deciphered
0x30    Time        1 byte      1 = 'H:mm:ss', 2="h:mm:ss AM", 4='AM h:mm:ss'
0x31    Date        1 byte      16 = 'DD-MM-YYYY', 32 = 'MM-DD-YYYY', 64 = 'YYYY-MM-DD'
0x32    Temperature 1 byte      0 = Celsius, 1 = Fahrenheit
0x33    Pressure    1 byte      0 = hPa, 1 = inHg, 2 = mmHg
0x34    Wind speed  1 byte      0 = m/s, 1 = km/h, 2 = knots, 3 = mph, 4 = Beaufort, 5 = ft/s
0x35    Rainfall    1 byte      0 = mm, 1 = in
0x36    Solar rad   1 byte      0 = lux, 1 = fc, 2=W/m^2
0x37    Rain display1 byte      0 = rain rate, 1 = daily, 2 = weekly, 3 = monthly, 4 = yearly
0x38    Graph time  1 byte      0 = 12h, 1 = 24h, 2 = 48h, 3 = 72h
0x39    Barometer   1 byte      0 = absolute, 1 = relative
0x3a    Weather     1 byte      number
0x3b    Storm       1 byte      number
0x3c    Current     1 byte      0 = sunny, 1 = partly cloudy, 2 = cloudy, 3 = raim. 4 = strom
0x3d    Reset       1 byte      Month for yearly rain reset, 1 = Jan, 2 = Feb...
0x3e    Update      1 byte      Update interval in minutes

Many of these (from offset 0x37 on) are of little interest for the driver byte the 
temperature, pressure, wind speed, rain and solar radiaiton units are all used.


The main data packet that is fetched is the NOWRECORD and it has the following structure
(again after the 32 byte header):

Offset  Value       Structure   Comment
0x20    unknown     8 bytes     Yet to be deciphered
0x28    Wind dir    2 bytes     Wind direction (in degrees from North = 0) [4]
0x2a    inHumidity  1 byte      Inside humidity [5]
0x2b    outHumidity 1 byte      Outside humidity [6]
0x2c    inTemp      4 bytes     Inside temperature (floating point) [7]
0x30    pressure    4 bytes     Relative pressure (floating point) [8]
0x34    barometer   4 bytes     Absolute pressure (floating point) [9]
0x38    outTemp     4 bytes     Outside temperature (floating point) [10]
0x3c    dewPoint    4 bytes     Dew Point tempperature (floating point) [11]
0x40    windChill   4 bytes     Wind Chill temperature (floating point) [12]
0x44    windSpeed   4 bytes     Wind speed (floating point) [13]
0x48    windGust    4 bytes     Wind Gust (floating point) [14]
0x4c    rainRate    4 bytes     Rain rate (floating point) [15]
0x50    dailyRain   4 bytes     Daily rain (floating point) [16]
0x54    weeklyRain  4 bytes     Weekly rain (floating point) [17]
0x58    monthlyRain 4 bytes     Monthly rain (floating point) [18]
0x5c    yearlyRain  4 bytes     Yearly rain (floating point) [19]
0x60    radiation   4 bytes     Current solar radiation(floating point) [20]
0x64    UVI         1 byte      UV Index [21]
0x65                1 bytes     Unknown [22]
0x66                2 bytes     Unkonwn [23]


When the driver starts up, it checks to see if the weather station has data 
available for the time it has been off. Weewx will call the 'genStartupRecords'
funciton passing the timestamp of the last archve record.
There are two main cases to account for:
- this is the first access by Weewx - the timestamp will by None and we need
  to get all of the records available in the weather station
- we are restarting and the passed timestamp contains a valid value - we need
  to find the corresponding record in the weather station
  
The HISTORY_FILE command will return a packet strcutured (after the header) as:

Offset  Value       Structure   Comment
0x20    packetSize  2 bytes     Total length of the packet (including the header)
0x22                2 bytes
0x24                2 bytes
0x26                2 bytes
0x28    Year        16 bytes    8 entries (2 bytes each) with the year containing valid data
0x38    RecCount    32 bytes    8 entries (4 bytes each) with the number of records for the year

The years are in desceding order (e.g. 2017 then 2016 etc.) with 0 for any year without
data. Thre RecCount values correspond to the years (e.g. the first one is for the latest
year, then 2nd one for the previous year etc.)

>From the timestamp of the last known good Weewx record, the driver finds the corresponding 
year index and then the record number. It does a binary search through the history data
records (retrieving one at a time) until it finds a record that "matches".

Matching here is a bit complex in that it is not likely that the Weewx timestamp will
correspond to any weather station record. Therefor the binary search ends when the 
upper and lower indicies converge. This means that we shoud have an index of the
first record to retrieve that will have a timestamp that is not less than the Weewx 
timestamp.

The driver then starts reading HISTORY_DATA records, 100 at a time if possible) from 
the starting index to the last record for the year. It then moves to the next year 
(starting with record index 0) until it runs out of data.

All of this can take a while (my setup seems to take a bit over an hour for each year's
worth of data) and so the last HISTORY_DATA record timestamp is used to repeat the whole
process of reading the archive records. Eventually this will result in no more records
being available and the 'genStartupRecords' fnciton completes and Weewx starts requesting
loop packets.

All HISTORY_DATA records appear to be in SI units without regard for the 'Set up' screen
settings, except for Solar Radiaion which is in Lux.

The HISTORY_DATA records are structured (after the header) as:

Offset  Value       Structure   Comment
0x20    dateTime    8 bytes     Date/time of the record in 100nSec increments since 1/1/1601
0x28    inTemp      2 bytes     Indoor temperature (x10 - 17.3 is passed as 173)
0x2a    inHumidity  2 bytes     Indoor humidity
0x2c    pressure    2 bytes     Absolute pressure (x10)
0x2e    barometer   2 bytes     Relative pressure (x10)
0x30    outTemp     2 bytes     Outdoor tempuerature (x10)
0x32    outHumidity 2 bytes     Outdoor humidity
0x34    dewPoint    2 bytes     Dew point temperature (x10)
0x36    windChill   2 bytes     Wind chill temperature (x10)
0x38    ???         2 bytes     This could be the heat index value but often shown as 0x00ff
0x3a    windSpeed   2 bytes     Wind speed (x10)
0x3c    windGust    2 bytes     Wind Gust (x10)
0x3e    windDir     2 bytes     Wind direction
0x40    RainRate    4 bytes     Rain rate (4 byte integer)
0x44    DailyRain   4 bytes     Daily rain (4 byte integer)
0x48    weeklyRain  4 bytes     Weekly rain (4 byte integer)
0x4c    monthlyRain 4 bytes     Monthly rain (4 byte integer)
0x50    yearlyRain  4 bytes     Yearly Rain (4 byte integer)
0x54    uv          4 bytes     UV (4 byte integer) in uW/cm^2
0x58    radiation   4 bytes     Solar radiation (4 byte integer) in lux X10

"""

import math
import time
import datetime

import weedb
import weewx.drivers
import weeutil.weeutil
from weewx.units import convertStd
from weeutil.weeutil import timestamp_to_string

from signal import signal, SIGPIPE, SIG_DFL

import syslog
import sys
import socket
import struct

DRIVER_NAME = 'HP1000'
DRIVER_VERSION = "1.3"

UDP_BROADCAST_PORT = 6000
TCP_PORT = 6500


def loader(config_dict, engine):
    station = HP1000Driver(**config_dict[DRIVER_NAME])
    return station


def logmsg(level, msg):
    syslog.syslog(level, 'HP1000: %s' % (msg))


def logdbg(msg):
    logmsg(syslog.LOG_DEBUG, msg)


def loginf(msg):
    logmsg(syslog.LOG_ERR, msg)


def logerr(msg):
    logmsg(syslog.LOG_ERR, msg)


class HP1000Driver(weewx.drivers.AbstractDevice):
    """HP1000 Driver"""

    def __init__(self, **stn_dict):
        """Initialize the HP1000 driver"""
        self.ws_name = "HP1000"

        loginf('HP1000 Starting')

        self.internal_test_mode = False
        self.startup_count = 5

        try:
            self.ip_address_mask = stn_dict['ip_address_mask']
            loginf("Using user-defined broadcast mask - %s" % self.ip_address_mask)
        except KeyError, e:
            try:
                import netifaces
                gateway_interface = netifaces.gateways()['default'][netifaces.AF_INET][1]
                self.ip_address_mask = netifaces.ifaddresses(gateway_interface) \
                        [netifaces.AF_INET][0]['broadcast']
                loginf('Using "netifaces" to determine broadcast mask')
            except ImportError:
                self.ip_address_mask = None

        if self.ip_address_mask is None:
            raise Exception(
                "Required parameter 'ip_address_mask' has not been specified or could not be determined")

        # Save the configuration parameters
        self.retry_count = int(stn_dict.get('retry_count', 5))
        self.socket_timeout = float(stn_dict.get('socket_timeout', 5))
        self.loop_delay = float( stn_dict.get('loop_delay', None))
        self.retry_wait = int(stn_dict.get('retry_wait', 5))
        self.max_retry = int(stn_dict.get('max_retry', 3))

        self.last_rain_value = None
        self.last_rain_time = None

        loginf('Address Mask = %s' % self.ip_address_mask)
        loginf('Retry count = %f' % self.retry_count)
        loginf('Socket timeout = %f' % self.socket_timeout)
        if self.loop_delay is None:
            loginf('No loop delay')
        else:
            loginf('Loop delay = %f' % self.loop_delay)
        loginf('Retry Wait = %f' % self.retry_wait)
        loginf('Max Retry = %f' % self.max_retry)

        # Show that we are not connected to a weather station
        self.ws_socket = None

    def string_to_null_padded(self, source, max_length, padd_char='\0'):
        # Create a byte array
        passed_string = source.ljust(max_length, '\0')
        return passed_string

    def create_cmd_string(self, cmd="READ", argument="NOWRECORD"):
        # Create the complete packet
        cmd_packet = '{0:<8s}{1:<8s}{2:<12s}'.format(
            self.string_to_null_padded('PC2000', 8),
            self.string_to_null_padded(cmd, 8),
            self.string_to_null_padded(argument, 12))
        cmd_packet = self.string_to_null_padded(cmd_packet, 40)

        return cmd_packet

    def convert_units(self, source, target):
        """Suppress errors when the conversion cannot occcur
        such as when the source and target unit are the same - it happens a lot
        in this code"""
        try:
            result = convertStd(source, target)
        except:
            result = source
        return result
        
    def connectToWeatherStation(self):
        # Not sure why but this seems to be needed on my Raspberry Pi
        signal(SIGPIPE,SIG_DFL)
        network_retry_count = self.max_retry   # Local network failure retry counter
        
        while self.ws_socket is None:
            # Search for a weather station on the specified subnet
            if not self.internal_test_mode:
                # Broadcast for a weather station
                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                                     socket.IPPROTO_UDP)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
                sock.settimeout(self.socket_timeout)
                bcData = "PC2000\0\0SEARCH\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
                retry_counter = self.retry_count
                sender_addr = None
                while True:
#                        loginf('Sending broadcast request')
                    try:
                        sock.sendto(bcData,
                                    (self.ip_address_mask, UDP_BROADCAST_PORT))
                    except socket.error:
                        network_retry_count -= 1
                        if network_retry_count > 0:
                            # Try accessing the network again after a short break
                            time.sleep(self.retry_wait)
                            break
                        else:
                            # Run out of attempts
                            raise weewx.RetriesExceeded
                    try:
                        # Receive the response form the weather station
                        data, sender_addr = sock.recvfrom(512)
#                            loginf( 'Received ack from {0}'.format(sender_addr))
                        break
                    except socket.timeout:
                        retry_counter -= 1
                        #                            loginf('Timeout')
                        if retry_counter == 0:
                            sender_addr = None
                            loginf('Timed out too many times')
                            break
                    except socket.error:
                        network_retry_count -= 1
                        if network_retry_count > 0:
                            # Try accessing the network again after a short break
                            time.sleep(self.retry_wait)
                            break
                        else:
                            # Run out of attempts
                            raise weewx.RetriesExceeded                        
                    except Exception as e:
                        sender_addr = None
#                            loginf('Unknown error: {0}'.format(e))
                        break
                sock.close()

                # make sure we found something
                if sender_addr is None:
                    continue

                # Get the data sent back by the weather station
                self.ws_name = data[0:8].decode().rstrip('\0')
                self.ws_MAC_address = data[40:64].decode().rstrip('\0')
                self.ws_IP_address = data[64:80].decode().rstrip('\0')

#                    loginf( 'WS Name = %s' % self.ws_name)
#                    loginf( 'MAC Address = %s' % self.ws_MAC_address)
#                    loginf( 'IP Address = %s' % self.ws_IP_address)

                # Connect to the weather station
                self.ws_socket = None
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                sock.settimeout(self.socket_timeout)
                try:
                    sock.bind(("0.0.0.0", TCP_PORT))
                except socket.error:
                    network_retry_count -= 1
                    if network_retry_count > 0:
                        sock.close()
                        # Try accessing the network again after a short break
                        sleep(self.retry_wait)
                        continue
                    else:
                        # Run out of attempts
                        raise weewx.RetriesExceeded

                while True:
                    sock.listen(5)
                    try:
                        # Wait until we are talked to (or timeout)
                        (self.ws_socket, address) = sock.accept()
                        loginf('Connected to address {0}'.format(address))
                        break
                    except socket.timeout:
                        retry_counter -= 1
                        loginf('accept timeout')
                        if retry_counter == 0:
                            self.ws_socket = None
                            break
                    except socket.error:
                        network_retry_count -= 1
                        if network_retry_count > 0:
                            # Try accessing the network again after a short break
                            sleep(self.retry_wait)
                            break
                        else:
                            # Run out of attempts
                            raise weewx.RetriesExceeded
                    except Exception as e:
                        self.ws_socket = None
                        loginf('Listening error: {0}'.format(e))
                        break
                sock.close()

                # Make sure that we are talking to a weather station
                if self.ws_socket is None:
                    loginf('Going around again')
                    continue

                # Now read the SETUP packet to find the current value units
                retry_counter = self.retry_count
                try:
                    self.ws_socket.send(self.create_cmd_string(argument='SETUP'))
                except socket.error:
                    network_retry_count -= 1
                    if network_retry_count > 0:
                        # Try accessign the network again after a short break
                        sleep(self.retry_wait)
                        continue
                    else:
                        # Run out of attempts
                        raise weewx.RetriesExceeded
                try:
                    rxData = self.ws_socket.recv(1024)
                except:
                    self.ws_socket.close()
                    self.ws_socket = None
                    continue

                # Interpret the data
                interp_data = struct.unpack("8s8s8s8s8s15b", rxData)
                # The fields are:
                #   [0] - The device ID
                #   [1] - The command (WRITE)
                #   [2] - The argument (SETUP)
                #   [3] - some part of the command that is echoed
                #   [4] - some response that overlaps the command and is not yet understood
                #   [5] - Time format (1 = 'H:mm:ss', 2 = 'h:mm:ss AM', 4 = 'AM h:mm:ss')
                #   [6] - Date format (16 = 'DD-MM-YYYY', 32 = 'MM-DD-YYYY', 64 = 'YYYY-MM-DD')
                #   [7] - Temperature unit (0 = Celsius, 1 = Fahrenheit)
                #   [8] - Pressure Unit (0 = hPa, 1 = inHg, 2 = mmHg)
                #   [9] - Wind speed (0=m/s, 1=km/h, 2=knot, 3=mph, 4=bft, 5=ft/s)
                #   [10]- Rainfall unit (0 = mm, 1 = in)
                #   [11]- Solar Radiation (0=lux, 1=fc, 2=W/m^2)
                #   [12]- Rain display (0=rain rate,1=daily, 2-weekly, 3=monthly, 4=yearly)
                #   [13]- Graph Time (0=12h, 1=24h, 2=48h, 3=72h)
                #   [14]- Barometer display (0=Abs, 1=Rel)
                #   [15]- Weather Threshold (number)
                #   [16]- Storm Threshold (number)
                #   [17]- Current Weather (0=Sun, 1=partly cloudy, 2=cloudy, 3=rain, 4=storm threshold)
                #   [18]- Rainfall reset month (1=January...)
                #   [19]- Update interval (number, minutes)
                # We are interested in the temperature, pressure, wind speed, rainfall and solar radiation values
                self.temperature_unit = interp_data[7]
                self.pressure_unit = interp_data[8]
                self.wind_unit = interp_data[9]
                self.rain_unit = interp_data[10]
                self.solar_unit = interp_data[11]
                
                # If we get here then we have established contact
                loginf('Established contact at %s' %
                       datetime.datetime.now().strftime('%d/%m/%y %H:%M:%S'))
            else:
                # Internal test mode
                print('HP1000 - Test Mode - Pretending connection has been made')
                self.temperature_unit = 0  # C
                self.pressure_unit = 0  # hPa
                self.wind_unit = 0  # m/s
                self.rain_unit = 0  # mm
                self.solar_unit = 2  # w/m^2
                self.rainfall_amount = 0
                self.max_iterations = 2 * 3 * 6 * 2 * 3 * 2
                self.iteration_count = 0
                self.ws_socket = 1  # Any old junk will do as we don't reference this in test mode


    def genLoopPackets(self):
        network_retry_count = self.max_retry   # Local network failure retry counter
        while True:
            # Make sure we are connected to the weather station
            # If we can't then this will raise an exception
            self.connectToWeatherStation()
            
            # See if we need to delay requesting the packet
            if self.loop_delay is not None:
                time.sleep( self.loop_delay)
            
            # Get current data
            if not self.internal_test_mode:
                try:
                    self.ws_socket.send(self.create_cmd_string(argument='NOWRECORD'))
                except socket.error:
                    network_retry_count -= 1
                    if network_retry_count > 0:
                        # Try accessing the network again after a short break
                        sleep(self.retry_wait)
                        self.ws_socket.close()
                        self.ws_socket = None
                        continue
                    else:
                        # Run out of attempts
                        raise weewx.RetriesExceeded
                try:
                    rxData = self.ws_socket.recv(1024)
                except:
                    self.ws_socket.close()
                    self.ws_socket = None
                    continue
                try:
                    interp_data = struct.unpack("8s8s16s8shbb14fbbh", rxData)
                except:
                    loginf('Bad data: length = {0}'.format(len(rxData)))
                    loginf('Bad data: "{0}"'.format(rxData))
                    continue
            else:
                # Create test mode data
                interp_data = [0] * 25
                # Internal Test Mode
                # interp_data[0] - device ID
                # interp_data[1] - Command
                # interp_data[2] - Argument
                # interp_data[3] - junk (yet to be understood)
                interp_data[4] = 95  # Wind direction in degrees
                interp_data[5] = 49  # Inside humidity in percent
                interp_data[6] = 71  # Outside humidity in percent

                if self.temperature_unit == 0:
                    interp_data[7] = 24.5  # inside temp in C
                    interp_data[10] = 15.9  # outside temp in C
                    interp_data[11] = 7.4  # dewpoint in C
                    interp_data[12] = 15.8  # Windchill in C
                else:
                    interp_data[7] = 24.5 * 9 / 5 + 32  # F
                    interp_data[10] = 15.9 * 9 / 5 + 32
                    interp_data[11] = 7.4 * 9 / 5 + 32
                    interp_data[12] = 15.8 * 9 / 5 + 32

                if self.pressure_unit == 0:
                    interp_data[8] = 1014.3  # Pressure in hPa
                    interp_data[9] = 998.4  # Barometer in hPa
                elif self.pressure_unit == 1:
                    interp_data[8] = 1014.3 * 0.02953  # Pressure in inHg
                    interp_data[9] = 998.4 * 0.02953  # Barometer in inHg
                else:
                    interp_data[8] = 1014.3 * 0.75006156  # Pressure in mmHg
                    interp_data[9] = 998.4 * 0.75006156  # Barometer in mmHg

                if self.wind_unit == 0:
                    interp_data[13] = 1.5  # Wind speed in m/s
                    interp_data[14] = 3.8  # Wind gust in m/s
                elif self.wind_unit == 1:
                    interp_data[13] = 1.5 * 3.6  # Wind speed in km/hr
                    interp_data[14] = 3.8 * 3.6  # Wind gust in km/hr
                elif self.wind_unit == 2:
                    interp_data[13] = 1.5 * 1.94384  # Wind speed in knots
                    interp_data[14] = 3.8 * 1.94384  # Wind gust in knots
                elif self.wind_unit == 3:
                    interp_data[13] = 1.5 * 2.23694  # Wind speed in mph
                    interp_data[14] = 3.8 * 2.23694  # Wind gust in mph
                elif self.wind_unit == 4:
                    # Formula taken from https://en.wikipedia.org/wiki/Beaufort_scale
                    # and inverted for a rough approximation
                    interp_data[13] = int(round(math.pow(1.5 / 0.836, 2.0 / 3.0)))
                    interp_data[14] = int(round(math.pow(3.8 / 0.836, 2.0 / 3.0)))
                else:
                    interp_data[13] = 1.5 * 3.28084  # Wind speed in ft/s
                    interp_data[14] = 3.8 * 3.28084  # Wind gust in fts

                if self.solar_unit == 0:
                    interp_data[20] = 532.7 / 4.02  # Sunlight in Lux
                elif self.solar_unit == 1:
                    interp_data[20] = 532.7 / 0.04358  # Sunlight in fc
                else:
                    interp_data[20] = 532.7  # w/m2

                if self.rain_unit == 0:
                    interp_data[16] = self.rainfall_amount  # Rain in mm
                else:
                    interp_data[16] = self.rainfall_amount / 25.4  # Rain in inches
                self.rainfall_amount += 0.1  # Heavy rain!!! - At least it will register

                interp_data[21] = 3  # Actually the UVI

                print('Temperature Unit: %d' % self.temperature_unit)
                print('   Pressure Unit: %d' % self.pressure_unit)
                print('       Wind Unit: %d' % self.wind_unit)
                print('      Solar Unit: %d' % self.solar_unit)
                print('       Rain Unit: %d' % self.rain_unit)
                # End of loading up the test data
            
            # Build the LOOP packet from the data
            _packet = {'dateTime': int(time.time()), 
                       'usUnits': weewx.METRICWX, 
                       'windDir': None if interp_data[4] == 32767 else interp_data[4],
                       'inHumidity': interp_data[5], 
                       'outHumidity': None if interp_data[6] == 127 else interp_data[6]}

            # For units that are set by the console, convert them to metricwx if necessary
            sourceUnit = ''
            if self.temperature_unit == 0:
                sourceUnit = 'degree_C'
            else:
                sourceUnit = 'degree_F'
            if interp_data[7] == 32767:
                _packet['inTemp'] = None
            else:
                _packet['inTemp'] = self.convert_units((interp_data[7], sourceUnit, 
                                                       'group_temperature'), weewx.METRICWX)[0]
            if interp_data[10] >= 3276:
                _packet['outTemp'] = None
            else:
                _packet['outTemp'] = self.convert_units((interp_data[10], sourceUnit, 
                                                        'group_temperature'), weewx.METRICWX)[0]
            if interp_data[11] >= 3276:
                _packet['dewPoint'] = None
            else:
                _packet['dewPoint'] = self.convert_units((interp_data[11], sourceUnit, 
                                                         'group_temperature'), weewx.METRICWX)[0]
            if interp_data[12] >= 3276:
                _packet['windChill'] = None
            else:
                _packet['windChill'] = self.convert_units((interp_data[12], sourceUnit, 
                                                        'group_temperature'), weewx.METRICWX)[0]

            if self.pressure_unit == 0:
                sourceUnit = 'hPa'
            elif self.pressure_unit == 1:
                sourceUnit = 'inHg'
            else:
                sourceUnit = 'mmHg'
            if interp_data[8] >= 3276:
                _packet['pressure'] = None
            else:
                _packet['pressure'] = self.convert_units((interp_data[8], sourceUnit, 
                                                         'group_pressure'), weewx.METRICWX)[0]
            if interp_data[9] >= 3276:
                _packet['pressure'] = None
            else:
                _packet['barometer'] = self.convert_units((interp_data[9], sourceUnit,
                                                          'group_pressure'), weewx.METRICWX)[0]

            if self.wind_unit == 0:
                sourceUnit = 'meter_per_second'
            elif self.wind_unit == 1:
                sourceUnit = 'km_per_hour'
            elif self.wind_unit == 2:
                sourceUnit = 'knot'
            elif self.wind_unit == 3:
                sourceUnit = 'mile_per_hour'
            elif self.wind_unit == 4:
                loginf('Beaufort Wind Scale Used - Using Approximation')
                interp_data = list(interp_data) # Convert to a list so we can alter the values
                sourceUnit = 'meter_per_second'  # Used so there will be no scale factor applied
                # Formula taken from https://en.wikipedia.org/wiki/Beaufort_scale
                interp_data[13] = 0.836 * math.pow(interp_data[13], 1.5)
                interp_data[14] = 0.836 * math.pow(interp_data[14], 1.5)
            else:
                sourceUnit = 'meter_per_second'  # Used so no scale factor will be applied
                interp_data = list(interp_data) # Convert to a list so we can alter the values
                interp_data[13] *= 0.3048  # ft/s to m/s
                interp_data[14] *= 0.3048
            if interp_data[13] >= 3276:
                _packet['windSpeed'] = None
            else:
                _packet['windSpeed'] = self.convert_units((interp_data[13], sourceUnit, 
                                                          'group_speed'), weewx.METRICWX)[0]
            if interp_data[14] >= 3276:
                _packet['windGust'] = None
            else:
                _packet['windGust'] = self.convert_units((interp_data[14], sourceUnit, 
                                                         'group_speed'), weewx.METRICWX)[0]

            # Weewx does not have a standard for Lux of foot-candles
            if interp_data[20] > 2147480:
                _packet['radiation'] = None
            elif self.solar_unit == 0:
                _packet['radiation'] = interp_data[20] * 4.02  # Lux to w/m2 for sunlight
            elif self.solar_unit == 1:
                _packet['radiation'] = interp_data[20] * 0.04358  # fc to photons *0.199) to w/m2 (0.219)
            else:
                _packet['radiation'] = interp_data[20]
            if interp_data[21] < 0:
                _packet['UV'] = None
            else:
                _packet['UV'] = interp_data[21]  # Actually the UVI

            current_time = datetime.datetime.now()
            if self.last_rain_value is None:
                # Should be the first time through
                _packet['rain'] = None
            else:
                # Regular path
                if interp_data[16] >= 214748367:
                    _packet['rain'] = None
                elif current_time.time() > self.last_rain_time.time() and \
                                interp_data[16] >= self.last_rain_value:
                    # Still in the same day as the previous loop and
                    # the rain value is not lower now than the last reading
                    if self.rain_unit == 0:
                        _packet['rain'] = interp_data[16] - self.last_rain_value
                    else:
                        _packet['rain'] = (interp_data[16] - self.last_rain_value) * 25.4  # in to mm
                else:
                    # We have started a new day or the rain value has (somehow) gone down
                    # without a 'new day reset' in the weather station
                    _packet['rain'] = 0.0
            self.last_rain_value = interp_data[16]
            self.last_rain_time = current_time
            # Leave 'rainRate' to be calculated by wxservice

            if self.internal_test_mode:
                # Increment the various units, wrapping around as necessary
                self.temperature_unit += 1
                if self.temperature_unit > 1:
                    self.temperature_unit = 0
                self.pressure_unit += 1
                if self.pressure_unit > 2:
                    self.pressure_unit = 0
                self.wind_unit += 1
                if self.wind_unit > 5:
                    self.wind_unit = 0
                self.solar_unit += 1
                if self.solar_unit > 2:
                    self.solar_unit = 0
                self.rain_unit += 1
                if self.rain_unit > 1:
                    self.rain_unit = 0

                self.iteration_count += 1
                if self.iteration_count > self.max_iterations:
                    sys.exit()

            yield _packet

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

    def internal_testing(self, new_state=False):
        self.internal_test_mode = new_state
        
    def getHistoryData( self, year, record_count, starting_record):
        try:
            # Build the command packet
            cmd_packet = struct.pack('8s8s16s2i2hi',
                'PC2000', 'READ', 'HISTORY_DATA',
                48, record_count * 60 + 40,
                year, record_count, starting_record)
            self.ws_socket.send(cmd_packet)
            rx_data = self.ws_socket.recv(8192)
        except Exception as e:
            loginf(str(e))
            raise weewx.RetriesExceeded
            
        # Get the length of the full returned packet
        pkt_length = struct.unpack('I', rx_data[32:36])[0]
        while len(rx_data) < pkt_length:
            # the full packet is being send in small chunks
            rx_data += self.ws_socket.recv(8192)
        return rx_data

    def genStartupRecords(self, lastTimestamp):
        #Start by making sure we can access the weather station
        #If we can't then the function will raise an exception
        self.connectToWeatherStation()
        loginf("Retrieving startup records")
        
        while True:            
            # Find out what data is available from the weather station
            try:
                cmd_packet = struct.pack('8s8s16s2i',
                    'PC2000', 'READ', 'HISTORY_FILE',
                    40, 0)
                self.ws_socket.send(cmd_packet)
                rxData = self.ws_socket.recv(1024)
            except:
#                loginf("Error #1")
                raise weewx.RetriesExceeded
            interp_data = struct.unpack('8s8s16s4h8H8I', rxData)
        
            # Look to see if we have data for the year we are interested in
            year_index = None
            epoch = datetime.datetime(1601,1,1)
            if lastTimestamp is None:
                # Special case - we are starting from an empty database
                # Therefore find the first year with data (i.e. the 'year' is not 0)
                year_index = [x for x, y in enumerate(interp_data[7:15],7) if y != 0][-1]
                start_record = 0
                last_rain_value = 0
                last_record_date = None
            else:
                start_date = datetime.datetime.fromtimestamp(lastTimestamp)
                last_record_date = start_date
                for index, ws_year in \
                            reversed(list(enumerate(interp_data[7:15],7))):
                    if ws_year >= start_date.year:
                        # We have data for the required year
                        # Find the date of the first record for this year
                        year_index = index
                        break;
                if year_index is None:
                    # The weather station does not have the requested start year
                    # and all of the data is older - there is nothing we can add
#                    loginf("No more to retrieve")
                    return
            
                # Find the record number of the first record with a date after
                # the start_date using a binary search
                year = interp_data[year_index]
                lower = 0
                upper = interp_data[year_index + 8] - 1  # max index for records held for that year

                while True:
                    if lower == upper:
                        # the lower and upper indicies are the same
                        sample = upper
                        break
                    sample = (upper + lower) // 2
                    # Read the data with the 'sample' record number
                    # This might be a slow way as it takes time for the 
                    # weather station to respond. The alternative is to read
                    # multiple records at a time but in the early stages of the binary
                    # search, the records are a long way apart so there is little gain
                    rec_data = self.getHistoryData( year, 1, sample)
                
                    # Extract the timestamp from the first 4 words
                    # The value is 100nSec since 1/1/1601!!!!
                    record_datetime = struct.unpack('Q', rec_data[40:48])[0] / 10 #uSec
                    record_datetime = epoch + \
                            datetime.timedelta(microseconds=record_datetime)

                    # While we have the data, also record the records daily rain
                    last_rain_value = struct.unpack('i', rec_data[76:80])[0] / 10.0
                
                    if start_date == record_datetime:
                        # The values are the same - return sample index + 1
                        sample += 1
                        break
                    if start_date > record_datetime:
                        if lower == sample:
                            sample += 1     # Go for the next entry
                            break
                        lower = sample + 1
                    else:
                        if upper == sample:
                            break
                        upper = sample
                start_record = sample

            # Sample will be the record number of the first record with the 
            # *NEXT* record to pass back.
            # Exception - if the target date is later than the last record
            # then we are pointed to that last record index
            
            # Check to see if the sample record date is the last history record
            # If it is then there is no point in continuing as we have everything
            if year_index == 7 and start_record == interp_data[year_index + 8] - 1:
                break;

            # Read all records from here to the last record the weather startion has
            # We need to account for crossing year boundaries
            while year_index >= 7:
                year = interp_data[year_index]
                year_record_count = interp_data[year_index + 8]
                record_count = 100
                if record_count + start_record >= year_record_count:
                    record_count = year_record_count - start_record - 1
#                loginf("Retrieving {0} records in year {1} from {2}".format(
#                            record_count, year, start_record))
                if record_count > 0:
                    rec_packet = self.getHistoryData(year, record_count, start_record)
                    rec_index = 40      # Skip the header data
                    base = start_record
                    start_record += record_count
                    while record_count > 0:
                        rec_data = struct.unpack('Q12h7I', 
                                        rec_packet[rec_index:rec_index + 60])
                        # Note: the values in the HISTORY_DATA record always seem to be
                        # metric, regardless of the setup options
                        # Please let me know if this is NOT THE CASE
                        _packet = {'usUnits': weewx.METRICWX}
                        rec_data = list(rec_data)

                        record_datetime = epoch + \
                                datetime.timedelta(microseconds=rec_data[0] / 10.0)
                        _packet['dateTime'] = time.mktime(record_datetime.timetuple())
                        _packet['inTemp'] = None if rec_data[1] == 32767 else rec_data[1] / 10.0
                        _packet['inHumidity'] = None if rec_data[2] == 32767 else rec_data[2]
                        _packet['pressure'] = None if rec_data[3] == 32767 else rec_data[3] / 10.0
                        _packet['barometer'] = None if rec_data[4] == 32767 else rec_data[4] / 10.0
                        _packet['outTemp'] = None if rec_data[5] == 32767 else rec_data[5] / 10.0
                        _packet['outHumidity'] = None if rec_data[6] == 127 else rec_data[6]
                        _packet['dewPoint'] = None if rec_data[7] == 32767 else rec_data[7] / 10.0
                        _packet['windchill'] = None if rec_data[8] == 32767 else rec_data[8] / 10.0
                        # rec_data[9] / 10.0 might be 'heatIndex'
                        _packet['windSpeed'] = None if rec_data[10] == 32767 else rec_data[10] / 10.0
                        _packet['windGust'] = None if rec_data[11] == 32767 else rec_data[11] / 10.0
                        _packet['windDir'] = None if rec_data[12] == 32767 else rec_data[12]
                        # rec_data[13] is the 'rain rate'

                        # Calculate the 'delta rain' since the last record
                        # Reset on change of day
                        rain = None if rec_data[14] == 2147483647 else rec_data[14] / 10.0
                        if last_record_date is None or rain is None or last_rain_value is None:
                            _packet['rain'] = None
                        elif last_record_date.time() > record_datetime.time() or \
                                   last_rain_value > rain:
                            # start of a new day (or other cause for the rain to be lower than before)
                            _packet['rain'] = 0.0
                        else:
                            _packet['rain'] = rain - last_rain_value
                        last_rain_value = rain
                    
                        #rec_data[15] is the 'weekly rain' total
                        #rec_data[16] is the 'monthly rain' total
                        #rec_data[17] is the 'yearly rain' total
                    
                        _packet['UV'] = None if rec_data[18] == 32767 else int(round(rec_data[18] / 250))   # Convert uW/cm2 to UVI
                        _packet['radiation'] = None if rec_data[19] == 2147483647 else rec_data[19] / 1267.0    # 126.7 lux/(w/m^2)
                        _packet['interval'] = 5
                
                        yield _packet
                
                        # Set up for the next record
                        record_count -= 1
                        rec_index += 60
                        last_record_date = record_datetime
        
                # reached the end of this packet
                if start_record >= year_record_count - 1:
                    # Reached the end of the year
                    year_index -= 1
                    start_record = 0

            # Go around again and pick up the history records
            # that have been added since we started
            lastTimestamp = time.mktime(record_datetime.timetuple())
                
                
                
def confeditor_loader():
    return HP1000ConfEditor()


class HP1000ConfEditor(weewx.drivers.AbstractConfEditor):
    @property
    def default_stanza(self):
        return """
[HP1000]
    # This section is for the weewx HP1000 weather station driver

    # The IP address mask to search for a weather station
    # Define this if you DO NOT have the 'netifaces' Python
    # package installed on your computer or you want to 
    # force the driver to use a specific broadcase address
    #ip_address_mask = "10.1.1.255"
    
    # The retry count for getting a response from the weather station
    retry_count = 5
    
    # Socket timeout value (seconds)
    socket_timeout = 5
    
    # Loop delay time (seconds)
    # None or not specified means loop packets are generated as fast as possible
    # (approx every few seconds) and will increase network traffic volume
    loop_delay = 15
    
    # Number of times to try to access the network
    max_retry = 3
    
    # Number of seconds to wait between attempts to access the network
    retry_wait = 5

    # The driver to use:
    driver = user.HP1000
"""


if __name__ == "__main__":
    station = HP1000Driver(retry_count="5", 
                           socket_timeout="5", loop_delay="2")
    station.internal_testing(True)
    print('All of the following should return (approx.) the same values')
    for packet in station.genLoopPackets():
        pass

Reply via email to