pywws.weatherstation

Get data from WH1080/WH3080 compatible weather stations.

Derived from wwsr.c by Michael Pendec (michael.pendec@gmail.com), wwsrdump.c by Svend Skafte (svend@skafte.net), modified by Dave Wells, and other sources.

Introduction

This is the module that actually talks to the weather station base unit. I don’t have much understanding of USB, so copied a lot from Michael Pendec’s C program wwsr.

The weather station memory has two parts: a “fixed block” of 256 bytes and a circular buffer of 65280 bytes. As each weather reading takes 16 bytes the station can store 4080 readings, or 14 days of 5-minute interval readings. (The 3080 type stations store 20 bytes per reading, so store a maximum of 3264.) As data is read in 32-byte chunks, but each weather reading is 16 or 20 bytes, a small cache is used to reduce USB traffic. The caching behaviour can be over-ridden with the unbuffered parameter to get_data and get_raw_data.

Decoding the data is controlled by the static dictionaries _reading_format, lo_fix_format and fixed_format. The keys are names of data items and the values can be an (offset, class, kwds) tuple or another dictionary. So, for example, the _reading_format dictionary entry 'rain' : (13, WSFloat, {'signed': False, 'scale': 0.3}) means that the rain value is an unsigned short (two bytes), 13 bytes from the start of the block, and should be multiplied by 0.3 to get a useful value.

The use of nested dictionaries in the fixed_format dictionary allows useful subsets of data to be decoded. For example, to decode the entire block get_fixed_block is called with no parameters:

ws = pywws.weatherstation.WeatherStation()
print(ws.get_fixed_block())

To get the stored minimum external temperature, get_fixed_block is called with a sequence of keys:

ws = pywws.weatherstation.WeatherStation()
print(ws.get_fixed_block(['min', 'temp_out', 'val']))

Often there is no requirement to read and decode the entire fixed block, as its first 64 bytes contain the most useful data: the interval between stored readings, the buffer address where the current reading is stored, and the current date & time. The get_lo_fix_block method provides easy access to these.

For more examples of using the pywws.weatherstation module, see the pywws.testweatherstation module.

Detailed API

Classes

CUSBDrive() Low level interface to weather station via USB.
DriftingClock(name, status, period, margin)
WSBits
WSDateTime
WSFloat
WSInt
WSStatus
WSTime
WeatherStation([context]) Class that represents the weather station to user program.
class pywws.weatherstation.WSBits[source]

Bases: dict

static from_int(value, keys)[source]
static from_raw(raw, pos, keys=[])[source]
class pywws.weatherstation.WSStatus[source]

Bases: pywws.weatherstation.WSBits

keys = ('bit0', 'bit1', 'bit2', 'bit3', 'bit4', 'bit5', 'lost_connection', 'rain_overflow')
classmethod from_raw(raw, pos)[source]
to_csv()[source]
classmethod from_csv(value)[source]
class pywws.weatherstation.WSInt[source]

Bases: int

static from_1(raw, pos, signed=False)[source]
static wind_dir(raw, pos)[source]
static from_2(raw, pos, signed=False)[source]
static from_3(raw, pos, signed=False)[source]
class pywws.weatherstation.WSFloat[source]

Bases: float

static from_1(raw, pos, signed=False, scale=1.0, nibble_pos=None, nibble_high=False)[source]
static from_2(raw, pos, signed=False, scale=1.0, nibble_pos=None, nibble_high=False)[source]
static from_3(raw, pos, signed=False, scale=1.0)[source]
class pywws.weatherstation.WSTime[source]

Bases: str

static from_raw(raw, pos)[source]
class pywws.weatherstation.WSDateTime[source]

Bases: datetime.datetime

to_csv()[source]
static from_csv(date_string)[source]
static from_raw(raw, pos)[source]
class pywws.weatherstation.CUSBDrive[source]

Bases: object

Low level interface to weather station via USB.

Loosely modeled on a C++ class obtained from http://site.ambientweatherstore.com/easyweather/ws_1080_2080_protocol.zip. I don’t know the provenance of this, but it looks as if it may have come from the manufacturer.

EndMark = 32
ReadCommand = 161
WriteCommand = 160
WriteCommandWord = 162
read_block(address)[source]

Read 32 bytes from the weather station.

If the read fails for any reason, None is returned.

Parameters:address (int) – address to read from.
Returns:the data from the weather station.
Return type:list(int)
write_byte(address, data)[source]

Write a single byte to the weather station.

Parameters:
  • address (int) – address to write to.
  • data (int) – the value to write.
Returns:

success status.

Return type:

bool

class pywws.weatherstation.DriftingClock(name, status, period, margin)[source]

Bases: object

before(now)[source]
avoid()[source]
set_clock(now)[source]
invalidate()[source]
class pywws.weatherstation.WeatherStation(context=None)[source]

Bases: object

Class that represents the weather station to user program.

min_pause = 0.5
margin = 0.9
live_data(logged_only=False)[source]
inc_ptr(ptr)[source]

Get next circular buffer data pointer.

dec_ptr(ptr)[source]

Get previous circular buffer data pointer.

get_raw_data(ptr, unbuffered=False)[source]

Get raw data from circular buffer.

If unbuffered is false then a cached value that was obtained earlier may be returned.

get_data(ptr, unbuffered=False)[source]

Get decoded data from circular buffer.

If unbuffered is false then a cached value that was obtained earlier may be returned.

current_pos()[source]

Get circular buffer location where current data is being written.

get_raw_fixed_block(unbuffered=False)[source]

Get the raw “fixed block” of settings and min/max data.

get_fixed_block(keys=[], unbuffered=False)[source]

Get the decoded “fixed block” of settings and min/max data.

A subset of the entire block can be selected by keys.

write_data(data)[source]

Write a set of single bytes to the weather station. Data must be an array of (ptr, value) pairs.

lo_fix_format = {'alarm_1': (21, <function WSBits.from_raw>, {'keys': ('bit0', 'time', 'wind_dir', 'bit3', 'hum_in_lo', 'hum_in_hi', 'hum_out_lo', 'hum_out_hi')}), 'alarm_2': (22, <function WSBits.from_raw>, {'keys': ('wind_ave', 'wind_gust', 'rain_hour', 'rain_day', 'pressure_abs_lo', 'pressure_abs_hi', 'pressure_rel_lo', 'pressure_rel_hi')}), 'alarm_3': (23, <function WSBits.from_raw>, {'keys': ('temp_in_lo', 'temp_in_hi', 'temp_out_lo', 'temp_out_hi', 'wind_chill_lo', 'wind_chill_hi', 'dew_point_lo', 'dew_point_hi')}), 'current_pos': (30, <function WSInt.from_2>, {'signed': False}), 'data_changed': (26, <function WSInt.from_1>, {'signed': False}), 'data_count': (27, <function WSInt.from_2>, {'signed': False}), 'display_1': (19, <function WSBits.from_raw>, {'keys': ('pressure_rel', 'wind_gust', 'clock_12hr', 'date_mdy', 'time_scale_24', 'show_year', 'show_day_name', 'alarm_time')}), 'display_2': (20, <function WSBits.from_raw>, {'keys': ('temp_out_temp', 'temp_out_chill', 'temp_out_dew', 'rain_hour', 'rain_day', 'rain_week', 'rain_month', 'rain_total')}), 'display_3': (29, <function WSBits.from_raw>, {'keys': ('illuminance_fc', 'alarm_illuminance_hi', 'alarm_uv_hi', 'bit3', 'bit4', 'illuminance_wm2', 'bit6', 'bit7')}), 'read_period': (16, <function WSInt.from_1>, {'signed': False}), 'settings_1': (17, <function WSBits.from_raw>, {'keys': ('temp_in_F', 'temp_out_F', 'rain_in', 'bit3', 'bit4', 'pressure_hPa', 'pressure_inHg', 'pressure_mmHg')}), 'settings_2': (18, <function WSBits.from_raw>, {'keys': ('wind_mps', 'wind_kmph', 'wind_knot', 'wind_mph', 'wind_bft', 'bit5', 'bit6', 'bit7')}), 'timezone': (24, <function WSInt.from_1>, {'signed': True}), 'unknown_01': (25, <function WSInt.from_1>, {'signed': False})}
fixed_format = {'abs_pressure': (34, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1}), 'alarm': {'abs_pressure': {'hi': (68, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1}), 'lo': (70, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'dewpoint': {'hi': (64, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1}), 'lo': (66, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'hum_in': {'hi': (48, <function WSInt.from_1>, {'signed': False}), 'lo': (49, <function WSInt.from_1>, {'signed': False})}, 'hum_out': {'hi': (54, <function WSInt.from_1>, {'signed': False}), 'lo': (55, <function WSInt.from_1>, {'signed': False})}, 'illuminance': (89, <function WSFloat.from_3>, {'signed': False, 'scale': 0.1}), 'rain': {'day': (85, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3}), 'hour': (83, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3})}, 'rel_pressure': {'hi': (72, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1}), 'lo': (74, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'temp_in': {'hi': (50, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1}), 'lo': (52, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'temp_out': {'hi': (56, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1}), 'lo': (58, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'time': (87, <function WSTime.from_raw>, {}), 'uv': (92, <function WSInt.from_1>, {'signed': False}), 'wind_ave': {'bft': (76, <function WSInt.from_1>, {'signed': False}), 'ms': (77, <function WSFloat.from_1>, {'signed': False, 'scale': 0.1})}, 'wind_dir': (82, <function WSInt.wind_dir>, {}), 'wind_gust': {'bft': (79, <function WSInt.from_1>, {'signed': False}), 'ms': (80, <function WSFloat.from_1>, {'signed': False, 'scale': 0.1})}, 'windchill': {'hi': (60, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1}), 'lo': (62, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}}, 'alarm_1': (21, <function WSBits.from_raw>, {'keys': ('bit0', 'time', 'wind_dir', 'bit3', 'hum_in_lo', 'hum_in_hi', 'hum_out_lo', 'hum_out_hi')}), 'alarm_2': (22, <function WSBits.from_raw>, {'keys': ('wind_ave', 'wind_gust', 'rain_hour', 'rain_day', 'pressure_abs_lo', 'pressure_abs_hi', 'pressure_rel_lo', 'pressure_rel_hi')}), 'alarm_3': (23, <function WSBits.from_raw>, {'keys': ('temp_in_lo', 'temp_in_hi', 'temp_out_lo', 'temp_out_hi', 'wind_chill_lo', 'wind_chill_hi', 'dew_point_lo', 'dew_point_hi')}), 'current_pos': (30, <function WSInt.from_2>, {'signed': False}), 'data_changed': (26, <function WSInt.from_1>, {'signed': False}), 'data_count': (27, <function WSInt.from_2>, {'signed': False}), 'date_time': (43, <function WSDateTime.from_raw>, {}), 'display_1': (19, <function WSBits.from_raw>, {'keys': ('pressure_rel', 'wind_gust', 'clock_12hr', 'date_mdy', 'time_scale_24', 'show_year', 'show_day_name', 'alarm_time')}), 'display_2': (20, <function WSBits.from_raw>, {'keys': ('temp_out_temp', 'temp_out_chill', 'temp_out_dew', 'rain_hour', 'rain_day', 'rain_week', 'rain_month', 'rain_total')}), 'display_3': (29, <function WSBits.from_raw>, {'keys': ('illuminance_fc', 'alarm_illuminance_hi', 'alarm_uv_hi', 'bit3', 'bit4', 'illuminance_wm2', 'bit6', 'bit7')}), 'lux_wm2_coeff': (36, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1}), 'magic_0': (0, <function WSInt.from_1>, {'signed': False}), 'magic_1': (1, <function WSInt.from_1>, {'signed': False}), 'max': {'abs_pressure': {'date': (201, <function WSDateTime.from_raw>, {}), 'val': (118, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'dewpoint': {'date': (191, <function WSDateTime.from_raw>, {}), 'val': (114, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'hum_in': {'date': (141, <function WSDateTime.from_raw>, {}), 'val': (98, <function WSInt.from_1>, {'signed': False})}, 'hum_out': {'date': (151, <function WSDateTime.from_raw>, {}), 'val': (100, <function WSInt.from_1>, {'signed': False})}, 'illuminance': {'date': (11, <function WSDateTime.from_raw>, {}), 'val': (94, <function WSFloat.from_3>, {'signed': False, 'scale': 0.1})}, 'rain': {'day': {'date': (236, <function WSDateTime.from_raw>, {}), 'val': (132, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3})}, 'hour': {'date': (231, <function WSDateTime.from_raw>, {}), 'val': (130, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3})}, 'month': {'date': (246, <function WSDateTime.from_raw>, {}), 'val': (136, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3, 'nibble_pos': 140, 'nibble_high': True})}, 'total': {'date': (251, <function WSDateTime.from_raw>, {}), 'val': (138, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3, 'nibble_pos': 140, 'nibble_high': False})}, 'week': {'date': (241, <function WSDateTime.from_raw>, {}), 'val': (134, <function WSFloat.from_2>, {'signed': False, 'scale': 0.3})}}, 'rel_pressure': {'date': (211, <function WSDateTime.from_raw>, {}), 'val': (122, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'temp_in': {'date': (161, <function WSDateTime.from_raw>, {}), 'val': (102, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'temp_out': {'date': (171, <function WSDateTime.from_raw>, {}), 'val': (106, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'uv': {'date': (6, <function WSDateTime.from_raw>, {}), 'val': (93, <function WSInt.from_1>, {'signed': False})}, 'wind_ave': {'date': (221, <function WSDateTime.from_raw>, {}), 'val': (126, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'wind_gust': {'date': (226, <function WSDateTime.from_raw>, {}), 'val': (128, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'windchill': {'date': (181, <function WSDateTime.from_raw>, {}), 'val': (110, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}}, 'min': {'abs_pressure': {'date': (206, <function WSDateTime.from_raw>, {}), 'val': (120, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'dewpoint': {'date': (196, <function WSDateTime.from_raw>, {}), 'val': (116, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'hum_in': {'date': (146, <function WSDateTime.from_raw>, {}), 'val': (99, <function WSInt.from_1>, {'signed': False})}, 'hum_out': {'date': (156, <function WSDateTime.from_raw>, {}), 'val': (101, <function WSInt.from_1>, {'signed': False})}, 'rel_pressure': {'date': (216, <function WSDateTime.from_raw>, {}), 'val': (124, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1})}, 'temp_in': {'date': (166, <function WSDateTime.from_raw>, {}), 'val': (104, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'temp_out': {'date': (176, <function WSDateTime.from_raw>, {}), 'val': (108, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}, 'windchill': {'date': (186, <function WSDateTime.from_raw>, {}), 'val': (112, <function WSFloat.from_2>, {'signed': True, 'scale': 0.1})}}, 'rain_factor_raw': (2, <function WSFloat.from_2>, {'signed': False}), 'read_period': (16, <function WSInt.from_1>, {'signed': False}), 'rel_pressure': (32, <function WSFloat.from_2>, {'signed': False, 'scale': 0.1}), 'settings_1': (17, <function WSBits.from_raw>, {'keys': ('temp_in_F', 'temp_out_F', 'rain_in', 'bit3', 'bit4', 'pressure_hPa', 'pressure_inHg', 'pressure_mmHg')}), 'settings_2': (18, <function WSBits.from_raw>, {'keys': ('wind_mps', 'wind_kmph', 'wind_knot', 'wind_mph', 'wind_bft', 'bit5', 'bit6', 'bit7')}), 'timezone': (24, <function WSInt.from_1>, {'signed': True}), 'unknown_01': (25, <function WSInt.from_1>, {'signed': False}), 'unknown_18': (97, <function WSInt.from_1>, {'signed': False}), 'wind_factor_raw': (4, <function WSFloat.from_2>, {'signed': False})}
data_start = 256
reading_len = {'1080': 16, '3080': 20}

Comments or questions? Please subscribe to the pywws mailing list http://groups.google.com/group/pywws and let us know.