From a3543aaec320e56c6f8a7c419707ac1aa40c4408 Mon Sep 17 00:00:00 2001 From: Someone Date: Sat, 29 May 2021 18:44:01 +0200 Subject: [PATCH] Periodically fetch wh1080 data. --- cron.sh | 40 +++++++++++++ fetch_data.py | 146 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + wh1080.cron | 4 ++ wh1080.udev-rules | 4 ++ 5 files changed, 195 insertions(+) create mode 100755 cron.sh create mode 100755 fetch_data.py create mode 100644 requirements.txt create mode 100644 wh1080.cron create mode 100644 wh1080.udev-rules diff --git a/cron.sh b/cron.sh new file mode 100755 index 0000000..ca2e700 --- /dev/null +++ b/cron.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# This script is run every min and does all the data gathering. + +MYPWD=$(pwd) + +mkdir -p /tmp/wh1080/device +START_TS="$(date -Isec)" +echo "** cron.sh: started: ${START_TS}" +echo "${START_TS}" >/tmp/wh1080/cron.sh.start.ts +echo "${START_TS}" >/tmp/wh1080/device/last.ts + + + +# cleanup old history data. +cd /tmp/wh1080/device +find . -type f -mmin +1440 -delete >/dev/null 2>&1 +find . -empty -delete >/dev/null 2>&1 +cd /tmp/wh1080 + + +# get data +echo "** cron.sh: processing" +mkdir -p /tmp/wh1080/device/history +cd "$MYPWD" +./fetch_data.py >/tmp/wh1080/device/last.json.new + +mv /tmp/wh1080/device/last.json.new /tmp/wh1080/device/last.json +cat /tmp/wh1080/device/last.json >"/tmp/wh1080/device/history/data.$(cat /tmp/wh1080/device/last.ts).json" +cd /tmp/wh1080/device/history +echo "[$(ls --format=commas -Q)]" >/tmp/wh1080/device/history.json + + + +# finish +ln -snf /tmp/wh1080.cron.sh.log /tmp/wh1080/cron.sh.log + +END_TS="$(date -Isec)" +echo "${END_TS}" >/tmp/wh1080/cron.sh.stop.ts +echo "** cron.sh: DONE: ${END_TS}" diff --git a/fetch_data.py b/fetch_data.py new file mode 100755 index 0000000..0e3d591 --- /dev/null +++ b/fetch_data.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# Model Dreamlink WH1080 +# + +import usb.core +import time +import struct +import math +import datetime + +VENDOR = 0x1941 +PRODUCT = 0x8021 +WIND_DIRS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] + + +def open_ws(): + ''' + Open a connection to the device, using the PRODUCT and VENDOR information + @return reference to the device + ''' + usb_device = usb.core.find(idVendor=VENDOR, idProduct=PRODUCT) + + if usb_device is None: + raise ValueError('Device not found') + + usb_device.get_active_configuration() + + # If we don't detach the kernel driver we get I/O errors + if usb_device.is_kernel_driver_active(0): + usb_device.detach_kernel_driver(0) + + return usb_device + + +def read_block(device, offset): + ''' + Read a block of data from the specified device, starting at the given offset. + @Inputs + device + - usb_device + offset + - int value + @Return byte array + ''' + + least_significant_bit = offset & 0xFF + most_significant_bit = offset >> 8 & 0xFF + + # Construct a binary message + tbuf = struct.pack('BBBBBBBB', + 0xA1, + most_significant_bit, + least_significant_bit, + 32, + 0xA1, + most_significant_bit, + least_significant_bit, + 32) + + timeout = 1000 # Milliseconds + retval = device.ctrl_transfer(0x21, # USB Requesttype + 0x09, # USB Request + 0x200, # Value + 0, # Index + tbuf, # Message + timeout) + + return device.read(0x81, 32, timeout) + + +def main(): + dev = open_ws() + dev.set_configuration() + + + ########### Read data + # Get the first 32 Bytes of the fixed + fixed_block = read_block(dev, 0) + + # Check that we have good data + if (fixed_block[0] != 0x55): + raise ValueError('Bad data returned') + + # Bytes 31 and 32 when combined create an unsigned short int that tells us where to find the weather data we want + curpos = struct.unpack('H', fixed_block[30:32])[0] + current_block = read_block(dev, curpos) + + # Indoor information + indoor_humidity = current_block[1] + tlsb = current_block[2] + tmsb = current_block[3] & 0x7f + tsign = current_block[3] >> 7 + indoor_temperature = (tmsb * 256 + tlsb) * 0.1 + # Check if temperature is less than zero + if tsign: + indoor_temperature *= -1 + + # Outdoor information + outdoor_humidity = current_block[4] + tlsb = current_block[5] + tmsb = current_block[6] & 0x7f + tsign = current_block[6] >> 7 + outdoor_temperature = (tmsb * 256 + tlsb) * 0.1 + # Check if temperature is less than zero + if tsign: + outdoor_temperature *= -1 + + # Bytes 8 and 9 when combined create an unsigned short int that we multiply by 0.1 to find the absolute pressure + abs_pressure = struct.unpack('H', current_block[7:9])[0]*0.1 + + wind = current_block[9] + gust = current_block[10] + wind_extra = current_block[11] + wind_dir = current_block[12] + + # Bytes 14 and 15 when combined create an unsigned short int + # that we multiply by 0.3 to find the total rain + # I'm not confident that this is correct. Neither abs_pressure nor + # total_rain are returning sane values. In fact total_rain has + # stayed static despite rainfall + # Looks like I fixed it. They used fixed_block instead of current_block + total_rain = struct.unpack('H', current_block[13:15])[0]*0.3 + + # Calculate wind speeds + wind_speed = (wind + ((wind_extra & 0x0F) << 8)) * 0.38 # Was 0.1 + gust_speed = (gust + ((wind_extra & 0xF0) << 4)) * 0.38 # Was 0.1 + + + ############### print data + print('{') + print('"indoor_humidity": "%i",' %indoor_humidity) + print('"indoor_temperature": "%2.1f",' %indoor_temperature) + print('"outdoor_humidity": "%i",' %outdoor_humidity) + print('"outdoor_temperature": "%2.1f",' %outdoor_temperature) + print('"abs_pressure": "%4.1f",' %abs_pressure) + print('"total_rain": "%3.1f",' %total_rain) + print('"wind_speed": "%2.1f",' %wind_speed) + print('"gust_speed": "%2.1f",' %gust_speed) + if wind_dir != 128: + print('"wind_dir": "%s",' %WIND_DIRS[wind_dir]) + print('"TS": "%s"' %(datetime.datetime.now().isoformat())) + print('}') + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6513d5e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyusb diff --git a/wh1080.cron b/wh1080.cron new file mode 100644 index 0000000..c7cc48f --- /dev/null +++ b/wh1080.cron @@ -0,0 +1,4 @@ +# cp wh1080.cron /etc/cron.d/wh1080 + +# fetch data from WH1080 +* * * * * root (cd /root/gitstuff/pyWH1080; ./cron.sh) &>/tmp/wh1080.cron.sh.log diff --git a/wh1080.udev-rules b/wh1080.udev-rules new file mode 100644 index 0000000..2da17a3 --- /dev/null +++ b/wh1080.udev-rules @@ -0,0 +1,4 @@ +# +# cp udev-90-wh1080.rules /etc/udev/rules.d/90-wh1080.rules + +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="1941", ATTR{idProduct}=="8021", MODE="0660", GROUP="plugdev" -- 2.43.0