Hello Peter,
It has been some time since I worked on this, I am not 100% sure to
remember everything correctly and some instructions might be out-dated. All
I can say for sure is that I can currently get the data from the WS3000
without any problem. So here is what I think I did...
First of all, forget the 'usb' version of weewx. I am downloading the
tar.gz from http://www.weewx.com/downloads/ and doing the install using
setup.py as per the instructions here (except for installing the
requirements):
http://www.weewx.com/docs/setup.htm
For the prerequisites, I simply do (just to create a requirement file that
I can reuse later):
echo "configobj
pyephem
pyserial
pyusb
willow
pillow
cheetah
Image
Django==1.11" > requirements.txt
And then:
pip install -r requirements.txt
Download weewx and run (customize home in setup.cfg if you want to change
the default location):
tar xvfz weewx-X.Y.Z.tar.gz
cd weewx-X.Y.Z
./setup.py build
sudo ./setup.py install
=> at this point, weewx should be installed and working (standard install,
you can test the simulator). Now you need to configure it for the HP3000.
For this, I am using my own driver. It might not be very well written, but
it seems to be doing the trick correctly.
But first some IMPORTANT notes:
1. I am using the WS3000 as a secondary station, and I am merging the data
with my primary station using a custom data service. This is optional of
course, and my data service is probably not going to work for anybody but
me: consider it as a poor example and if you would like to do the same make
sure to do all the required changes to ws3000DataService.py.
2. Even if you use the WS3000 on its own, you will probably need to make a
few changes and think carefully about how you want to store the data in the
database: you have up to 8 sensors, and by default weewx has only 3 columns
for temperature and 2 for humidity. So you will probably have to modify the
database schema anyway. Read
http://www.weewx.com/docs/customizing.htm#archive_database carefully.
What my driver is doing by default is storing all the data from the WS3000
in the extraTemp* and extraHumid* columns, you may want to change this
(this can be done in the configuration file). At first you could try to use
the default and existing database columns and only try to get the data from
1 sensor, just to confirm that your installation is working properly.
To install my WS3000 driver:
- copy ws3000.py, ws3000Extensions.py, ws3000DataService.py (optional) to
<weewx home>/bin/user
- update the database schema to add the extra columns (as defined in
ws3000Extensions.py)
- add the following section to your configuration file (doing the
configuration using wee_config should also work I think. Check if
user.ws3000 is listed by./bin/wee_config --list-drivers)
[WS3000]
# This section is for the Ambient Weather WS3000
# The driver to use
driver = user.ws3000
# The station model, e.g., WS3000, WS3000-X3, WS3000-X5 (all the
same...)
model = WS3000
# [Optional] The interval at which loop packets should be generated by the
driver
# Default is 10
#loop_interval = 15
# [Optional] USB vendor ID and product ID, as returned by lsusb. Only
required if the device
# cannot be found with the default values
# Defaults are 0x0483 and 0x5750
#vendor_id = 0x0483
#product_id = 0x5750
# [Optional] By default, all the sensor values are stored in the extraTemp
or extraHumid columns. The assumption here is
# that the WS3000 is used as a secondary station to enhance another
existing station with additional
# temperature sensors, and that the usual inTemp, outTemp, etc. are already
used by the primary station.
# NOTE: of course, the database schema must be modified to include the
missing columns.
#[[sensor_map]]
# extraTemp1 = t_1
# extraTemp2 = t_2
# extraTemp3 = t_3
# extraTemp4 = t_4
# extraTemp5 = t_5
# extraTemp6 = t_6
# extraTemp7 = t_7
# extraTemp8 = t_8
# extraHumid1 = h_1
# extraHumid2 = h_2
# extraHumid3 = h_3
# extraHumid4 = h_4
# extraHumid5 = h_5
# extraHumid6 = h_6
# extraHumid7 = h_7
# extraHumid8 = h_8
And add the following line to the DataBinings section:
[DataBindings]
[[wx_binding]]
...
schema = user.ws3000Extensions.ws3000Schema
I hope that this help. It's far from a step by step install guide, but it
is the best I can do on a short notice... And because of the number of
temp/humidity sensors, some level of customization will be required in any
case, both to store the data and to generate the reports.
On Sunday, 19 August 2018 13:31:57 UTC+2, Peter Grefges wrote:
>
> Hi,
>
> I also have the HP3000 weather station and I spent serveral hours to get
> it to run with weewx, but without sucess :(
>
> I first found the manuals on github for the HP3000, but it actually does
> not work.
> Now after another 5 hours of try n error i found this topic.
>
> @olivier: you posted serveral files here in this topic with drivers and
> so on. Actually i am a Raspberry beginner and I now do not know what to do
> with these drivers and files. Where do I have to put them, which commands
> do I have to execute?
> Which installation method for weewx I have to use?
>
> At the moment I have installed it like on the github homepage written:
> git clone https://github.com/weewx/weewx
> cd weewx
> git checkout usb
> ./setup.py install
>
> I have now folders from weewx at /home/weewx and there also the html site
> is created, at /home/weewx/public_html
> But I also have a weewx folder at /root/weewx
>
>
> For me as a raspberry beginner it is just overwhelming, there are too much
> ways to realise one and the same thing...
>
> Maybe somebody can help me? That would be really nice!
>
> best regards
>
--
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.
#
#
"""Classes and functions for interfacing with an Ambient Weather WS-3000
station.
The following references were useful for developing this driver. More than simply useful,
in fact, since a lot of material has been directly reused:
>From Matthew Wall:
https://github.com/matthewwall/weewx-hp3000
>From Tom Keffer, the WMR100 driver for weewx
http://www.weewx.com
NOTE: the HP3000 driver developed by Matthew Wall should also be working
for the WS-3000 station. But various issues led me to rewrite a new driver
on the model of the one for the WMR100. One benefit is that this driver will
work with the "default" version of weewx and doesn't require the usb branch.
"""
import time
import syslog
import usb.core
import usb.util
import sys
import traceback
import usb
import weewx.drivers
import weewx.wxformulas
DRIVER_NAME = 'WS3000'
DRIVER_VERSION = "0.1"
def loader(config_dict, engine):
return WS3000(**config_dict[DRIVER_NAME])
def confeditor_loader():
return WS3000ConfEditor()
def logmsg(level, msg):
syslog.syslog(level, 'ws3000: %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 tohex(buf):
"""Helper function used to print a byte array in hex format"""
if buf:
return "%s (len=%s)" % (' '.join(["%02x" % x for x in buf]), len(buf))
return ''
class WS3000(weewx.drivers.AbstractDevice):
"""Driver for the WS3000 station."""
DEFAULT_MAP = {
'extraTemp1': 't_1',
'extraTemp2': 't_2',
'extraTemp3': 't_3',
'extraTemp4': 't_4',
'extraTemp5': 't_5',
'extraTemp6': 't_6',
'extraTemp7': 't_7',
'extraTemp8': 't_8',
'extraHumid1': 'h_1',
'extraHumid2': 'h_2',
'extraHumid3': 'h_3',
'extraHumid4': 'h_4',
'extraHumid5': 'h_5',
'extraHumid6': 'h_6',
'extraHumid7': 'h_7',
'extraHumid8': 'h_8'}
COMMANDS = {
'sensor_values': 0x03,
'calibration_values': 0x05,
'interval_value': 0x41,
'unknown': 0x06,
'temp_alarm_configuration': 0x08,
'humidity_alarm_configuration': 0x09,
'device_configuration': 0x04
}
def __init__(self, **stn_dict):
"""Initialize an object of type WS3000.
NAMED ARGUMENTS:
model: Which station model is this?
[Optional. Default is 'WS3000']
timeout: How long to wait, in seconds, before giving up on a response
from the USB port.
[Optional. Default is 15 seconds]
wait_before_retry: How long to wait before retrying.
[Optional. Default is 5 seconds]
max_tries: How many times to try before giving up.
[Optional. Default is 3]
vendor_id: The USB vendor ID for the WS3000
[Optional. Default is 0x0483]
product_id: The USB product ID for the WS3000
[Optional. Default is 0xca01]
interface: The USB interface
[Optional. Default is 0]
loop_interval: The time (in seconds) between emitting LOOP packets.
[Optional. Default is 10]
packet_size: The size of the data fetched from the WS3000 during each read.
[Optional. Default is 64 (0x40)]
mode: Can be 'simulation' or 'hardware'.
[Optional. Default is hardware]
"""
# The following variables will in fact be fetched from the device itself.
# There are anyway declared here with the usual values for the WS3000.
self.IN_ep = 0x82
self.OUT_ep = 0x1
loginf('driver version is %s' % DRIVER_VERSION)
self.model = stn_dict.get('model', 'WS3000')
self.record_generation = stn_dict.get('record_generation', 'software')
self.timeout = float(stn_dict.get('timeout', 15.0))
self.wait_before_retry = float(stn_dict.get('wait_before_retry', 5.0))
self.max_tries = int(stn_dict.get('max_tries', 3))
self.loop_interval = int(stn_dict.get('loop_interval', 10))
self.vendor_id = int(stn_dict.get('vendor_id', '0x0483'), 0)
self.product_id = int(stn_dict.get('product_id', '0x5750'), 0)
self.interface = int(stn_dict.get('interface', 0))
self.packet_size = int(stn_dict.get('packet_size', 64)) # 0x40
self.mode = stn_dict.get('mode', 'hardware')
self.sensor_map = dict(self.DEFAULT_MAP)
if 'sensor_map' in stn_dict:
self.sensor_map.update(stn_dict['sensor_map'])
loginf('sensor map is %s' % self.sensor_map)
self.device = None
self.observations = {}
self.openPort()
def openPort(self):
"""Establish a connection to the WS3000"""
if self.mode == 'simulation':
from weewx.drivers.simulator import Observation
from random import uniform
start = time.time()
for key in self.sensor_map:
if "temp" in key.lower():
self.observations[key] = Observation(magnitude=uniform(4, 8),
average=uniform(18, 22),
period=uniform(22, 26),
phase_lag=uniform(6, 18),
start=start)
elif "humid" in key.lower():
self.observations[key] = Observation(magnitude=uniform(2, 20),
average=uniform(40, 60),
period=uniform(22, 26),
phase_lag=uniform(6, 18),
start=start)
return
# try to find the device using the vend and product id
self.device = self._find_device()
if not self.device:
logerr("Unable to find USB device (0x%04x, 0x%04x)" %
(self.vendor_id, self.product_id))
raise weewx.WeeWxIOError("Unable to find USB device")
for line in str(self.device).splitlines():
logdbg(line)
# reset device, required if it was previously left in a 'bad' state
self.device.reset()
# Detach any interfaces claimed by the kernel
# if self.device.is_kernel_driver_active(self.interface):
# print("Detaching kernel driver")
# self.device.detach_kernel_driver(self.interface)
# FIX: is_kernel_driver_active is not working on all systems, the solution
# below should work in those cases.
try:
self.device.detach_kernel_driver(self.interface)
except:
pass
# get the interface and IN and OUT end points
self.device.set_configuration()
configuration = self.device.get_active_configuration()
self.interface = usb.util.find_descriptor(
configuration, bInterfaceNumber=self.interface
)
self.OUT_ep = usb.util.find_descriptor(
self.interface,
# match the first OUT endpoint
custom_match=lambda eo: \
usb.util.endpoint_direction(eo.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
self.IN_ep = usb.util.find_descriptor(
self.interface,
# match the first OUT endpoint
custom_match=lambda ei: \
usb.util.endpoint_direction(ei.bEndpointAddress) == \
usb.util.ENDPOINT_IN)
# The following is normally not required... could be removed?
try:
usb.util.claim_interface(self.device, self.interface)
except usb.USBError, e:
self.closePort()
logerr("Unable to claim USB interface: %s" % e)
raise weewx.WeeWxIOError(e)
def closePort(self):
"""Tries to ensure that the device will be properly 'unclaimed' by the driver"""
if self.mode == 'simulation':
return
try:
usb.util.dispose_resources(self.device)
except usb.USBError:
try:
self.device.reset()
except:
pass
def getCurrentValues(self):
"""Function that only returns the current sensors data.
Should be used by a data service that will add temperature data to an existing packet, for
example, since a single measurement would be required in such a case."""
if self.mode == 'simulation':
current_time = time.time() + 0.5
new_packet = {'dateTime': int(current_time), 'usUnits': weewx.METRICWX}
for x in self.observations:
new_packet[x] = self.observations[x].value_at(current_time)
return new_packet
nberrors = 0
while nberrors < self.max_tries:
# Get a stream of raw packets, then convert them
try:
read_sensors_command = self.COMMANDS['sensor_values']
raw_data = self._get_raw_data(read_sensors_command)
#
if not raw_data: # empty record
raise weewx.WeeWxIOError("Failed to get any data from the station")
formatted_data = self._raw_to_data(raw_data, read_sensors_command)
logdbg('data: %s' % formatted_data)
new_packet = self._data_to_wxpacket(formatted_data)
logdbg('packet: %s' % new_packet)
return new_packet
except usb.USBError, weewx.WeeWxIOError:
print "An error occurred while generating loop packets:", sys.exc_info()
nberrors += 1
# The driver seem to 'loose' connectivity with the station from time to time.
# Trying to close/reopen the USB port to fix the problem.
self.closePort()
self.openPort()
time.sleep(self.wait_before_retry)
logerr("Max retries exceeded while fetching USB reports")
traceback.print_exc(file=sys.stdout)
raise weewx.RetriesExceeded("Max retries exceeded while fetching USB reports")
def genLoopPackets(self):
"""Generator function that continuously returns loop packets"""
try:
while True:
loop_packet = self.getCurrentValues()
yield loop_packet
time.sleep(self.loop_interval)
except GeneratorExit:
pass
# def genPackets(self):
# TODO
@property
def hardware_name(self):
return self.model
# ===============================================================================
# USB functions
# ===============================================================================
def _find_device(self):
"""Find the given vendor and product IDs on the USB bus"""
device = usb.core.find(idVendor=self.vendor_id, idProduct=self.product_id)
return device
def _write_usb(self, buf):
logdbg("write: %s" % tohex(buf))
return self.device.write(self.OUT_ep, data=buf, timeout=100)
def _read_usb(self):
logdbg("reading " + str(self.packet_size) + " bytes")
buf = self.device.read(self.IN_ep, self.packet_size, 100)
if not buf:
return None
logdbg("read: %s" % tohex(buf))
if len(buf) != 64:
logdbg('read: bad buffer length: %s != 64' % len(buf))
return None
if buf[0] != 0x7b:
logdbg('read: bad first byte: 0x%02x != 0x7b' % buf[0])
return None
idx = None
for i in range(0, len(buf) - 1):
if buf[i] == 0x40 and buf[i + 1] == 0x7d:
idx = i
break
if idx is None:
logdbg('read: no terminating bytes in buffer: %s' % tohex(buf))
return None
return buf[0: idx + 2]
# =========================================================================
# LOOP packet related functions
# ==========================================================================
def _get_cmd_name(self, hex_command):
return self.COMMANDS.keys()[self.COMMANDS.values().index(hex_command)]
def _get_raw_data(self, hex_command=COMMANDS['sensor_values']):
"""Get a sequence of bytes from the console."""
sequence = [0x7b, hex_command, 0x40, 0x7d]
try:
logdbg("sending request for " + self._get_cmd_name(hex_command))
self._write_usb(sequence)
logdbg("reading results...")
buf = self._read_usb()
return buf
except:
print "An error occurred:", sys.exc_info()
traceback.print_exc(file=sys.stdout)
raise weewx.IO_ERROR("Error while fetching " + self._get_cmd_name(hex_command))
def _raw_to_data(self, buf, hex_command=COMMANDS['sensor_values']):
"""Convert the raw bytes sent by the console to human readable values."""
# TODO: implement conversion of other type of data (configuration, calibration, etc.)
# TODO: even if this probably won't be very useful for weewx.
logdbg("extracting values for " + self._get_cmd_name(hex_command))
logdbg("raw: %s" % buf)
record = dict()
if not buf:
return record
if hex_command == self.COMMANDS['sensor_values']:
if len(buf) != 27:
raise weewx.WeeWxIOError("Incorrect buffer length, failed to read " + self._get_cmd_name(hex_command))
record['type'] = self._get_cmd_name(hex_command)
for ch in range(8):
idx = 1 + ch * 3
if buf[idx] != 0x7f and buf[idx + 1] != 0xff:
record['t_%s' % (ch + 1)] = (buf[idx] * 256 + buf[idx + 1]) / 10.0
if buf[idx + 2] != 0xff:
record['h_%s' % (ch + 1)] = buf[idx + 2]
else:
logdbg("unknown data: %s" % tohex(buf))
return record
def _data_to_wxpacket(self, station_data):
# prepare the packet for weewx (map sensor data to database fields)
new_packet = {'dateTime': int(time.time() + 0.5), 'usUnits': weewx.METRICWX}
for x in self.sensor_map:
if self.sensor_map[x] in station_data:
new_packet[x] = station_data[self.sensor_map[x]]
return new_packet
class WS3000ConfEditor(weewx.drivers.AbstractConfEditor):
@property
def default_stanza(self):
return """
[WS3000]
# This section is for the Ambient Weather WS3000
# The driver to use
driver = weewx.drivers.WS3000
# [Optional] Fetch data from the console or generate it
# Useful to test without a console plugged in
# Values are: 'hardware' or 'simulation'
# mode = simulation
# The station model, e.g., WS3000, WS3000-X3, WS3000-X5 (all the same...)
model = WS3000
# [Optional] The interval at which loop packets should be generated by the driver
# Default is 10
loop_interval = 30
# [Optional] USB vendor ID and product ID, as returned by lsusb. Only required if the device
# cannot be found with the default values
# Defaults are 0x0483 and 0x5750
vendor_id = 0x0483
product_id = 0x5750
# [Optional] By default, all the sensor values are stored in the extraTemp or extraHumid columns.
# The assumption here is that the WS3000 is used as a secondary station used
# to enhance another existing station with additional temperature sensors,
# and that the usual inTemp, outTemp, etc. are already used by the primary station.
# NOTE: of course, the database schema must be modified to include the missing columns.
[[sensor_map]]
extraTemp1 = t_1
extraTemp2 = t_2
extraTemp3 = t_3
extraTemp4 = t_4
extraTemp5 = t_5
extraTemp6 = t_6
extraTemp7 = t_7
extraTemp8 = t_8
extraHumid1 = h_1
extraHumid2 = h_2
extraHumid3 = h_3
extraHumid4 = h_4
extraHumid5 = h_5
extraHumid6 = h_6
extraHumid7 = h_7
extraHumid8 = h_8
"""
def modify_config(self, config_dict):
print """
Changing the schema to include extraTemp and extraHumid colums """
config_dict['DataBindings']['wx_binding']['schema'] = 'user.ws3000Extensions.ws3000Schema'
#
# *******************************************************************
#
# define a main entry point for basic testing of the station.
# Invoke this as follows from the weewx root dir:
#
# PYTHONPATH=bin python bin/user/ws3000.py
if __name__ == '__main__':
import optparse
usage = """%prog [options] [--debug] [--help]"""
syslog.openlog('ws3000', 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('--test', default='station',
help='what to test: station or driver')
(options, args) = parser.parse_args()
if options.version:
print "driver version %s" % DRIVER_VERSION
exit(1)
if options.debug:
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
if options.test == 'driver':
driver = WS3000()
try:
for p in driver.genLoopPackets():
print p
finally:
driver.closePort()
else:
station = WS3000()
while True:
command = station.COMMANDS["sensor_values"]
raw = station._get_raw_data(command)
data = station._raw_to_data(raw, command)
logdbg('data: %s' % data)
packet = station._data_to_wxpacket(data)
logdbg('packet: %s' % packet)
import weewx
import sys
from weewx.engine import StdService
import time
import syslog
def logmsg(level, msg):
syslog.syslog(level, 'ws3000DataService: %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)
class AddWS300Data(StdService):
def __init__(self, engine, config_dict):
# Initialize my superclass first:
super(AddWS300Data, self).__init__(engine, config_dict)
# Bind to any new archive record events and new loop record events
# TODO: selecting to which events to bind should be done via configuration
self.bind(weewx.NEW_ARCHIVE_RECORD, self.update_record)
# self.bind(weewx.NEW_LOOP_PACKET, self.update_packet)
# Load WS3000 driver
driver = config_dict['WS3000']['driver']
__import__(driver)
driver_module = sys.modules[driver]
loader_function = getattr(driver_module, 'loader')
self.ws3000 = loader_function(config_dict, self)
self.converter = None
def init_converter(self, wspacket):
"""Used to initialize the unit converted.
Data is coming from WS3000 using METRICWX, and must be converted to the units using in the
weewx packet for consistency."""
if self.converter is None:
target_unit = wspacket['usUnits']
self.converter = weewx.units.StdUnitConverters[target_unit]
def update_record(self, event):
"""Event handler for record packets.
Currently the service is no able to fetch historical data, it can only read current sensors values.
Records are therefore updated only if the record's time is matching current time to avoid backfilling
the DB with incorrect values."""
current_time = int(time.time() + 0.5)
loginf("update_record invoked. Current time: " + str(current_time) + ". Record time: " + str(event.record['dateTime']))
delta = 59 # 30 seconds delta. TODO: should be a configuration setting, to review
if current_time - delta < event.record['dateTime'] < current_time + 5: # clock could be up to 5 seconds early
loginf("delta ok, updating record")
self.update_dict(event.record)
def update_packet(self, event):
"""Event handler for loop packets"""
self.update_dict(event.packet)
def update_dict(self, wxpacket):
"""Function that copies data from the WS3000 packet to the weewx packet."""
self.init_converter(wxpacket)
wspacket = self.ws3000.getCurrentValues()
loginf("ws3000 data:" + str(wspacket))
converted_packet = self.converter.convertDict(wspacket)
# logdbg(wxpacket)
for key in converted_packet.keys():
wxpacket[key] = converted_packet[key]
import schemas.wview
import weewx.units
#
# *******************************************************************
#
# Changes to the database schema to take up to 8 sensors into account
ws3000Schema = schemas.wview.schema + [('extraTemp4', 'REAL'),
('extraTemp5', 'REAL'),
('extraTemp6', 'REAL'),
('extraTemp7', 'REAL'),
('extraTemp8', 'REAL'),
('extraHumid3', 'REAL'),
('extraHumid4', 'REAL'),
('extraHumid5', 'REAL'),
('extraHumid6', 'REAL'),
('extraHumid7', 'REAL'),
('extraHumid8', 'REAL')]
weewx.units.obs_group_dict['extraTemp8'] = 'group_temperature'
weewx.units.obs_group_dict['extraHumid8'] = 'group_percent'