Sending multiple values Feb 2017
The previous article showed how to send a value wirelessly from one node to another, as text.
While it’s a good example to start off with due to the simplicity, this doesn’t really scale. It’s tedious to build up text packets, they lead to larger packet payloads, and this all adds to the complexity on both ends. It’s better to send binary data, using an encoding which compresses small values a little (which also happens with ASCII: small integers take fewer bytes).
Also, we really need to be able to send multiple values per packet, not just one.
Sending individual bytes would be easy: just store them one after another in a buffer and send the buffer as payload. But not all values are in the range -128..+127 (or 0..255 if unsigned). And using 4 bytes per value is a bit wasteful for values which do happen to be relatively small, even though this could transfer values from -2,197,815,296 .. +2,197,815,295.
There are a few ways around it. One approach been used in various projects at JeeLabs is “varint-coding” - a loosely defined term which means that you store a variable number of bytes for each value, depending on its magnitude.
The varint format was described a while ago on the weblog, see this article. Unfortunately, it suffers from being limited to non-negative integers. Not so great when sending temperatures.
A slightly extended version of varints has now been implemented in Forth, which adds support for negative values and is nevertheless quite easy to encode and decode in other programming languages. See the new Varint documentation page for details.
This provides us with a tool to send multiple values, so let’s extend the previous example to send four ADC readings every second, measured on pins PA0..PA3.
Let’s start off with a quick interactive check that the ADC works:
adc-init ok.
PA0 adc . 11 ok.
PA1 adc . 641 ok.
PA2 adc . 640 ok.
PA3 adc . 677 ok.
The varint
package convention is that the first value is a “packet type ID” -
this lets us verify that we’re decoding the proper data, and allows us to deal
with different packet formats later.
The first task is to make sure that we have the varint
package loaded - and
while we’re at it, let’s store that code in flash memory (the usual approach
would be to add this to core.fs
):
compiletoflash ok.
!s ../flib/any/varint.fs
compiletoram ok.
If you get a bunch of “Redefine” warnings, then varint
was already present.
That’s fine.
Now we can “assemble” a packet. Let’s use ID 6. To try this out, we need to have
rf-listen
running on the receiving F103 node. Then we can enter the following
on the sending JNZ:
adc-init rf-init
6 <pkt pa0 adc +pkt pa1 adc +pkt pa2 adc +pkt pa3 adc +pkt pkt>rf
The result of repeating this a few times shows up as follows on the F103:
rf-listen
RF69 21EB2AB701FDE2803D08 8C900A8C0A9C0BB0
RF69 21EB2ABB01FD26803D08 8C800A920AA00BB4
RF69 21EB2AB901FEC2803D08 8C800A920A9A0BAE
Only 8 bytes of payload, but nearly undecypherable. We’re going to have to
decode this stuff. Luckily, that’s trivial - all we need to do is replace
type
with var.
from the varint
package:
: rxtestv ( -- )
rf-init
begin
rf-recv ?dup if
cr rf.buf 2+ swap 2- var.
then
again ;
As before, we can save this to file (”rxtestv.fs
”) and start up the receiver
as follows:
!s rxtestv.fs
rxtestv
Then, after sending a few more packets from the JeeNode Zero, we’ll get something like:
6 8 641 654 742
6 57 652 660 716
6 0 646 668 732
As expected: a packet format ID of 6, and then 4 values from the ADC channels. Success!