comparison wh1080.py @ 0:de9fe8d30147

Initial commit. Seems to work for the basics.
author Daniel O'Connor <darius@dons.net.au>
date Sat, 13 Feb 2010 18:19:42 +1030
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:de9fe8d30147
1 #!/usr/bin/env python
2
3 import struct
4 import usb
5
6 WH1080_VENDOR = 0x1941
7 WH1080_DEVICE = 0x8021
8
9 WH1080_TIMEOUT = 100
10
11 WH1080_RECORD_SIZE = 32
12 WH1080_PAGE_SIZE = 32
13 WH1080_BASE = 0x100
14
15 WH1080_PAGE0_MAGIC1A = 0xffffffffffffaa55
16 WH1080_PAGE0_MAGIC1B = 0xffffffffffaaaa55
17 WH1080_PAGE0_MAGIC2 = 0xffffffffffffffff
18
19 WH1080_WIND_DIRECTIONS = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
20 "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]
21
22 def apparent_temp(Ta, rh, ws, Q = None):
23 """Compute apparent temperature. Obtained from the Australian BOM at http://www.bom.gov.au/info/thermal_stress/
24 Ta = dry bulb temperature (Celcius)
25 rh = relative humidity (percentage)
26 ws = Wind speed (m/s)
27 Q = net radiation absorbed by body (W/m2) (optional)"""
28 e = rh / 100 * 6.105 * exp(17.27 * Ta / (237.7 + Ta))
29
30 if Q == None:
31 return(Ta + 0.33 * e - 0.70 * ws - 4.00)
32 else:
33 return(Ta + 0.33 * e - 0.70 * ws + 0.70 * Q/(ws + 10) - 4.25)
34
35 def list2uintN(l, size):
36 res = 0
37 for i in xrange(size):
38 res += l[i] << 8 * i
39 return res
40
41 class WH1080(object):
42 def __init__(self):
43
44 busses = usb.busses()
45
46 #
47 # Search for the device we want
48 #
49 self.handle = None
50 for bus in busses:
51 for dev in bus.devices:
52 #print "Looking at 0x%04x 0x%04x" % (dev.idVendor, dev.idProduct)
53 if dev.idVendor == WH1080_VENDOR and dev.idProduct == WH1080_DEVICE:
54 # Open the device and claim the USB interface that supports the spec
55 self.handle = dev.open()
56 self.dev = dev
57 break
58
59 if self.handle == None:
60 raise "Could not find a suitable USB device"
61 self.handle.claimInterface(0)
62
63 def get_current_record(self):
64 page0 = Page0(self.read_page(0))
65 return(page0.current_record)
66
67 def read_current_record(self):
68 return self.read_record(self.get_current_record())
69
70 def read_record(self, record):
71 """Read the nominated record from the device"""
72
73 # Calculate offset for this record
74 ofs = (record * WH1080_PAGE_SIZE) + WH1080_BASE
75 # 32 bytes covers 2 records.
76 # We don't want to read past the end of memory so we start at
77 # the even page then pick what we need.
78 ofs &= ~WH1080_RECORD_SIZE
79
80 data = self.read_page(ofs)
81 #print "Reading record %d => 0x%04x" % (record, ofs)
82 if record % 2:
83 data = data[0:16]
84 else:
85 data = data[16:32]
86
87 return Reading(data)
88
89 def read_page(self, ofs):
90 """Read a page from the device at ofs
91 Due to apparent hardware bugs / race conditions we read a few times until we have 2 identical reads"""
92 for t in xrange(10):
93 pageA = self._read_page(ofs)
94 pageB = self._read_page(ofs)
95 if pageA == pageB:
96 break
97 else:
98 raise IOError("Could not read page cleanly")
99
100 return pageA
101
102 def _read_page(self, ofs):
103 """Read a page from from the device at ofs (no retries)"""
104 msb = (ofs >> 8) & 0xff
105 lsb = ofs & 0xff
106
107 req = [0xa1, msb, lsb, 0x20] * 2
108 if self.handle.controlMsg(usb.TYPE_CLASS | usb.RECIP_INTERFACE, 0x9, req, value = 0x200, timeout = WH1080_TIMEOUT) != 8:
109 raise IOError("Unable to send control message")
110
111 data = self.handle.interruptRead(usb.ENDPOINT_IN | usb.RECIP_INTERFACE, WH1080_PAGE_SIZE, WH1080_TIMEOUT)
112 if len(data) != WH1080_PAGE_SIZE:
113 raise IOError("Unable to read from endpoint expected %d bytes got %d" %
114 (WH1080_PAGE_SIZE, len(data)))
115
116 data = map(chr, data)
117 return reduce(lambda a, b: a + b, data)
118
119 class Page0(object):
120 """Decode page 0, which contains a pointer to the current page"""
121 def __init__(self, data):
122 (magic1, magic2, current_offset) = struct.unpack('< Q Q 14x H', data)
123 if (magic1 != WH1080_PAGE0_MAGIC1A and magic1 != WH1080_PAGE0_MAGIC1B) or magic2 != WH1080_PAGE0_MAGIC2:
124 raise ValueError("page0 magic not valid")
125
126 self.current_record = (current_offset - WH1080_BASE) / WH1080_PAGE_SIZE
127 #print "Offset 0x%04x => %d" % (current_offset, self.current_record)
128
129 class Reading(object):
130 def __init__(self, data):
131 (self.last_save_mins, self.inside_humidity, self.inside_temp,
132 self.outside_humidity, self.outside_temp, self.pressure,
133 self.wind_speed, self.wind_gust, foo, self.wind_direction,
134 self.rain, bar) = struct.unpack('< B B h B h H B B B B H B', data)
135
136 #print "foo = 0x%02x, bar = 0x%02x" % (foo, bar)
137 self.inside_temp /= 10.0
138 self.outside_temp /= 10.0
139 self.wind_speed /= 10.0
140 self.pressure /= 10.0
141 if self.wind_direction == 0x80 or self.wind_direction == 0xff:
142 self.wind_direction = None
143 else:
144 self.wind_direction = WH1080_WIND_DIRECTIONS[self.wind_direction]
145
146 def __str__(self):
147 return "%2d %4.1f %3d %4.1f %3d %6.1f %5.1f %5.1f %s %4.1f" % (
148 self.last_save_mins, self.inside_temp, self.inside_humidity, self.outside_temp,
149 self.outside_humidity, self.pressure, self.wind_speed, self.wind_gust,
150 self.wind_direction, self.rain)