mailto: blog -at- heyrick -dot- eu

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 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.
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.

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.
Note that they cheaped out on even supplying a ground connection.

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.
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.

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 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.

 

What we're going to do now is walk through the process of reading the outdoor temperature.

 

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

The next job is to send an &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)

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.

 

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.

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:

      0373  BCD   0.01s (°C)
      0374  BCD   0.1s
      0375  BCD   1s
      0376  BCD  10s

This is four nibbles, so in order to retrieve this, you would request two bytes, which is returned as follows:

      Byte #0 Low   BCD   0.01s
      Byte #0 High  BCD   0.1s
      Byte #1 Low   BCD   1s
      Byte #1 High  BCD  10s

Or to view this more visually:

                 7 6 5 4 3 2 1 0
      Byte #0:   <0.1s>  <0.01s>
      Byte #1:   <10s>   <1s>

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:

      Nibble of address × 4 + &82

Let's work this one together. The address we want to read from is &0373, so:

      ( ( ( (&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.

The final byte giving the number of bytes to read is calculated as:

      ( bytecount << 2 ) + &C2
which gives us the result &CA.

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.
If the reply bytes don't match up, abort the command (you should retry it a couple of times).

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:

      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.

Following this, the weather station will reply with the data requested all at once. We'll discuss this in a moment.

 

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.

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.

 

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.

In my case, the data was as follows:

      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.

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:

      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)

If you encounter these values, you should disregard them and instead display something like --- 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.

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.


(picture added the following day)

 

Acknowledgements

Many thanks to:
  • Colin Granville for the USBSerial module to talk to a Prolific PL-2303 serial port.
  • Kenneth Lavresen and the volunteers of the Open2300 project, for documenting the protocol and memory map.
  • The staff and volunteers of RISC OS Open for keeping RISC OS alive.
  • The RaspberryPi foundation that gave me a decent little machine for running RISC OS that cost peanuts. You don't wanna know how much Acorn hardware cost, back in the day...
  • All the teachers that called me stupid and a waste of time. <blows raspberry> They weren't all imbeciles. Special mention to my Physics teacher who taught me to question everything and not believe something until I can measure it for myself.
  • And, of course, YOU, for coming here and reading this. Don't worry, you can keep your AdBlock engaged here, there are no annoying adverts and I positively encourage Ad Blocking.

 

 

Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
 
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

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.

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 00714 backwards.
Your comment
French flagSpanish flagJapanese flag
Calendar
«   April 2016   »
MonTueWedThuFriSatSun
    13
45678910
1213141516
192021222324
252627282930 

Advent Calendar 2023
(YouTube playlist)

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 08:48 on 2024/03/29.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 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.

 

Have you noticed the watermarks on pictures?
Next entry - 2016/04/11
Return to top of page