Mercurial > ~darius > hgwebdir.cgi > py-wh1080
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) |