Low Power Wireless: 6LoWPAN, IEEE802.15.4 and the Raspberry Pi
The Internet of Things (IoT) is one of the new kids on the block. It promises connection of sensors and actuators to the internet, for data to flow both ways, and once on the internet, to become part of new and exciting business systems, reaching up into the realms of big data and artificial intelligence.
IoT applications will rely on a large and complex system. One of the components in this will be the connections between sensors and actuators and the internet. This will most likely be wireless, and it will have to be low power. If you have a thousand sensors, they will most likely be running off batteries, and you will want those batteries to last years, not days.
Low power wireless is heading in two directions right now: personal-area networks (LoWPAN) spanning up to 20–30 meters and wide-area networking (LPWAN) of up to 20 or more kilometers. The technologies at the physical layer are completely different and lead to different Linux solutions. This article deals only with LoWPAN.
The physical layer for LoWPAN is specified by IEEE802.15.4. This defines communication using various wireless bands, such as 2.4GHz, with a range of about 10 meters and data transfer rates of 250kb/s—good enough for most sensors, but not good enough to stream MP3s!
On top of IEEE802.15.4 is a variety of protocols: Zigbee, Z-Wave, Thread and so on. Of these, only the IETF 6LoWPAN is an open standard, and this is where the Linux development community has settled. This article covers only 6LoWPAN. I also ignore other wireless systems, such as Bluetooth LE.
6LoWPAN and Linux
6LoWPAN is IPv6 over IEEE802.15.4 wireless. That isn't easy. IPv6 is designed for the current internet, while IEEE802.15.4 is designed for a different environment. You don't need to worry about how this mismatch has been overcome, but it does mean you need to be aware that two different levels are dealt with here: getting two wireless devices to talk to each other and getting a networking layer talking over these devices.
The device layer is where physical hardware choices come into play. Linux supports several devices, such as the AT86RF230 series, the MRF24J40 and several others. The kernel needs to have those device drivers compiled in or available as dynamically loadable modules.
The networking layer requires 6LoWPAN support. Again, the kernel needs to have this compiled in or available as modules. These modules are the ieee802154_6LoWPAN, ieee802154 and mac802154 modules.
6LoWPAN Devices and the Raspberry Pi
The Raspberry Pi is a wonderful toy or a full-blown Linux computer, depending on your viewpoint. With its GPIO pins, it can act as a connection into the realm of sensors and actuators, while with Ethernet (and on the RPi3, Wi-Fi), it can be a part of LANs and WANs. For the IoT, it (and the Arduino) form an excellent bridge between the physical and ICT worlds. But, there are now IEEE802.15.4 modules available, and they can be used to turn an RPi into a "full-function 6LoWPAN device".
I used the RPi with the OpenLabs "Raspberry Pi 802.15.4 radio". This is an Atmel AT86RF233 radio on a small board with a header that allows it to be plugged straight onto pins 15–26 of the RPi. It can be plugged in facing out or facing in—facing in the right way to do it.
I started off using the standard Raspbian distro (dated May 27, 2016). This can be set up to recognize the radio, but—oh dear!—the 4.4 Linux kernel it uses has 6LoWPAN modules, but they don't work properly in that kernel. The IPv6 packets get corrupted even for pinging itself, so this Raspbian distro won't support 6LoWPAN.
The hunt is on then for a setup that allows the RPi to support 6LoWPAN with an AT86RF233 radio. This is painful: there are many helpful sites that are outdated or with instructions that I just couldn't get to work. I finally was pointed by Sebastian Meiling to his page "Create a generic Raspbian image with 6LoWPAN support". In summary, what is needed is an upstream Linux kernel, 4.7 or 4.8, recent firmware and suitable configuration of the /boot/config.txt file. At the time of this writing, these instructions work only for the RPi 1 and 2. The RPi 3 isn't working yet, but it may be by the time this article is published.
Installing a 6LoWPAN Kernel
For this article I'm using the OpenLabs module on the RPi 2B. For other modules and RPis, see Sebastian's page. I'm also going to assume a reasonable amount of Linux savvy in installing software and building from source.
Start by installing the latest Raspbian image. If that runs a 4.7 (or
later) kernel, you may be okay already; otherwise, you need to build and
install an upstream 4.7 kernel. You probably will need extra tools for
this, such as rpi-update
,
git
, libncurses5-dev
,
bc
and maybe development
tools that you can install using apt-get
.
Before you do anything else, make sure your system is up to date by running:
rpi-update
This will install the latest firmware bootloader.
Download a 4.7 kernel into the linux-rpi2 directory with:
git clone --depth 1 https://github.com/raspberrypi/linux.git \
--branch rpi-4.7.y --single-branch linux-rpi2
Building a kernel means compiling a lot of files and is very slow on the RPi. Most people recommend cross-compiling, but that's more complex, and I like things simple. So, I prefer to build on the RPi itself. It takes only about 5 hours, so start it up, and either go to bed or go out, listen to some jazz and stay out late.
In the linux-rpi2 directory, set up a configuration file for the RPi 2B with:
make bcm2709_defconfig
Then run menuconfig
to do two things:
1) Install the device driver as a module from the menu entry:
Device Drivers
--> Network device support
--> IEEE 802.15.4 drivers
2) Install 6LoWPAN support as a module from the menu entry:
Networking support
--> Networking options
--> IEEE Std 802.15.4 Low-Rate Wireless Personal Area
Networks support
Build the kernel and associated files with:
make zImage modules dtbs -j4
Five hours later, install the modules and dtbs files:
sudo make modules_install dtbs_install
The safest way to install the kernel is to copy it to an appropriate
location. When I run make kernelversion
in the source tree, it tells me I
have built 4.7.2. So I use that number in copying the kernel:
sudo cp arch/arm/boot/zImage /boot/kernel.4.7.2.img
That way I don't destroy any existing images, so I have a safe fallback to the previous system.
Finally, you need to tell the RPi to boot into the new kernel. As root, edit /boot/config.txt and add these lines at the end:
kernel=kernel.4.7.2.img
device_tree=bcm2709-rpi-2-b.dtb
dtoverlay=at86rf233
What does that do? First, it tells the RPi to use the new boot image kernel.4.7.2.img. Second—and this is currently ARM-specific—it tells the RPi to pick up hardware default values using the device tree system from bcm2709-rpi-2-b.dtb. And third—and this is RPi-specific—it says to add in the at86rf233 device in an additional file to the device tree file.
Finally...reboot. If all went well, you should have the new kernel running. Check this with:
uname -a
It should show something like this:
Linux raspberrypi 4.7.2-v7+ #1 SMP Fri Aug 26 15:45:29 UTC 2016
↪armv7l GNU/Linux
If it didn't boot or showed the wrong kernel, take your SD card back to somewhere else so you can comment out the lines you added to /boot/config.txt. Back on the RPi, reboot back into the default kernel, and try to figure out which step went wrong. I skipped some steps from Sebastian's guide because I didn't need them, but if your system isn't working, pay very close attention to his guide. He seems to be pretty diligent about updating it.
Setting Up 6LoWPAN
Are you there yet? Sorry, no. You've built and installed an upstream kernel
with 6LoWPAN support. You're more than half-way there though. To configure the
6LoWPAN stack, you need another tool, wpan-tools
.
Get this from GitHub:
git clone --depth 1 https://github.com/linux-wpan/wpan-tools.git
↪wpan-tools
Before you can build this though, you need
autoreconf
:
sudo apt-get install dh-autoreconf
Then in the wpan-tools directory, you can run:
./autogen.sh
./configure CFLAGS='-g -O0' --prefix=/usr --sysconfdir=/etc
↪--libdir=/usr/lib
make
sudo make install
What's going on here? Linux is part of the UNIX family of operating systems
(including BSD, among many others). They all have quirks, and source code
authors have to deal with those. There have been many tools to make this
management easier, and wpan-tools
uses
autoreconf
to build a configuration
file, then configure
to work out the specifics of your RPi system so that
when you make
your application, all of the correct
pieces are in place.
The result of this is that the application iwpan
is now in the /usr/bin
directory for use.
You're nearly there! Remember in the kernel configuration you set the 6LoWPAN and device drivers to be dynamic modules. They won't have been installed by default like you would expect modules to be. That's what all this device tree stuff is about—bringing devices into the system when it can't detect them normally. So the next step is to load the modules:
sudo modprobe at86rf230
Then lsmod
should include something like this:
Module Size Used by
ieee802154_6LoWPAN 19335 0
6LoWPAN 13191 8 nhc_fragment,ieee802154_6LoWPAN
at86rf230 22211 0
mac802154 49035 1 at86rf230
ieee802154 55698 2 ieee802154_6LoWPAN,mac802154
crc_ccitt 1278 1 mac802154
And now—ta-da!—iwpan list
shows
something like this:
wpan_phy phy0
supported channels:
page 0: 11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26
current_page: 0
current_channel: 13, 2415 MHz
cca_mode: (1) Energy above threshold
cca_ed_level: -77
tx_power: 4
capabilities:
iftypes: node,monitor
channels:
page 0:
[11] 2405 MHz, [12] 2410 MHz, [13] 2415 MHz,
[14] 2420 MHz, [15] 2425 MHz, [16] 2430 MHz,
[17] 2435 MHz, [18] 2440 MHz, [19] 2445 MHz,
[20] 2450 MHz, [21] 2455 MHz, [22] 2460 MHz,
[23] 2465 MHz, [24] 2470 MHz, [25] 2475 MHz,
[26] 2480 MHz
tx_powers: 4,3.7,3.4,3,2.5,2,1,0,-1,-2,-3,-4,-6,-8,-12,-17
cca_ed_levels: -91,-89,-87,-85,-83,-81,-79,-77,-75,-73,-71,
↪-69,-67,-65,-63,-61
cca_modes:
(1) Energy above threshold
(2) Carrier sense only
(3, cca_opt: 0) Carrier sense with energy above threshold
↪(logical operator is 'and')
(3, cca_opt: 1) Carrier sense with energy above threshold
↪(logical operator is 'or')
min_be: 0,1,2,3,4,5,6,7,8
max_be: 3,4,5,6,7,8
csma_backoffs: 0,1,2,3,4,5
frame_retries: 0,1,2,3,4,5,6,7
lbt: false
Supported commands:
...
Your 6LoWPAN device is now known to the Linux system.
Configuring 6LoWPAN
So now you have a new kernel, you have the at86rf230 device recognized, and
the 6LoWPAN networking stack is in place. The final steps are to configure
networking and bring the device up. You likely are used to Wi-Fi networks
having an SSID. IEEE802.15.4 networks have a similar concept, a PAN ID. Two
devices will be on the same network only if they have the same PAN ID. You
use iwpan
to set this:
iwpan dev wpan0 set pan_id 0xbeef
The ID of 0xbeef
isn't fixed, but every example seems to use it!
Then, you bring up the interface using normal networking tools:
ip link add link wpan0 name lowpan0 type lowpan
ifconfig wpan0 up
ifconfig lowpan0 up
What have you got now? ifconfig
returns something
like this:
lowpan0 Link encap:UNSPEC HWaddr
↪EE-0B-FB-0F-76-B9-F3-93-00-00-00-00-00-00-00-00
inet6 addr: fe80::ec0b:fb0f:76b9:f393/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1280 Metric:1
RX packets:38 errors:0 dropped:0 overruns:0 frame:0
TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:5205 (5.0 KiB) TX bytes:5205 (5.0 KiB)
wpan0 Link encap:UNSPEC HWaddr
↪EE-0B-FB-0F-76-B9-F3-93-00-00-00-00-00-00-00-00
UP BROADCAST RUNNING NOARP MTU:123 Metric:1
RX packets:58 errors:0 dropped:0 overruns:0 frame:0
TX packets:55 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:300
RX bytes:4111 (4.0 KiB) TX bytes:4904 (4.7 KiB)
The interface wpan0
is the wireless device. The
interface lowpan0
is the
6LoWPAN network device, just like eth0
, the loopback device and so on. Note how
it has an IPv6 address, but no IPv4 address—this is next-generation IP
only!
Ping!
You are done! Well, almost. There is an old B.C. comic strip where one character invents the telephone. "Who do we ring?" asks his friend. "I only invented one" is the reply. You need someone to talk to. So, do all this over again with another RPi. You did buy two RPis and two wireless modules, didn't you?
The ifconfig
command tells you the IPv6 address of the 6LoWPAN device. From
the other
device, once you have it set up, do:
ping6 -I lowpan0 fe80::ec0b:fb0f:76b9:f393 # IPv6 address of
# the other device
or:
ping6 fe80::ec0b:fb0f:76b9:f393%lowpan0
The ping6
command is the IPv6 version of
ping
. The IPv6 address of each
network interface is assigned automatically and is a link
local address.
If you have multiple interfaces, each of them can be on a network segment
with non-routable link local addresses. Hosts on these different network
segments can have the same address. These are like IPv4 link local
addresses 169.254.0.0/16, and they can't be routed across different network
segments. So in Linux, you need to specify the interface to use
(lowpan0
)
to avoid possible confusion. There are two ways of doing this: either use the
-I lowpan0
option or append
%lowpan0
to the IPv6 address.
On my system, this produces:
$ping6 -I lowpan0 fe80::ec0b:fb0f:76b9:f393
PING fe80::ec0b:fb0f:76b9:f393(fe80::ec0b:fb0f:76b9:f393) from
↪fe80::f0f9:a4ed:3cad:d1de lowpan0: 56 data bytes
64 bytes from fe80::ec0b:fb0f:76b9:f393: icmp_seq=1 ttl=64
↪time=11.6 ms
64 bytes from fe80::ec0b:fb0f:76b9:f393: icmp_seq=2 ttl=64
↪time=11.1 ms
64 bytes from fe80::ec0b:fb0f:76b9:f393: icmp_seq=3 ttl=64
↪time=10.5 ms
Success! The two devices can ping each other across 6LoWPAN.
What if it doesn't work? Well, it didn't work for me for a long time, and
working out where the failure occurred was painful. It turned out to be a
wrong kernel for 6LoWPAN. To troubleshoot, first keep running
ifconfig
. This tells you
which interfaces are getting and sending packets. It told me that the
wireless layer (wpan0
) was getting and receiving packets, but the
networking layer wasn't. Then I ran wireshark
using
selector ip6
on packets,
and it showed me errors at the network layer. The command
dmesg
gave gold,
telling me the IPv6 packets were corrupted, even when pinging myself.
In desperation, I turned to Sebastian, giving him as much information as I
could (uname
, firmware version using /opt/vc/bin/vcgencmd, contents of
/boot/config.txt, decompiling the device tree using dtc -I fs
/proc/device-tree
, and then wireshark
and dmesg
reports). He needed
only the first line: wrong kernel. But, spending time working out a detailed
report at least shows you are serious. "Duh, it doesn't work" isn't helpful
to a maintainer!
A Sensor and a Receiver
You don't really need 6LoWPAN to communicate between Raspberry Pis. Wi-Fi and Ethernet are better. But now suppose one of them is a sensor running off a battery or solar panel. Wi-Fi is estimated to drain a battery within a fortnight; whereas 6LoWPAN on batteries can be expected to run for several years. I'm simulating this here by using one of the RPis as sensor for convenience.
To follow along, you will need to set up a client-server system. Usually, people think of servers as big grunty machines somewhere, but in the IoT world, the sensors will be the servers, handling requests for values from clients elsewhere in the network.
The server is just like a normal IPv6 server as described in the Python
documentation: 18.1. socket
—Low-level networking
interface.
But note
that just as with the ping6
command above, you need to specify the network
interface to be used. This means you have to use Python 3 rather than Python
2, as this has the socket function
socket.if_nametoindex()
that allows you
to specify the IPv6 "scope id", which is the interface you use.
I don't want to complicate this article with how to add sensors to an RPi. Instead, I'll just measure the temperature of the RPi's CPU, as this can be found really easily by running this command from a shell:
vcgencmd measure_temp
This will return a string like:
temp=36.9'C
Within Python, you create a process to run this command using
Popen
and read
from the stdout
pipeline.
Here's an IPv6 TCP server that waits for connections, sends the temperature and then closes the connection:
#!/usr/bin/python3
import socket
from subprocess import PIPE, Popen
HOST = '' # Symbolic name meaning all available interfaces
PORT = 2016 # Arbitrary non-privileged port
def get_cpu_temperature():
process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
output, _error = process.communicate()
return output
def main():
s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
scope_id = socket.if_nametoindex('lowpan0')
s6.bind((HOST, PORT, 0, scope_id))
s6.listen(1)
while True:
conn, addr = s6.accept()
conn.send(get_cpu_temperature())
conn.close()
if __name__ == '__main__':
main()
And, here's a client that opens a connection and reads the temperature every ten seconds:
#!/usr/bin/python3
import socket
import time
ADDR = 'fd28:e5e1:86:0:e40c:932d:df85:4be9' # the other RPi
PORT = 2016
def main():
# scope_id = socket.if_nametoindex('lowpan0')
while True:
s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
s6.connect((ADDR, PORT, 0, 0))
data = s6.recv(1024)
print(data.decode('utf-8'), end='')
# get it again after 10 seconds
time.sleep(10)
if __name__ == '__main__':
main()
The output looks like this:
temp=37.4'C
temp=37.4'C
temp=37.9'C
What's My Server's Address?
So imagine you've now got 1,000 of these sensors scattered out in the wild somewhere, and they are all running IPv6 servers. What are their addresses? How do you talk to them? Unfortunately, the OpenLabs module generates a new MAC address each time it is booted, so it generates a new IPv6 address each time. Running multi-cast discovery is not recommended for these low power networks as it is a power drain. I will cheat a bit in the next article, but show better ways in the third article.
Conclusion
The scenario presented in the last section is still a bit unrealistic. If you have enough power to drive an RPi as a sensor, you probably have enough power for it to use Wi-Fi or Ethernet. But soon there will be genuine low power sensors using 6LoWPAN, and this article has shown you how to bring them into one particular Linux system. It's been pretty heavy going, but right now this is cutting-edge stuff, so expect to bleed a bit!
In my next article, I'll describe how to bring a 6LoWPAN network into the standard IPv6 world, and in the third article, I plan to look at CoAP, the equivalent of HTTP for low power networks.
Resources
OpenLabs Raspberry Pi 802.15.4 Radio
Python API:
socket
— Low-level networking interface
IETF RFC4944: Transmission of IPv6 Packets over IEEE 802.15.4 Networks (6LoWPAN)
6LoWPAN: The Wireless Embedded Internet by Zach Shelby, Wiley 2009
Create a generic Raspbian image with 6LoWPAN support by Sebastian Meiling