Rick's b.log - 2016/04/02 |
|
It is the 21st of November 2024 You are 18.224.73.157, pleased to meet you! |
|
mailto:
blog -at- heyrick -dot- eu
The weather station uses a fairly simple but rather weird protocol to communicate with a host. You request specific nibbles (given byte addresses) and you ask for a number of bytes in reply (one byte is two nibbles). The reply is the data requested, followed by a checksum byte.
Right. So the first hurdle for many USB to serial interfaces is that it is necessary to control the DTR and RTS lines. It appears that a fair few clone devices cannot do this, especially if it's a "it cost two quid including postage from China" model. If you cannot independently control DTR and RTS, give up now.
But wait! I hear you cry. That phone plug thingy only has four wires, there's no way this device uses flow control and Shouldn't that be CTS?.
I hear you, and you would be correct. You want to know what the four wires are? Nah... You don't. You really don't.
Oh. Okay then...
One wire is for serial data from the computer to the weather station. One wire is for serial data from the weather station to the computer. One wire is DTR which is held low to provide a negative voltage. The final wire is RTS which is held high to provide a positive voltage.
How it works is a rather fascinating hack. There are two ways this could go. The first is the typical way. A chip, like a MAX232 or the like can take a TTL (logic level) serial connection and amp it up to RS232 line levels. And do the same conversion in reverse.
So on to plan B. Serial communications are defined as being +3V and -3V. Those are the two logic levels. Modern USB leads tend to offer +6V and -6V (or thereabout), while older computers often had swings in the order of 9V or 12V each way. The actual voltage doesn't matter, so long as it is over 3V for a "high" and over -3V for a "low".
What we're going to do now is walk through the process of reading the outdoor temperature.
The next job is to send an
If there is no reply after n attempts, either the station is not going to respond (faulty? batteries dead?) or it isn't connected. There's nothing you can do. Give up.
A complication to be aware of is that the nibbles have byte addresses, and you request data as a count of bytes. A byte will hold two nibbles.
If you look at the memory map on the Open2300 site, you will see that the outside temperature is located at address &0373, and it is laid out as follows:
This is four nibbles, so in order to retrieve this, you would request two bytes, which is returned as follows:
Or to view this more visually:
Some shifting and masking will be required to extract the data.
If you wish to read only one nibble, or an odd number of nibbles, round up to the nearest byte count and just ignore the extra nibble. So to read one nibble, read one byte (gives you two nibbles) and to read five nibbles, read three bytes (gives you six nibbles).
In order to send a request to the weather station, you will need to send four bytes holding the address (most significant byte first), followed by one byte giving the number or bytes (nibble pairs) to read. The address is calculated as follows:
Let's work this one together. The address we want to read from is &0373, so:
The final byte giving the number of bytes to read is calculated as:
Now, when we send the address bytes, the station will reply with a byte. The high nibble of the byte is not important so it can be masked off. The low nibble of the byte is the nibble of the address when decoded. So if we send &82, &8E, &9E, and &8E we would expect to receive &x0, &x3, &x7, and &x3 back.
Likewise, the byte count will be replied with a byte giving the count of bytes to be send in the low nibble. As we are sending &CA to request two bytes, we would expect to see the response &x2.
Here is the entire transaction:
Following this, the weather station will reply with the data requested all at once. We'll discuss this in a moment.
However... you need a way to indicate to your read routine that this is a retry of a failed command. Why? Because you will need to resynchronise communication with the weather station. This is simple - as described previously, just send &06 bytes until you receive an &02 in response.
You could send an &06 sync before every command, but it isn't necessary (and wastes time). One command can directly follow another, so long as the communication doesn't fail.
In my case, the data was as follows:
Now, if you recall the layout of the temperature data, you will be able to directly read the temperature. The second byte holds the tens and ones values, with the first byte holding the factional part. Looking at it, we can see that the temperature is 36.80°C.
Wait... Dude... It's April 2nd, how can you be having 36.80°C in the late evening in northern France?
The answer to this lies in the realisation that there is no way to report negative temperatures using this encoding scheme. So to deal with this anomaly, the weather station actually reports temperatures 30°C higher than reality. Thus, to obtain the true temperature, simply subtract 30. That makes it a somewhat chillier 6.8°C which is...sadly...the truth.
However, for the sake of calculation, here's how to extract the data to a floating point variable:
If you encounter these values, you should disregard them and instead display something like
My "new and improved" (more bulletproof) program is online at http://heyrick.ddns.net/, should you be interested in my current weather. It updates every five minutes.
Weather station redux - reading from the La Crosse WS-2300
Because the station sticks out from the wall, the piece of
paper is to shield it from the sun. The external sensor transmitter is
to the right of the old bread oven, away in the background.
The problem, and what makes this loads of fun, is that the weather station considers serial comms to be its least important priority. In other words, it'll totally drop the ball on you if it has anything else to do. Or if it gets bored. Or... You must design your data fetch routine with the understanding that it will probably fail and that this will happen at any time.
Note that they cheaped out on even supplying a ground connection.
But that might increase the production cost and risks sucking battery life. I keep my weather station plugged into its mains adaptor with batteries to cover over any periods that it is disconnected. The alternative? Three AA cells.
What the weather station does, therefore, is to use DTR to define the low level and RTS to define the high level. It then fakes a serial bitstream by simply switching between these as necessary.
Waking up the weather station
The first job is to 'wake up' the weather station. To do this, you should set DTR and RTS high. There is no need for a delay, you can immediately set DTR low and RTS high (normal mode). But you must begin the conversation by flicking both high otherwise you won't get any reply from the station.
Init: Set 2400 baud, format 8N1, with no flow control.
Send: DTR high, RTS high
Send: DTR low, RTS low
&06
byte and wait for a brief time (around 10cs).
And keep on doing this until you get &02
returned. You may have no reply, you may get other values back. Just keep sending sixes until you get a two back. Or you've tried 'n' times (in which case just give up).
REPEAT
Send: &06
WAIT: 10cs
UNTIL Recv: &02 (or >n attempts)
Requesting what to read
There is no specific API for reading weather data. Instead, there is a memory map of about 2.5KiB from &0000 to &13CF (that's 5071 locations, but the data is stored in 4 bit nibbles, not bytes) and there are commands to read and write nibbles, or write bits into a nibble.
We do not concern outselves with writing to the device (reset high/low or set high/low alarms, that sort of thing). We'll stick to reading.
0373 BCD 0.01s (°C)
0374 BCD 0.1s
0375 BCD 1s
0376 BCD 10s
Byte #0 Low BCD 0.01s
Byte #0 High BCD 0.1s
Byte #1 Low BCD 1s
Byte #1 High BCD 10s
7 6 5 4 3 2 1 0
Byte #0: <0.1s> <0.01s>
Byte #1: <10s> <1s>
Nibble of address × 4 + &82
( ( ( (&0373 >> 12 ) AND 15 ) << 2 ) + &82 ) = &82 (0)
( ( ( (&0373 >> 8 ) AND 15 ) << 2 ) + &82 ) = &8E (3)
( ( ( (&0373 >> 4 ) AND 15 ) << 2 ) + &82 ) = &9E (7)
( ( ( &0373 AND 15 ) << 2 ) + &82 ) = &8E (3)
This means that the four address bytes are &82, &8E, &9E, and &8E.
( bytecount << 2 ) + &C2
which gives us the result &CA.
If the reply bytes don't match up, abort the command (you should retry it a couple of times).
FLUSH RX buffer
Send: &82
WAIT: 10cs
Recv: &x0
Send: &8E
WAIT: 10cs
Recv: &x3
Send: &9E
WAIT: 10cs
Recv: &x7
Send: &8E
WAIT: 10cs
Recv: &x3
Send: &CA
WAIT: 10cs
Recv: &x2
WAIT: 10cs
As stated previously, if any of the responses don't match, immediately abort the command.
The buffer flushing (just read from the serial port until there's no data) is to guard against any stray data that may be lingering from communications failures. It shouldn't happen, but it doesn't hurt to be paranoid.
Aborting a failed command
It is likely that your read routine will be in a separate function, so the simplest way to abort is to return a magic value such as -1 to indicate a failure. You can then retry the same command again. It should work on the second or third attempt.
Reading the data
The weather station will reply with the data bytes requested, plus one additional byte. This additional byte contains the checksum. The checksum is simply the data bytes added together, and masked to be a byte wide value. This'll do it:
checksum% = 0
FOR loop% = 0 TO (bytes% - 1)
checksum% += buffer%?loop%
NEXT
checksum% = checksum% AND 255
When asking for the outdoor temperature, the weather station will reply with three bytes. The two data bytes, and a checksum byte.
Recv: &80
Recv: &36
Recv: &B6
We can tell just by looking at it that the checksum is good - &80 + &36 is &B6. If the checksum is good, the data is good.
outtemp = ((buffer%?1 >> 4) * 10)
outtemp += ((buffer%?1 AND 15))
outtemp += ((buffer%?0 >> 4) / 10.0)
outtemp += ((buffer%?0 AND 15) / 100.0)
outtemp -= 30.0
And here's how to extract it to two integers, one for the units and one for the ones (doing it this way avoids annoying rounding errors like the temperature being 10.50000001°C!):
outtempunit% = ((buffer%?1 >> 4) * 10)
outtempunit% += ((buffer%?1 AND 15))
outtempfrac% += ((buffer%?0 >> 4) * 10)
outtempfrac% += ((buffer%?0 AND 15))
outtempunit% -= 30
To display the integer version, simply do:
... +STR$(outtempunit%)+"."+STR$(outtempfrac%)+ ...
or to assure two decimal places of accuracy, lean on RIGHT$:
... +STR$(outtempunit%)+"."+RIGHT$("0"+STR$(outtempfrac%),2)+ ...
Beware bogus data!
If you are using the 433MHz wireless connection between the external sensors and the weather station, it is possible for the signal to be lost. In this case, the weather station will report bogus data which you should be aware of:
Temperature : 80.80°C (after -30 correction)
Humidity : 110%
Wind speed : 183.6 km/h (which is 51 m/s)
---
to show that the data is invalid.
That's it!
That's pretty much all there is to reading data from the La Crosse WS-2300 weather station. For reading other data, simply provide different addresses and interpret the data as appropriate.
(picture added the following day)Acknowledgements
Many thanks to:
David Pilling, 9th April 2016, 18:15 Well Done Colin - I am surprised so much can be achieved with what is a small piece of code. Naively I would expect oem libraries to be involved for correct operation - for example to brick non-FTDI copies. Dunno, I expect you can refer to the Linux versions.Rick, 9th April 2016, 20:31 Just goes to show that you don't need a driver that is megabytes in size.
© 2016 Rick Murray |
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted. RIPA notice: No consent is given for interception of page transmission. |