| 1 | A partly protocol description: |
|---|
| 2 | |
|---|
| 3 | Serial port settings |
|---|
| 4 | The serial port settings used to connect to the plugwise stick (USB) are the following: |
|---|
| 5 | |
|---|
| 6 | - Baud rate: 115200 |
|---|
| 7 | - Data bits: 8 |
|---|
| 8 | - Stop bits: 1 |
|---|
| 9 | - Parity: none |
|---|
| 10 | |
|---|
| 11 | Packet header |
|---|
| 12 | Every command send to the plugwise stick must have a valid packet header. |
|---|
| 13 | |
|---|
| 14 | Example packet header: |
|---|
| 15 | |
|---|
| 16 | <code> |
|---|
| 17 | <ENQ><ENQ><ETX><ETX> |
|---|
| 18 | </code> |
|---|
| 19 | |
|---|
| 20 | The hexadecimal representation of the above is: |
|---|
| 21 | <code> |
|---|
| 22 | \x05\x05\x03\x03 |
|---|
| 23 | </code> |
|---|
| 24 | |
|---|
| 25 | Packet end |
|---|
| 26 | Every command send must be ended by a control feed followed by a linefeed. |
|---|
| 27 | |
|---|
| 28 | Example packet end: |
|---|
| 29 | <code> |
|---|
| 30 | <CR><LF> |
|---|
| 31 | </code> |
|---|
| 32 | |
|---|
| 33 | The hexadecimal representation of the above is: |
|---|
| 34 | <code> |
|---|
| 35 | \x0d\x0a |
|---|
| 36 | </code> |
|---|
| 37 | |
|---|
| 38 | Analyzing the on/off packet |
|---|
| 39 | In this part I analyze the on/off packet. |
|---|
| 40 | Because examples say more then a thousand words, here's an example of an ON packet: |
|---|
| 41 | <code> |
|---|
| 42 | <ENQ><ENQ><ETX><ETX>0017000A1100003111AB01AC92<CR><LF> |
|---|
| 43 | </code> |
|---|
| 44 | |
|---|
| 45 | The first 4 characters represent a function code, in this case it's the on/off function represented by the "0017" characters. |
|---|
| 46 | |
|---|
| 47 | Followed by the function code is the mac address of your device. In this example the mac address is: 00A1100003111AB |
|---|
| 48 | |
|---|
| 49 | Then followed by the mac address is the function value, in this case it's 01. 01 in this function represents "ON". If it stated "00" it would have meant "OFF" |
|---|
| 50 | |
|---|
| 51 | Last but not least, the toughest nut to crack. The last value, in this case the 4 characters "AC92" is a CRC16 value. |
|---|
| 52 | For more information about CRC please see the following wikipedia article: |
|---|
| 53 | |
|---|
| 54 | http://en.wikipedia.org/wiki/Cyclic_redundancy_check |
|---|
| 55 | |
|---|
| 56 | The CRC16 value is calculated over the following parts of the packet: |
|---|
| 57 | - Function code |
|---|
| 58 | - MAC address |
|---|
| 59 | - Function value/new state |
|---|
| 60 | |
|---|
| 61 | In this example the CRC16 value is calculated over the following string: |
|---|
| 62 | <code> |
|---|
| 63 | 0017000A1100003111AB01 |
|---|
| 64 | </code> |
|---|
| 65 | |
|---|
| 66 | The CRC checksum used is not a standard one, it has the following properties (in some documents refered to as the xmodem or ymodem CRC): |
|---|
| 67 | |
|---|
| 68 | - Polynomial: 0x11021 |
|---|
| 69 | - Seed value: 0x00000 |
|---|
| 70 | - Xor mask: 0x00000 |
|---|
| 71 | - Width: 16 |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | Power measurement: |
|---|
| 75 | |
|---|
| 76 | Calibration |
|---|
| 77 | |
|---|
| 78 | Each plugwise plug has some calibration information. This information can be found in the plugwise access database (PlugwiseData.mdb) |
|---|
| 79 | The values containing calibration information are the following: |
|---|
| 80 | |
|---|
| 81 | - OffRuis |
|---|
| 82 | - OffTot |
|---|
| 83 | - GainA |
|---|
| 84 | - GainB |
|---|
| 85 | |
|---|
| 86 | You can also query the information from the plug itself using the stick. |
|---|
| 87 | |
|---|
| 88 | Let's analyze a calibration request: |
|---|
| 89 | |
|---|
| 90 | <ENQ><ENQ><ETX><ETX>002600A1100003111AB7071<CR><LF> |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | The first 4 characters represent the function code, in this case it's the "0026" command for the calibration request. |
|---|
| 95 | The next characters represent the mac address of the plug, in this case "00A1100003111AB" |
|---|
| 96 | The last 4 characters are the CRC16 code, for an explanation of this see my earlier post. |
|---|
| 97 | |
|---|
| 98 | Let's analyze the calibration response: |
|---|
| 99 | |
|---|
| 100 | <ENQ><ENQ><ETX><ETX>0027 00A1100003111AB 3F78BD69 B6FF0876 3CA99962 00000000 EE6D<CR><LF> |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | |
|---|
| 104 | Note: the spaces have been included for readability. |
|---|
| 105 | |
|---|
| 106 | The first 4 characters represent the function code, in this case calibration response. |
|---|
| 107 | The next string is the mac address. |
|---|
| 108 | Now for the interesting part: |
|---|
| 109 | |
|---|
| 110 | - 3F78BD69 represents the GainA value hexadecimal. |
|---|
| 111 | - B6FF0876 represents the GainB value hexadecimal. |
|---|
| 112 | - 3CA99962 represents the OffTot value hexadecimal. |
|---|
| 113 | - 00000000 represents the OffRuis value hexadecimal. |
|---|
| 114 | - The last code is (i think) a CRC16 code again, not sure about this one. |
|---|
| 115 | |
|---|
| 116 | I use a function like this in python to convert the hexadecimal values to a "human readable" float or double: |
|---|
| 117 | |
|---|
| 118 | def hexToFloat(self, hexstr): |
|---|
| 119 | intval = int(hexstr, 16) |
|---|
| 120 | bits = struct.pack('L', intval) |
|---|
| 121 | return struct.unpack('f', bits)[0] |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | |
|---|
| 125 | The calibration information is used for a correct reading of the watt usage. |
|---|
| 126 | |
|---|
| 127 | Power information |
|---|
| 128 | |
|---|
| 129 | Power information is read by using the following command: |
|---|
| 130 | |
|---|
| 131 | <ENQ><ENQ><ETX><ETX>0012 00A1100003111AB AB43<CR><LF> |
|---|
| 132 | |
|---|
| 133 | |
|---|
| 134 | |
|---|
| 135 | It needs no explanation that the function code is "0012" the mac adress is the next string etc. |
|---|
| 136 | |
|---|
| 137 | The powerinfo response looks like this: |
|---|
| 138 | |
|---|
| 139 | <ENQ><ENQ><ETX><ETX>0013 00A1100003111AB 0030 0030 0001D62A 9863<CR><LF> |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | The first two parts of this need no explanation. |
|---|
| 144 | The following explains the codes followed by that: |
|---|
| 145 | |
|---|
| 146 | - 0030 pulse information of 8 seconds reading. |
|---|
| 147 | - 0030 pulse information of 1 second reading. |
|---|
| 148 | - 0001D62A yet unknown, still trying to figure out. |
|---|
| 149 | |
|---|
| 150 | The pulse information is again hexadecimal. To convert it to a integer I use the following python code: |
|---|
| 151 | |
|---|
| 152 | def hexToInt(self, hexstr): |
|---|
| 153 | return int(hexstr, 16) |
|---|
| 154 | |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | How to get the watt I hear you asking? |
|---|
| 158 | First the pulse information has to be corrected based on the calibration information of the plug, that's done using the following formula: |
|---|
| 159 | |
|---|
| 160 | 1.0 * (((pow(value + offruis, 2.0) * gain_b) + ((value + offruis) * gain_a)) + offtot) |
|---|
| 161 | |
|---|
| 162 | Where value is the number of pulses in integer format. pow() is a python for the mathematical power. |
|---|
| 163 | |
|---|
| 164 | If the pulse information has been corrected based upon the calibration information you can go to KWH using the following formula: |
|---|
| 165 | |
|---|
| 166 | (pulses / 1) / 468.9385193 |
|---|
| 167 | |
|---|
| 168 | Where pulses is the number of pulses offcourse. |
|---|
| 169 | |
|---|
| 170 | To go to watt you'll simply have to do multiply by 1000. |
|---|
| 171 | |
|---|
| 172 | Long story, I hope it's clear enough. |
|---|
| 173 | |
|---|
| 174 | As promised I would publish some information about the power buffers. |
|---|
| 175 | |
|---|
| 176 | Circle information request |
|---|
| 177 | |
|---|
| 178 | You can read some information about the plugwise device, some values are unknown. But, I already want to share this because it's needed for power buffer reading. |
|---|
| 179 | |
|---|
| 180 | Let's analyze the information request: |
|---|
| 181 | |
|---|
| 182 | 0023000D6F00002366BB231B |
|---|
| 183 | |
|---|
| 184 | |
|---|
| 185 | |
|---|
| 186 | - The first 4 bits represent the function code, which is in this case "0023" |
|---|
| 187 | - The next 16 bits represent the MAC address of the device, which is in this case "000D6F00002366BB" |
|---|
| 188 | - The last 4 bits represent the CRC16 checksum value, in this case "231B" |
|---|
| 189 | |
|---|
| 190 | Let's analyze the information response: |
|---|
| 191 | |
|---|
| 192 | 0024 000D6F00002366BB00003681 000457C8 01 8500000473000748B4253801F74D |
|---|
| 193 | |
|---|
| 194 | |
|---|
| 195 | |
|---|
| 196 | Note: the spaces have been included for readability. |
|---|
| 197 | |
|---|
| 198 | The first 4 characters represent the function code, in this case information response. |
|---|
| 199 | The next string is the mac address. |
|---|
| 200 | Now for the interesting part: |
|---|
| 201 | |
|---|
| 202 | - 000457C8 represents the last logaddress value hexadecimal. This logaddress is used for power buffer information. |
|---|
| 203 | To get the logaddress in nice decimal format, do the following: |
|---|
| 204 | |
|---|
| 205 | 1) Convert the hexadecimal value to integer. |
|---|
| 206 | 2) Substract 278528 from the outcome. |
|---|
| 207 | 3) Divide that outcome by 32. |
|---|
| 208 | |
|---|
| 209 | The result will be the latest log address of the power buffers. |
|---|
| 210 | |
|---|
| 211 | My calculation looks like this: |
|---|
| 212 | |
|---|
| 213 | |
|---|
| 214 | (self.hexToInt(data) - 278528) / 32 |
|---|
| 215 | |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | These logaddresses are also visible in the plugwise database. |
|---|
| 219 | |
|---|
| 220 | - 01 represents the current relay state of the device (01=ON, 00=OFF) |
|---|
| 221 | |
|---|
| 222 | The other values are yet unknown. Also because I wasn't interested in these values yet. |
|---|
| 223 | |
|---|
| 224 | Power buffer information |
|---|
| 225 | |
|---|
| 226 | The circle's hold an internal buffer of power usage. This way you can safely close the source software and read that information later on. I have succeeded in reading this without using the source. |
|---|
| 227 | |
|---|
| 228 | Let's analyze the power buffer request: |
|---|
| 229 | |
|---|
| 230 | 0048 000D6F0000236317 00045640 439B |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | - The first 4 bits represent the function code, which is in this case "0048" |
|---|
| 235 | - The next 16 bits represent the MAC address of the device, which is in this case "000D6F0000236317" |
|---|
| 236 | - The next 8 bits represent the log address of the power buffer you want to fetch, remember we read the latest buffer address with the information request? You can use this to determine the buffers you are missing in your program, for example if the latest log address is 160 and the last processed buffer in your program is 150 you are missing 10 buffers starting from 150 (so you would do a read request here for 151, 152 etc.) |
|---|
| 237 | The log address requires some ca****ation (again) In this case the log address is: |
|---|
| 238 | |
|---|
| 239 | - 00045640 to decimal 284224 |
|---|
| 240 | - 284224 - 278528 |
|---|
| 241 | - 5696 / 32 = 178 |
|---|
| 242 | |
|---|
| 243 | So the log address is 178. |
|---|
| 244 | |
|---|
| 245 | To calculate the hexadecimal for a log address one would just reverse the formula: |
|---|
| 246 | |
|---|
| 247 | The log address is 178. |
|---|
| 248 | 278528 + (32 * 178) = 284224 |
|---|
| 249 | |
|---|
| 250 | Converting this to hexadecimal is: 00045640 |
|---|
| 251 | |
|---|
| 252 | - The last 4 bits represent the CRC16 checksum value, in this case "439B" |
|---|
| 253 | |
|---|
| 254 | Let's analyze the power buffer response: |
|---|
| 255 | |
|---|
| 256 | 0049 000D6F0000236317 000036B1 0000ABAA 000036B2 0000AB72 000036B3 0000AB66 000036B4 0000ABAD 00045620 758F |
|---|
| 257 | |
|---|
| 258 | |
|---|
| 259 | |
|---|
| 260 | This is a huge one |
|---|
| 261 | |
|---|
| 262 | - The first 4 bits represent the function code, which is in this case "0049" |
|---|
| 263 | - The next 16 bits represent the MAC address of the device, which is in this case "000D6F0000236317" |
|---|
| 264 | - The next 8 bits represent the hour of the first buffer in abshour format (got this name from the access database :-)) more about this abshour format later. |
|---|
| 265 | - The next 8 bits represent the usage in pulses for the first hour. |
|---|
| 266 | - The next 8 bits represent the hour of the second buffer in abshour format (got this name from the access database :-)) more about this abshour format later. |
|---|
| 267 | - The next 8 bits represent the usage in pulses for the second hour. |
|---|
| 268 | - The next 8 bits represent the hour of the third buffer in abshour format (got this name from the access database :-)) more about this abshour format later. |
|---|
| 269 | - The next 8 bits represent the usage in pulses for the third hour. |
|---|
| 270 | - The next 8 bits represent the hour of the fourth buffer in abshour format (got this name from the access database :-)) more about this abshour format later. |
|---|
| 271 | - The next 8 bits represent the usage in pulses for the fourth hour. |
|---|
| 272 | - The next 8 bits represent logaddress of the buffer, this is the same log address as explained before. |
|---|
| 273 | - The last 4 bits represent the CRC16 checksum value, in this case "758F" |
|---|
| 274 | |
|---|
| 275 | The abshour format |
|---|
| 276 | Let's pick the first buffer address as example: 000036B1 |
|---|
| 277 | The decimal value of this abshour value is 14001 |
|---|
| 278 | |
|---|
| 279 | This value was a real brain cracker, after comparing the values I figured out the following. |
|---|
| 280 | For illustration let's convert the next abshour value (000036B2) to decimal: 14002 |
|---|
| 281 | |
|---|
| 282 | Aha! It increases by one all the time, so there must be a constant in these values. Then I substracted this value as hours from the current date (as it states abshour I was figuring it had something to do with hoours) this brought me to the following date: |
|---|
| 283 | |
|---|
| 284 | 1-6-2007 |
|---|
| 285 | |
|---|
| 286 | So if you add the number of hours to that date you'll get the datetime of that buffer. |
|---|
| 287 | Example in python: |
|---|
| 288 | |
|---|
| 289 | >>> import datetime |
|---|
| 290 | >>> timestart = datetime.datetime(2007, 6, 1, 2) |
|---|
| 291 | >>> dif = datetime.timedelta(hours=14001) |
|---|
| 292 | >>> datetime = timestart + dif |
|---|
| 293 | >>> datetime |
|---|
| 294 | datetime.datetime(2009, 1, 4, 11, 0) |
|---|
| 295 | |
|---|
| 296 | |
|---|
| 297 | So the date and time of this buffer request is: |
|---|
| 298 | 4-1-2009 11:00 |
|---|
| 299 | |
|---|
| 300 | The pulses |
|---|
| 301 | I have explained these pulses before in a post. The pulses here are exactly the same pulses, however there is one difference. |
|---|
| 302 | The formula must be changed from: |
|---|
| 303 | |
|---|
| 304 | 1.0 * (((pow(value + offruis, 2.0) * gain_b) + ((value + offruis) * gain_a)) + offtot) |
|---|
| 305 | |
|---|
| 306 | To: |
|---|
| 307 | |
|---|
| 308 | 3600 * (((pow(value + offruis, 2.0) * gain_b) + ((value + offruis) * gain_a)) + offtot) |
|---|
| 309 | |
|---|
| 310 | Also you need to divide the pulses value by 3600. |
|---|
| 311 | This is needed because the pulses are now logged for an entire hour rather then a second. |
|---|
| 312 | I use a generic function now for the pulsecorrection: |
|---|
| 313 | |
|---|
| 314 | |
|---|
| 315 | def PulseCorrection(self, pulses, id, timespan): |
|---|
| 316 | """ |
|---|
| 317 | Corrects plugwise pulses using calibration information from plug. |
|---|
| 318 | """ |
|---|
| 319 | device = Device.get(int(id)) |
|---|
| 320 | value = pulses / timespan; |
|---|
| 321 | out = timespan * (((pow(value + device.extension.offruis, 2.0) *\ |
|---|
| 322 | device.extension.gainb) + ((value + device.extension.offruis) * \ |
|---|
| 323 | device.extension.gaina)) + device.extension.offtot) |
|---|
| 324 | return out |
|---|
| 325 | |
|---|
| 326 | Phew.. this is gotten a really long post, I hope it's clear enough and that it's usefull for you. If you have any questions don't hesitate to ask. |
|---|
| 327 | |
|---|
| 328 | -- |
|---|
| 329 | Maarten Damen |
|---|
| 330 | |
|---|
| 331 | www.maartendamen.com |
|---|