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
bc and maybe development
tools that you can install using
Before you do anything else, make sure your system is up to date by running:
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:
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:
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,
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
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
autoreconf to build a configuration
configure to work out the specifics of your RPi system so that
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
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
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:  2405 MHz,  2410 MHz,  2415 MHz,  2420 MHz,  2425 MHz,  2430 MHz,  2435 MHz,  2440 MHz,  2445 MHz,  2450 MHz,  2455 MHz,  2460 MHz,  2465 MHz,  2470 MHz,  2475 MHz,  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.
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
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
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)
wpan0 is the wireless device. The
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
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?
ifconfig command tells you the IPv6 address of the 6LoWPAN device. From
other device, once you have it set up, do:
ping6 -I lowpan0 fe80::ec0b:fb0f:76b9:f393 # IPv6 address of # the other device
ping6 command is the IPv6 version of
ping. The IPv6 address of each
network interface is assigned automatically and is a link
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
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
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
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
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
socket —Low-level networking
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:
This will return a string like:
Within Python, you create a process to run this command using
Popen and read
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.
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.
6LoWPAN: The Wireless Embedded Internet by Zach Shelby, Wiley 2009