Since no open-source software or other information about the Oregon WMR 300 USB protocol was around, I had to test and decode every single packet. Find out the detailed USB protocol here.
In case you missed the first step of this series, go back to the first article
Oregon WMR 300 on a Raspberry Pi.
Once you set up the low-level C++ communication with the base station, we are ready to decode the USB protocol data!
Overall structure of the WMR 300 USB protocol
As mentioned in the previous article, each line retrieved via USB consists out of 64 hex-encoded bytes. Please note that we are using
zero-based indices in order to be able to implement it easier later on!
The general set-up of the data packet can be specified as following:
Bytes |
---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 to length-1 | length | length+1 to 63 |
package type | data length | UTC date | channel ID | [varying according to data packages, see below, depending on length. first live data, then day and month high/lows for each field are sent.] | checksum | padding with 00 |
First of all, it turns out, the WMR 300 sends different data package types via USB which is specified by byte 0, namely
0xD3
packages contain temperature, humidity and dewpoint data, with a length of 0x3D
0xD4
packages contain wind data, with a length of 0x36
0xD5
packages contain rain data, with a length of 0x28
0xD6
packages contain air pressure data, with a length of 0x2E
Byte 1 contains the overall data package length, starting from 0 to the checksum field. That means, a length of
0x3D
(61) means that the checksum will be on byte 60, and all other following bytes will be
0x00
.
Byte 2 through 6 contain the base stations UTC timestamp, encoded as followed:
Bytes |
---|
... | 2 | 3 | 4 | 5 | 6 | ... |
... | years since 2000, e.g. 0x0F for 2015 | month | day | hour | minute | ... |
Byte 7 contains the channel, that is a number from
0x00
to
0x09
, because the WMR 300 supports up to 8 sensor stations connected plus one base station. This is why the channel
0x00
announces data coming from the base station (that is, inside temperature, humidity and dewpoint as well as air pressure).
The following bytes start with the current weather data, followed by historical daily and monthly high/lows together with their UTC dates. Since this is mostly irrelevant for sending live data, this blog entry won't explain how to decode them. You can, however, find information
about the extended WMR 300 USB Protocol Information here.
Representation of numbers
Numbers are stored over 1 to three bytes in big endian byte order. Negative numbers are stored via the Two's complement, that is if the first byte starts with
F
it is a negative number.
Let's suppose we got a hex-notation style line from our C++ program developed in the earlier article. The following PHP snippet demonstrates on how the data can be accessed.
[code lang="php"]
$line = 'D3 12 34 56 78 ... 00 00';
$bytes = explode(' ',$line);
function getBytes($bytes,$start,$len) {
// invalid parameters specified- out of range
if(($start + $len) < count($bytes))
return 0;
// concat the relevant bytes
$s = '';
for($i = $start;$i<$start+$len;$i++) {
$s = $s.$bytes[$i];
}
// handles when invalid data is sent. this happens when the sensor is disconnected!
$failed = '7f'.str_repeat('ff',$len-1);
if(strpos($s,$failed) !== false) {
return false;
}
// handle negative numbers
$sub = 0;
if($s[0] === 'f') {
$sub = pow(2,8*$len);
}
return hexdec($s) - $sub;
}
var_dump(getBytes($bytes,0,1)); // 211
[/code]
This means we can extract a date with the following function:
[code lang="php"]
function getDate($bytes,$start) {
$y = '20'.str_pad(getBytes($bytes,$start++,1),2,'0',STR_PAD_LEFT);
$m = str_pad(getBytes($bytes,$start++,1),2,'0',STR_PAD_LEFT);
$d = str_pad(getBytes($bytes,$start++,1),2,'0',STR_PAD_LEFT);
$h = str_pad(getBytes($bytes,$start++,1),2,'0',STR_PAD_LEFT);
$mi = str_pad(getBytes($bytes,$start,1),2,'0',STR_PAD_LEFT);
//var_dump($y,$m,$d,$h,$mi);
return strtotime($y.'-'.$m.'-'.$d.' '.$h.':'.$mi);
}
[/code]
D3 package: temperature, humidity and dewpoint
Bytes |
---|
0 | 1 | 2 to 6 | 7 | 8 | 9 | 10 | 11 | 12 |
D3 | 3D | Y-m-d H:i | 0 =in, 1 = out | temperature*10 [°C] | humidity [%] | dewpoint*10 [°C] |
D4 package: wind average, gusts and directions
Bytes |
---|
0 | 1 | 2 to 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
D4 | 36 | Y-m-d H:i | 0 =in, 1 = out | gust speed*10 [m/s] | gust dir [°] | avg speed*10 [m/s] | avg dir[°] |
D5 package: rain rate, sums and total rain
Bytes |
---|
0 | 1 | 2 to 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
D5 | 28 | Y-m-d H:i | 0 =in, 1 = out | ?? | rain 1h*100 [in] | ?? | rain 24h*100 [in] | ?? | rain since reset*100 [in] | rain rate*100 [in/h] |
D6 package: air pressure and height
Bytes |
---|
0 | 1 | 2 to 6 | 7 | 8 | 9 | 10 | 11 |
D6 | 2E | Y-m-d H:i | 0 =in, 1 = out | pressure*10 [mb] | pressure at sea level*10 [mb] |
For further development, I decided to share the source code that I am using to upload the data from my Raspberry Pi to the SQL database. You can access the folder
here.
WMR 300 PHP Decoder by
Zahlii is licensed under a
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.