Low Power Wireless: 6LoWPAN, IEEE802.15.4 and the Raspberry Pi

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

______________________

Jan Newmarch has written many books and papers about software engineering, network programming, user interfaces and artificial intelligence, and he is currently digging into the IoT.