view wh1080.py @ 1:03ad4796a36d default tip

Add license.
author Daniel O'Connor <darius@dons.net.au>
date Sat, 13 Feb 2010 18:21:31 +1030
parents de9fe8d30147
children
line wrap: on
line source

#!/usr/bin/env python

import struct
import usb

WH1080_VENDOR = 0x1941
WH1080_DEVICE = 0x8021

WH1080_TIMEOUT = 100

WH1080_RECORD_SIZE = 32
WH1080_PAGE_SIZE = 32
WH1080_BASE = 0x100

WH1080_PAGE0_MAGIC1A = 0xffffffffffffaa55
WH1080_PAGE0_MAGIC1B = 0xffffffffffaaaa55
WH1080_PAGE0_MAGIC2  = 0xffffffffffffffff

WH1080_WIND_DIRECTIONS = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
                          "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]

def apparent_temp(Ta, rh, ws, Q = None):
    """Compute apparent temperature. Obtained from the Australian BOM at http://www.bom.gov.au/info/thermal_stress/
Ta = dry bulb temperature (Celcius)
rh = relative humidity (percentage)
ws = Wind speed (m/s)
Q = net radiation absorbed by body (W/m2) (optional)"""
    e = rh / 100 * 6.105 * exp(17.27 * Ta / (237.7 + Ta))

    if Q == None:
        return(Ta + 0.33 * e - 0.70 * ws - 4.00)
    else:
        return(Ta + 0.33 * e - 0.70 * ws + 0.70 * Q/(ws + 10) - 4.25)

def list2uintN(l, size):
    res = 0
    for i in xrange(size):
        res += l[i] << 8 * i
    return res

class WH1080(object):
    def __init__(self):

        busses = usb.busses()

        #
        # Search for the device we want
        #
        self.handle = None
        for bus in busses:
            for dev in bus.devices:
                #print "Looking at 0x%04x 0x%04x" % (dev.idVendor, dev.idProduct)
                if dev.idVendor == WH1080_VENDOR and dev.idProduct == WH1080_DEVICE:
                    # Open the device and claim the USB interface that supports the spec
                    self.handle = dev.open()
                    self.dev = dev
                    break

        if self.handle == None:
            raise "Could not find a suitable USB device"
        self.handle.claimInterface(0)

    def get_current_record(self):
        page0 = Page0(self.read_page(0))
        return(page0.current_record)

    def read_current_record(self):
        return self.read_record(self.get_current_record())

    def read_record(self, record):
        """Read the nominated record from the device"""

        # Calculate offset for this record
        ofs = (record * WH1080_PAGE_SIZE) + WH1080_BASE
        # 32 bytes covers 2 records.
        # We don't want to read past the end of memory so we start at
        # the even page then pick what we need.
        ofs &= ~WH1080_RECORD_SIZE

        data = self.read_page(ofs)
        #print "Reading record %d => 0x%04x" % (record, ofs)
        if record % 2:
            data = data[0:16]
        else:
            data = data[16:32]
            
        return Reading(data)

    def read_page(self, ofs):
        """Read a page from the device at ofs
Due to apparent hardware bugs / race conditions we read a few times until we have 2 identical reads"""
        for t in xrange(10):
            pageA = self._read_page(ofs)
            pageB = self._read_page(ofs)
            if pageA == pageB:
                break
        else:
            raise IOError("Could not read page cleanly")

        return pageA
            
    def _read_page(self, ofs):
        """Read a page from from the device at ofs (no retries)"""
        msb = (ofs >> 8) & 0xff
        lsb = ofs & 0xff

        req = [0xa1, msb, lsb, 0x20] * 2
        if self.handle.controlMsg(usb.TYPE_CLASS | usb.RECIP_INTERFACE, 0x9, req, value = 0x200, timeout = WH1080_TIMEOUT) != 8:
            raise IOError("Unable to send control message")
        
        data = self.handle.interruptRead(usb.ENDPOINT_IN | usb.RECIP_INTERFACE, WH1080_PAGE_SIZE, WH1080_TIMEOUT)
        if len(data) != WH1080_PAGE_SIZE:
            raise IOError("Unable to read from endpoint expected %d bytes got %d" % 
                          (WH1080_PAGE_SIZE, len(data)))
            
        data = map(chr, data)
        return reduce(lambda a, b: a + b, data)

class Page0(object):
    """Decode page 0, which contains a pointer to the current page"""
    def __init__(self, data):
        (magic1, magic2, current_offset) = struct.unpack('< Q Q 14x H', data)
        if (magic1 != WH1080_PAGE0_MAGIC1A and magic1 != WH1080_PAGE0_MAGIC1B) or magic2 != WH1080_PAGE0_MAGIC2:
            raise ValueError("page0 magic not valid")

        self.current_record = (current_offset - WH1080_BASE) / WH1080_PAGE_SIZE
        #print "Offset 0x%04x => %d" % (current_offset, self.current_record)

class Reading(object):
    def __init__(self, data):
        (self.last_save_mins, self.inside_humidity, self.inside_temp, 
         self.outside_humidity, self.outside_temp, self.pressure,
         self.wind_speed, self.wind_gust, foo, self.wind_direction,
         self.rain, bar) = struct.unpack('< B B h B h H B B B B H B', data)

        #print "foo = 0x%02x, bar = 0x%02x" % (foo, bar)
        self.inside_temp /= 10.0
        self.outside_temp /= 10.0
        self.wind_speed /= 10.0
        self.pressure /= 10.0
        if self.wind_direction == 0x80 or self.wind_direction == 0xff:
            self.wind_direction = None
        else:
            self.wind_direction = WH1080_WIND_DIRECTIONS[self.wind_direction]
        
    def __str__(self):
        return "%2d %4.1f %3d %4.1f %3d %6.1f %5.1f %5.1f %s %4.1f" % (
            self.last_save_mins, self.inside_temp, self.inside_humidity, self.outside_temp,
            self.outside_humidity, self.pressure, self.wind_speed, self.wind_gust,
            self.wind_direction, self.rain)