Introduction

Welcome to the Network-of-Things Applications (NoT A) Lab.

This lab will guide you through the components used to connect devices to the internet and will give you a hands-on experience using actual hardware and software as well as realistic network topologies.

We will cover topics like software development on ressource-constrained embedded devices, network protocols used in the IoT, local data processing in a so-called fog zone and cloud-based data processing.

But before we dive right in we will have to cover some basics to get you started.

As a first task click on the arrow to the right to get to the first chapter.

Basics

Congratulations for finishing the first task! Yor ability to follow basic instructions will come in handy later on.

In the course of this lab we will be using a lot of commandline programs without a graphical user interface. If you are not used to using a liux commandline or want a quick refresher the next sub-chapter will give you a brief introduction.

If you feel like a real hacker already you may also skip the next sub-chapter and go straight to working with NodeMCU and come back whenever you see fit.

Linux commandline basics

Before we start our adventure to the depths of textual programs we have to open a terminal emulator as shown in the video below.

Navigation

The commands you execute on the commandline always work relative to a directory on the filesystem. This directory is called the working directory and you can find out your current working directory by typing

pwd

on the commandline and hitting enter.

After hitting enter your terminal should show something like the following:

[user@computer ~]$ pwd
/home/user

This means that you are currently working in the /home/user directory.

Often times $ pwd (for “print working directory”) is used as an abbreviation to saying “Type pwd into a terminal emulator and hit the enter key”, the $ prefix stems from the way the terminal displays things, you should not type it in when asked to execute a program.

To demonstrate navigating the filesystem we first have to create some directories to navigate to. Execute the following commands to create the directories test_1 and test_2:

$ mkdir test_1
$ mkdir test_2

Now type $ ls (for “list”) to see the content of the current working directory. You should see, possibly among others, your newly created test folders.

[user@computer ~]$ ls
test_1
test_2

To change your current working directory you use the cd command:

[user@computer ~]$ pwd
/home/user
[user@computer ~]$ cd test_1
[user@computer ~/test_1]$ pwd
/home/user/test_1

To go up in the directory structure you can use .. in a path.

[user@computer ~/test_1]$ pwd
/home/user/test_1
[user@computer ~/test_1]$ cd ..
/home/user
[user@computer ~]$ cd test_2
[user@computer ~/test_2]$ pwd
/home/user/test_2
[user@computer ~/test_2]$ cd ../test_1
/home/user
[user@computer ~/test_1]$ pwd
/home/user/test_1

Tab completion

As typing is tedious you should, whenever possible, use the Tab ↹ key. When pressed once it will autocomplete whatever you entered as far as possible, when pressed twice it will show possible further completions. Try it by typing the following and pressing the tabulator key whenever a ↹ symbol comes up.

[user@computer ~/test_1]$ cd
[user@computer ~]$ cd te↹

(should autocomplete to test_)

[user@computer ~]$ cd test_↹↹
test_1/ test_2/

(should show options to go to)

To cancel the currently running program or to clear the current commandline you can use the key combination Ctrl+C (or Strg+C on a german keyboard). Pressing these two keys asks the running program to exit.

Reading and writing files

We will use a text editor to create a simple file. The text editor we use is called nano. To start it type the following:

[user@computer ~]$ cd
[user@computer ~]$ cd test_1
[user@computer ~/test_1]$ nano hello.txt

The last command starts an interactive text editor. For now just type hello world! and use the key combination Ctrl+X (Strg+X on a german keyboard) to exit the editor. The editor will ask if it should save the changes you made. Type Y (or J on a german system) and hit enter to save the changes and exit the editor.

To read the content of the file we just created we can either use nano to open it or the cat command to print its content to the terminal.

[user@computer ~/test_1]$ cat hello.txt
hello world!

Hint: Be careful when cating files to the terminal, sometimes when reading binary files instead of text files your terminal may end up all garbled up.

Command history

In addition to using the Tab ↹ key for autocompletions you should also use the command history. The terminal automatically keeps a log of the last commands you executed and you can navigate these commands using the arrow keys ↑↓ on your keyboard.

You can also search the history using the key combination Ctrl+R (Strg+R on a german keyboard).

NodeMCU

In this lab we will be using a small microcontroller based development board to act as our internet connected device. These microcontrollers are most commonly programmed in low level programming languages like C or C++, we will instead by using an ESP8266 microcontroller running the NodeMCU firmware, this firmware lets us program the controller using the Lua programming language which allows for faster experimenting than C or C++.

Communication

Before we can program the controller we have to be able to communicate with it. To do so please disconnect the USB connection between your Raspberry Pi and the microcontroller board, count to two and reconnect it.

Now run $ dmesg in a terminal, one of the last lines should read something like:

[100000.000000] usb 1-5: cp210x converter now attached to ttyUSB0

the last part is what we need, the device name we use to communicate to the microcontroller. If dmesg shows a different device name you should use that instead of ttyUSB0 wherever it comes up.

Now we use picocom to connect to the controller, see its output and send commands to it.

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
picocom v3.1

[…]

Type [C-a] [C-h] to see available commands
Terminal ready

If you now press enter the controller should answer with a > command prompt.

Type [C-a] [C-h] to see available commands
Terminal ready
>

To check if everything is working correctly we may now turn on an LED on the controller board using the following commands:

> gpio.mode(4, gpio.OUTPUT)
> gpio.write(4, gpio.LOW)

and turn it off again using this command:

> gpio.write(4, gpio.HIGH)

Hint: If no LED turned on and off there may be something wrong with your setup and you should seek assistance.

The controller board has an onboard LED connected between the supply voltage and pin 4 of the microcontroller. For more information on the commands you just executed consult the NodeMCU documentation.

Uploading code

Using the NodeMCU commandline for anything but basic commands quickly becomes tedious, so the next step is to upload full programs to the controller in one go.

To do so we first have to exit picocom.

Always remember to exit picocom before uploading a program.

Doing so can be a bit tricky as the process consists of two key combinations. You have to first press Ctrl+A and then Ctrl+X (Strg+A and then Strg+X on a german keyboard, respectively).

After you hit these key combinations picocom should exit with the output:

>
Terminating...
Thanks for using picocom
[user@computer ~]$

Before we can upload a project to the microcontroller we need a project to upload, a basic template is located in the /usr/src/nodemcu_base directory of your Raspberry Pis.

Copy this template to a new project directory using the following commands:

[user@computer ~]$ mkdir projects
[user@computer ~]$ cp -rv /usr/src/nodemcu_base projects/chapter_1
[user@computer ~]$ cd projects/chapter_1

The template contains three files init.lua credentials.lua application.lua before going into any detail on what the purpose of these files is we can just upload them as-is using the following command (remember that you might have to replace the ttyUSB0 part):

[user@computer ~]$ nodemcu-uploader --port /dev/ttyUSB0 upload *.lua
opening port /dev/ttyUSB0 with 115200 baud
Preparing esp for transfer.
Transferring init.lua as init.lua
Transferring credentials.lua as credentials.lua
Transferring application.lua as application.lua
All done!
[user@computer ~]$

Hint: If the command above throws any errors you should try pressing the reset button on the microcontroller board and quickly retry flashing after releasing it.

After uploading you can reconnect picocom, press the reset button on the microcontroller board and should see output like the following:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
…
init: WiFi credentials are not configured
init: startup will continue in 3s
> init: handing over to application
Hello from your NodeMCU application!
Hi from your NodeMCU application!
…

NodeMCU project structure

The template project is split into the files init.lua, credentials.lua and application.lua, we will now take a look at these files in a text editor.

init.lua

This file is executed by the firmware on the microcontroller, it is used to set up a WiFi connection and hand over to application.lua.

credentials.lua

This file contains name and password of the WiFi network to connect to.

application.lua

This file contains the application running on the microcontroller, most of your code goes in here.

Task: Read the code in application.lua and try editing the greeting_print function so that the LED is turned on whenever the application prints “Hello from your NodeMCU application!” and off whenever the application prints “Hi from your NodeMCU application!”.

NodeMCU programming model

NodeMCU is inspired by node.js, a javascript framework and server based on the concept of callback-based asynchronous programming.

This concept can take some getting used to. The following examples should give you an idea of how asynchronous programs are structured.

Synchronous vs. Asynchronous

The previos chapter ended in an example of a blinking LED program. Some of you may have already used Arduino to write a similar program:

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

The first thing to notice is that no other code can run while the loop function is running. This means that the microcontroller will sit idly for one second while the delay(1000) calls are being executed.

The code-snipped below shows the asynchronous counterpart to the Arduino code above.

function led_toggle()
   if (status == gpio.LOW) then
      status = gpio.HIGH
   else
      status = gpio.LOW
   end

   gpio.write(4, status)
end

function led_setup()
   gpio.mode(4, gpio.OUTPUT)

   -- schedule led_toggle() to run once a second
   tmr.create():alarm(1000, tmr.ALARM_AUTO, led_toggle)
end

The function led_toggle is scheduled to be executed every 1000ms using the tmr.create():alarm() call. In the meantime the microcontroller is free to run different pieces of code.

Hint: The NodeMCU firmware makes use of this property, for example for handling network connections. This is why you should avoid writing functions in NodeMCU that may take a long time to run, especially by avoiding the tmr.delay() function.

Handling input

After learning the basics of what asynchronous programming means we have to learn how they transfer to actual real-world tasks.

One real-world task is acting upon data input.

Replace the content of you application.lua file with the code-snippet below and upload it to your microcontroller.

function uart_on_char(char)
   print("Read character: "..char.." from uart")
end

function uart_setup()
   -- Whenever 1 byte of data is received
   -- run uart_on_char, do not interpret the
   -- received byte as lua command
   uart.on("data", 1, uart_on_char, 0)
end

uart_setup()

After uploading the program, resetting your microcontroller and connecting to it using picocom the microcontroller should respond to every character you enter with a line like the following:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
Read character: h from uart
Read character: e from uart
Read character: l from uart
Read character: l from uart
Read character: o from uart

Task: modify the application.lua to turn on the LED whenever the character l is received and off whenever the character d is received.

Controlling RC servo motors

Controlling RC servos with a microcontroller is rather simple, due to the minimalistic communication protocol used by these motors.

The diagram below is a mostly complete description of this protocol:

5V  ┌────┬┄┄┄┄┐               ┌───
    │    │    ┆               │
0V ─┘    └────┴───────────────┘
    0ms  1ms  2ms             20ms

The angle the servo is set to is controlled by the pulse length seen in the diagram.

  • A pulse length of 1ms means turn all the way in one direction
  • A pulse length of 2ms means turn all the way in the other direction
  • A pulse length of 1.5ms means move to the origin

To generate these pulses we will use the pwm module. Below are two functions to controll the servos.

  • servo_setup performs the necessary setup to control a servo motor on pin 2 of the microcontroller
  • servo_set sets the position of the servo on a scale from 0-100
function servo_setup()
    pwm.setup(2, 50, 75)
    pwm.start(2)
end

function servo_set(pos)
    local pos_clip = math.max(math.min(pos, 100), 0)
    local duty = (1 + pos_clip/100) / 20 * 1023

    pwm.setduty(2, duty)
end

The servo is connected using three wires:

  • The black wire is connected to GND
  • The red wire is connected to 5V
  • The white wire is connected to pin D2

Task: Connect a servo and write a program that toggles between the to endpoints once a second.

Ultrasonic distance sensors

In the lab we have ultrasonic sensors available that measure distance using the time of flight of sound.

These sensors use two pins to communicate, a trigger pin and an echo pin.

A measurement is started by a falling edge on the trigger pin, which triggers the sending of an ultrasonic pulse.

After sending the pulse the echo pin will be high until an echo is received.

The distance to an obstacle can then be calculated from the duration of the echo pulse and the speed of sound in air.

         5V ────┐   ┌────────────────────────────
Trigger         │   │
         0V     └───┘

         5V        ┌────────────────┐
Echo               │                │
         0V ───────┘                └────────────

To connect the 5V ultrasonic sensor to the 3.3V microcontroller we need basic level shifting.

The following connections need to be made:

  • Sensor GND to microcontroller GND
  • Sensor VCC to microcontroller 5V
  • Sensor TRIG to microcontroller D6

And one of the following to convert voltage levels:

  • Voltage divider

             ┌──────┐   D5     ┌──────┐
     ECHO ───┤ 10kΩ ├────┴─────┤ 15kΩ ├─── GND
             └──────┘          └──────┘
    
  • Diode

            C   A
     ECHO ───┤⯇├── D5
    

The code snipped below contains a function that, when called, performs a single distance measurement and calls the function passed as the db_done when the measurement is done.

A single argument is passed to the function:

  • nil if no echo was received
  • A number giving the measured distance in meters
function measure_distance(cb_done)
    local timeout = tmr.create()
    local echo_start = nil

    function on_timeout(t)
        -- Stop waiting for an interrupt and
        -- report failure
        gpio.trig(5)
        cb_done(nil)
    end

    function on_echo(level, when, evcount)
        -- Measure the time between the pin going high
        -- and going low to calculate time of flight
        if level == gpio.HIGH then
            echo_start= when
        else
            timeout:unregister()
            gpio.trig(5)

            local tof = when - echo_start
            local distance = tof / 2 * 343e-6

            cb_done(distance)
        end
    end

    -- When the echo ouput does not change
    -- for 200ms no echo was detected
    timeout:alarm(210, tmr.ALARM_SINGLE, on_timeout)

    -- Pin 5 is connected to the echo pin setup
    -- interrupts to measure the length of the pulse
    gpio.mode(5, gpio.INT, gpio.PULLUP)
    gpio.trig(5, "both", on_echo)

    gpio.mode(6, gpio.OUTPUT)
    -- Trigger a measurement by pulsing
    -- pin 6 high and then low for 20us
    gpio.serout(6, gpio.HIGH, {20, 20}, 1, 0)
end

Task: Call the function once a second and print the measured distance

TODO: Actually test this with the required voltage divider resistors/diodes

Networking

In this chapter we will work with the netcat commandline program, NodeMCU and the fundamental internet protocols TCP and UDP to facilitate communication between the computer and the microcontroller.

WiFi Setup

Before we can start sending IP packets to and from our microcontroller board we have to connect it to an IP network.

For this we will be using WiFi. If you, for example by using your phone, scan for WiFi networks near the lab you should find a few networks with names starting with nota_. These networks are spanned by the Raspberry Pis on your desks.

To find out which of these networks belongs to your Pi you can execute the following command:

[user@computer ~]$ cat /etc/hostapd/hostapd.conf
interface=wint0
driver=nl80211
channel=1
wpa=2
ssid=nota_x
wpa_passphrase=password_x

Of particular interest are the last two lines containing the name of the WiFi network and its password.


Next we will create a new NodeMCU project for this chapter and use the WiFi credentials to connect to the network.

First create the new project:

[user@computer ~]$ cd projects
[user@computer ~]$ cp -rv /usr/src/nodemcu_base projects/chapter_2a
[user@computer ~]$ cd projects/chapter_2a

Then use the text editor to open the credentials.lua file in your new project. After uncommenting the two lines in this file and entering the WiFi name and Password the file should look something like this:

[user@computer ~]$ cat credentials.lua
SSID= "nota_x"
PASSWORD= "password_x"

We can now upload the project to the project to the microcontroller like before:

[user@computer ~]$ nodemcu-uploader --port /dev/ttyUSB0 upload *.lua

When you now attach to the output of the microcontroller using picocom and press the reset button you should get messages like the below, showing that connecting to the WiFi network was sucessful.

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
init: connecting to AP: nota_1
init: retrying connection 2/75
init: connected to AP: nota_1
init: got IP address 192.168.94.158 via DHCP
init: startup will continue in 3s
init: handing over to application
Hello from your NodeMCU application!
Hi from your NodeMCU application!

UDP

Basics

The first internet protocol we will have a look at is UDP, or the User Datagram Protocol.

UDP is a very simple protocol on top of IP that basically only adds a notion of ports to IP packets so that multiple applications can be addressed on a single host.

NodeMCU Server

First we will be receiving UDP packets on the microcontroller boards. For this to work replace the content of the current application.lua with the following:

function on_udp_packet(ip, data, port)
   local stripped= data:gsub("\n", "")

   print("Received: "..stripped)
end

function setup_udp()
   local socket = net.createUDPSocket()

   socket:on("receive", on_udp_packet)
   socket:listen(5000)

   print("listening on port 5000")
end

setup_udp()

upload it, connect picocom and reset the board.

If everything went well the output should look something like the following:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
…
init: got IP address 192.168.94.158 via DHCP
init: startup will continue in 3s
init: handing over to application
listening on port 5000

Meaning, in this case, that the application is listening on 192.168.94.158:5000 for UDP packets.

Netcat client

To send packets to the server we will use the Netcat commandline utility.

To make netcat send UDP packets to your microcontroller it has to be started like the following:

[user@computer ~]$ nc -u 192.168.94.158 5000

Hint: The IP address and port you use in the command have to match the IP and port you see in picocom.

Hint: Open a new terminal emulator so that you can have picocom and netcat running at the same time.

Now whenever you enter text into the terminal running netcat and hit enter a UDP packet is sent to the microcontroller.

In picocom you should see output like the following:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
…
listening on port 5000
Received: Hello World from Netcat
Received: Test

Task: Adapt the application.lua to turn on an LED when the word “on” is received and off when the word “off” is received.

TCP

Basics

Next up we will be looking at TCP, the Transmission Control Protocol.

TCP is a procotol far more complicated than UDP, as in addition to ports, introduces a handshaking process, a notion of a “connection”, message acknowledgement for reliable transport, flow control, timeouts and a lot of other aspects.

Luckily these aspects are mostly abstracted away by the software we use and we can treat a TCP connection as a reliable pipe where data put in on one side will make it to the other side eventually.

NodeMCU Server

The code snipped below starts a TCP server on the microcontroller board that waits for connections.

clients= {}

function socket_to_cid(socket)
   local client_id= nil

   -- Go through the list of sockets and
   -- find the one matching the requested socket
   for cid, client in ipairs(clients) do
      if client == socket then
         client_id = cid
      end
   end

   return client_id
end

function broadcast(message)
   for cid, client in ipairs(clients) do
      client:send(message.."\n")
   end

   print(message)
end

function on_tcp_data(socket, data)
   local stripped= data:gsub("\n", "")

   local client_id= socket_to_cid(socket)
   broadcast("Client "..client_id..": "..stripped)
end

function on_tcp_disconnect(socket, code)
   local client_id= socket_to_cid(socket)

   table.remove(clients, client_id)

   broadcast("Client "..client_id.." disconnected")
end

function on_tcp_connect(socket)
   socket:on("receive", on_tcp_data)
   socket:on("disconnection", on_tcp_disconnect)

   table.insert(clients, socket)

   local client_id= socket_to_cid(socket)
   broadcast("Client "..client_id.." connected")
end

function setup_tcp()
   local server = net.createServer()

   server:listen(5000, on_tcp_connect)

   print("listening on port 5000")
end

setup_tcp()

Replace the content of you application.lua with the code above, upload it, start picocom and reset the microcontroller.

Netcat client

You can now open a new terminal and connect to the server using netcat (note the missing -u switch compared to UDP).

[user@computer ~]$ nc 192.168.94.158 5000

Task: Using multiple terminals and netcat sessions and the sourcecode find out what the program does.

Task: Edit the program so that the first client can turn on the LED when she sends the “on” command and the second client can turn off the LED when she sends the “off” command.

MQTT

While building IoT applications directly on top of TCP or UDP is certainly possible the process will be unnecessary complicated and the resulting application will not be compatible to other applications.

One should instead use a common IoT communication protocol. While there are a few emerging protocols for different usecases we will concentrate on MQTT.

MQTT works on top of TCP and is based on a broker, publishers of information and subscribers to informations.

MQTT Subscribe

In this chapter we will connect your microcontroller board to the MQTT broker already running on your Raspberry Pi.

For this to work the microcontroller has to know where to connect to to reach the broker. So the first steps will be creating a new project folder for this chapter and adding the connection informations to it.

First create the project folder:

[user@computer ~]$ cd projects
[user@computer ~]$ cp -rv /usr/src/nodemcu_base projects/chapter_3_sub
[user@computer ~]$ cd projects/chapter_3_sub

now you should edit the credentials.lua like you did in the previous chapter and in addition to that add the following three lines to the bottom of the file:

MQTT_HOST= "192.168.94.1"
MQTT_PORT= 1883
MQTT_ID= "ESP1"

These set the ip address and port number of the broker and the identification the microcontroller should use.


The next step is to replace the content of the application.lua with the following:

function mqtt_on_message(mc, topic, data)
   print("Topic "..topic..": "..data)
end

function mqtt_on_connect(mc)
   print("MQTT connection successful")

   mc:subscribe("/led", 0)
end

function mqtt_on_failure(mc, reason)
   print("MQTT connection failed: "..reason)
end

function mqtt_setup()
   local mc= mqtt.Client(MQTT_ID)

   mc:on("message", mqtt_on_message)
   mc:connect(MQTT_HOST, MQTT_PORT, 0, mqtt_on_connect, mqtt_on_failure)
end

mqtt_setup()

Then you are ready to upload the files, connect picocom and reset the microcontroller.

If everything went well the output should look something like the following:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB0
…
init: connected to AP: nota_1
init: got IP address 192.168.94.158 via DHCP
init: startup will continue in 3s
init: handing over to application
MQTT connection successful

To send messages to the topic the microcontroller is subscribed to we use the mosquitto_pub commandline program.

The following command will send the message “MyMessage” to the topic “/mytopic”.

[user@computer ~]$ mosquitto_pub -h localhost -t /mytopic -m MyMessage

Task: find the topic that the application subscribed to and send a message to that topic.

Task: make the topic actually controll the state of the LED.

MQTT Publish

Next we will program a microcontroller to publish information for the previous application to consume.

To do so we will connect a second microcontroller board to the Raspberry Pi. In order to reduce confusion later on on which board you are connect to you should open a new terminal emulator now, dedicated to working on the second microcontroller board.

Now connect the microcontroller board to your Raspberry Pi (disconnect and reconnect if it is already connected) and run dmesg like in the first chapter to find the name to use with picocom and nodemcu-uploader.

[user@computer ~]$ dmesg
…
[100000.000000] cp210x 1-9:1.0: cp210x converter detected
[100000.010000] usb 1-9: cp210x converter now attached to ttyUSB1

Now create a project folder for the publishing side of our application, based on the code written earlier:

cd projects
cp -rv chapter_3_sub/ chapter_3_pub
cd chapter_3_pub

As every client connect to the MQTT broker needs its own ID you have to edit the credentials.lua file in the chapter_3_pub folder and replace "ESP1" with "ESP2".

You are now ready to edit the application.lua in the chapter_3_pub folder. You can remove the mqtt_on_message function as it will not be used and replace the mqtt_on_connect function with the following code snippet:

function mqtt_on_connect(mc)
   print("MQTT connection successful")

   local status = gpio.HIGH

   function gpio_check(timer)
      local level = gpio.read(1)

      if level ~= status then
         status= level

         if level == gpio.HIGH then
            mc:publish("/led", "on", 1, 0)
         else
            mc:publish("/led", "off", 1, 0)
         end
      end
   end

   gpio.mode(1, gpio.INPUT, gpio.PULLUP)

   print("Install D1 watcher")

   local t = tmr.create()
   t:register(100, tmr.ALARM_AUTO, gpio_check)
   t:start()
end

Upload all .lua files in the chapter_3_pub folder to the second microcontroller board, connect picocom to the second board and press the reset button on the second board.

If everything went well the output should look something like this:

[user@computer ~]$ picocom -b 115200 /dev/ttyUSB1
…
init: connected to AP: nota_1
init: got IP address 192.168.94.159 via DHCP
init: startup will continue in 3s
init: handing over to application
MQTT connection successful
Install D1 watcher

Task: find out what the code does and use a piece of wire connected to GND to make the microcontroller publish messages. If everything went well you should be able to controll the LED on the first board by connecting a wire on the second board.

Fog computing

In this chapter we will make our microcontroller board dumb devices that do one thing and do that one thing well like sending sensor data to the MQTT broker or taking messages from the broker and controlling actuators.

We will introduce a new component in our network that does the computing required to link the dumb devices.

For this we will be using Node-RED.

Node-RED

Node-RED provides “Flow-based programming for the Internet of Things”, we will find out what that means by diving right in.

Node-RED is already installed on your Raspberry Pis, so all you need to do is start it on the commandline:

[user@computer ~]$ node-red
Welcome to Node-RED
===================
…
1 Jan 00:00:09 - [info] Starting flows
1 Jan 00:00:09 - [info] Started flows
1 Jan 00:00:09 - [info] Server now running at http://127.0.0.1:1880/

You can now open the Node-RED interface by right-clicking on the URL in the last line and selecting “Open Link”.


The video below shows how to setup your first Node-RED flow:

Task: Try replicating the steps seen in the video.


So what did just happen there?

First we start with the blank Node-RED interface.

Node-RED interface

Then we drag the “inject” and “debug “nodes from the pool of available nodes into our flow window.

Node-RED inject inject

Next we connect the Nodes. That means whenever the “inject” node produces output it will go into the input of the “debug” node.

Node-RED connect nodes

Next we start the flow…

Node-RED deploy

… open the debug output…

Node-RED debug output

… and trigger the inject node.

Node-RED

The inject node sends, via the connection we made, a message to the debug node and the debug node prints that message to the debug output window.

Node-RED and MQTT

The video below shows you how to connect Node-RED to the MQTT broker running on the Raspberry Pi:

Task: Try replicating the steps seen in the video.


Okay, once again: what did just happen there?

First we start with an empty flow and add an MQTT subscription node.

Add Subscribe Node

Next we set up the MQTT broker configuration to connect to the broker running on localhost:1883.

Configure broker

Then we configure the node to subscribe to the /led topic.

Subscribe to led topic

Next we set up an MQTT publish node to use the same broker and to publish to the /led topic.

Publish to led topic

Finally we add a debug output node and an injection node.

Add debug nodes

The inject node will, by default, inject a timestamp message, we configure it to instead publish the string on whenever it is triggered.

Inject “on”

The image below shows the flow in action: the injected message is sent to the broker, received by the subscription node and displayed by the debug node.

Run MQTT flow


Now that we know how to use Node-RED with MQTT we can make it actually do something.

Task: Add an injection node that injects the string off.

Task: Connect the microcontrollers that you programmed in the last chapter and controll the LEDs from Node-RED.


Later on we want to make our setup more flexible by routing all the messages through Node-RED.

Right now we will prepare that step by decoupling the functionality of the publishing microcontroller and the subscribing microcontroller.

Task: Edit the program on the publishing microcontroller to publish to the topic /button instead of the topic /led.

The publishing microcontroller should now no longer be able to controll the LED.

Task: In Node-RED: subscribe to the /button topic and, using a debug node, make sure that messages are correctly received there.

Task: In Node-RED: forward messages from the /button topic to the /led topic so that the publishing microcontroller can control the LED again.

Fog computing scenario

In this subchapter we will move the processing from our sensor and actuator nodes to the Raspberry Pi, acting as our fog node.

To do so we will use custom functions, written in javascipt, in Node-RED.

The Video below gives you an overview of how a function node is added to Node-RED.

This is the code snipped pasted into the function block 45 seconds into the video:

var led_status= context.get('led_status');

if (led_status === undefined) {
    led_status = 'off';
}

if (msg.payload === 'toggle') {
    if (led_status === 'on') {
        led_status = 'off';
    }
    else {
        led_status = 'on';
    }
}

context.set('led_status', led_status);

var out = {'payload': led_status};

return out;

Task: Recreate the flow shown in the video and, by reading the sourcecode and analyzing the behaviour, find out what the function node does.

Task: Change the function block so that, in addition to the toggle functionality, injecting on and off messages sets the LED state to on and off, regardless of previous state.


Now we want to integrate the function block into our hardware setup.

Task: Edit the program on your publishing microcontroller to send pressed to the /button topic whenever the button input goes from unpressed to pressed.

Task: Edit the function block in Node-RED to toggle the LED state when the string pressed is received instead of toggle.

Task: Add MQTT subscription and publishing nodes to the flow so that pressing the button on the publishing microcontroller toggles the LED on the subscribing microcontroller.

Task: Add a delay node to delay the reaction between pressing the button an changing the LED by two seconds.


TODO: Do more complicated stuff with, for example, delays or triggers.

TODO: Change publisher to read another sensor, like ultrasonic distance or temperature and publish measurements in a generic format like cm or °C.

TODO: Change subscriber to control another actuator than the LED and take commands in a generic format.

Cloud Computing

In this chapter we will have a look into the world of cloud computing.

HTTP

HTTP is an internet protocol on top of TCP that was initially designed to transfer websites over the internet.

Nowadays HTTP has become an ubiquitous protocol used in a lot of applications like, as we will see, IoT scenarios.

Before version 2.0 HTTP was a simple text based protocol, so simple in fact that will be manually sending an HTTP request to a server using netcat.

Start netcat on the commandline and connect to an HTTP server using the following command (port 80 is the default port for unencrypted HTTP):

[user@computer ~]$ nc nota-cloud 80

Now enter the following text, making sure to press the enter key whenever you see the symbol:

GET / HTTP/1.1⏎
Host: nota-cloud⏎
⏎

The output should now look something like the following:

[user@computer ~]$ nc nota-cloud 80
GET / HTTP/1.1
Host: nota-cloud

HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Mon, 01 Jan 2018 00:00:01 GMT
Content-Type: text/html
Content-Length: 986
Last-Modified: Mon, 01 Jan 2018 00:00:00 GMT
Connection: keep-alive
ETag: "deadbeef-123"
Accept-Ranges: bytes

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <title>Unencrypted Nota Server content</title>
</head>
 <body>
  <h1>Unencrypted NoTA Server content</h1>
  <ul>
    <li><a href="https://nota-cloud/">Encrypted version</a></li>
    <li><a href="/nota-ca.crt">Certificate for encrypted page</a></li>
  </ul>
 </body>
</html>

In this output we can see most things we have to know about HTTP.

Request Header

The request header is the part we sent to the server:

GET / HTTP/1.1 Host: nota-cloud

It contains:

  • The request method, in this case we want to GET a ressource from the server, we could also POST ressources to the server or DELETE ressources if the server lets us.
  • The ressource path, in this case we want to GET the index /, we could also request other ressources like ressource.html.
  • The request header parameters, in this case just the Host header that tells the server which host we expect to talk to.

Request Body

When POSTing or PUTing ressources to the server we want to include actual data in our request. This would be sent after the request headers.

In this case we did not send any data to the server.

Response Header

The response header is the first part of the transfer sent by the server:

HTTP/1.1 200 OK Server: nginx/1.10.3 Date: Mon, 01 Jan 2018 00:00:01 GMT Content-Type: text/html Content-Length: 986 Last-Modified: Mon, 01 Jan 2018 00:00:00 GMT Connection: keep-alive ETag: "deadbeef-123" Accept-Ranges: bytes

The response header contains:

  • The status code, in this case everything went well and we got a 200 OK code, other common codes are 404 Not Found or 500 Internal Server Error.
  • The response header parameters, in this case for example the Server parameter that tells us that the server software is called nginx.

Response Body

Everything following the response headers is part of the requested ressource, in this case a HTML page intened to be rendered by a web browser, containing links to other ressources.

HTTPS

Instead of bare TCP HTTP can also be used with an encrypted TLS layer on top of TCP, this is secure HTTP or HTTPS.

Using HTTPS instead of HTTP makes sure that no unauthorized user can impersonate the server and that the content of the transfer can not be intercepted by third parties.

Nowadays unencrypted communication like HTTP over the public internet is only used in legacy setups and is highly discouraged.

Do not run unencrypted services over the public internet!

Do not run unencrypted services over the public internet!

Do not run unencrypted services over the public internet!

REST

In this lab we will be using REST over HTTPS to send data to our cloud server.

In REST we use the appropriate HTTP method to transfer state from and to the server.

The most important methods are:

  • GET to get ressources from the server.
  • DELETE to delete a ressource on the server.
  • POST to create a new ressource on the server.
  • PUT to replace a ressource with a new one.

We will now manually use some of these methods to exchange information with the cloud server.

But first we will find the name of your device as we will use it to identify us later on. Execute the command below and remember its output, it will be something like nota-1 or nota-2.

[user@computer ~]$ uname -n
nota-x

Now open the cloud application in a new tab, when you successfully send data later on you should see it appear there (although you may have to refresh the page).

POST

First we will send a new ressource to the server using the POST method.

Replace the nota-x part in the URL below by the name of your device, as found out before.

[user@computer ~]$ http -v POST https://nota-cloud/api/log/nota-x value:=1

Task: Execute the command multiple times with different values and observe the cloud application.

GET

Next we want to get back the information sent to the server using the GET method.

First we request a list of device names that sent data to the server:

[user@computer ~]$  http -v GET https://nota-cloud/api/log
GET /api/log HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: nota-cloud
User-Agent: HTTPie/0.9.8



HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 40
Content-Type: application/json
Date: Mon, 01 Jan 2018 00:00:00 GMT
Server: nginx/1.10.3
Strict-Transport-Security: max-age=15768000

{
    "keys": [
        "nota-1",
        "nota-2",
        "nota-3"
    ]
}

We can see that the nota-1, nota-2 and nota-3 ressources exist on the server.

Next we can request one of these ressources from the server:

[user@computer ~]$ http -v GET https://nota-cloud/api/log/nota-1
GET /api/log/nota-1 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: nota-cloud
User-Agent: HTTPie/0.9.8



HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 5343
Content-Type: application/json
Date: Mon, 1 Jan 2018 00:00:00 GMT
Server: nginx/1.10.3
Strict-Transport-Security: max-age=15768000

{
    "values": [
        {
            "timestamp": 1500000000.000000,
            "value": 1.0
        },
        {
            "timestamp": 1500000001.000000,
            "value": 2.0
        },
        …
    ]
}

Note: we did not perform any authentication of the user, for example using an username and password, so anyone can POST and GET data to and from the server. This was done as a simplificiation in the lab and is obviously bad practice.

Do not run unauthenticated services over the public internet!

JSON

In the response header we can see a Content-Type: application/json line that specifies the file type of the response body to be JSON.

JSON is a common internet data format based on the way JavaScript represents data objects.

In the response above we see the main JSON components.

  • Collections of name/value pairs: {"name_1": "value_1", "name_2: "value_2"}
  • Numbered collections: ["value_1", "value_2", "value_3"]
  • Strings: "text"
  • Numbers: 1.0

Names in collections are always strings, values can be any other type, even other collections allowing for nested structures.

How entries in a JSON file are represented and accessed depends on the programming language and library used but the following pseudo-code interaction should give you an intuition:

> var json_file = {
>    "values": [
>        { "timestamp": 1500000000.000000, "value": 1.0 },
>        { "timestamp": 1500000001.000000, "value": 2.0 }
>    ]
> };

> json_file["values"]
[
   { "timestamp": 1500000000.000000, "value": 1.0 },
   { "timestamp": 1500000001.000000, "value": 2.0 }
]

> json_file["values"][0]
{ "timestamp": 1500000000.000000, "value": 1.0 }

> json_file["values"][0]["timestamp"]
1500000000.000000

Fog to Cloud

In this subchapter we will link out Node-RED based Fog setup to an existing cloud application using HTTPS, REST and JSON.

First we will generate a Node-RED flow that produces appropriately formatted JSON objects and then we will send these fragments to the cloud application.

Generating Data

First we will need to generate data to send to the cloud application.

Task: Based on the “Fog Scenario” subchapter write a function block that behaves like in the video below. The node should output a number that increments whenever a message with the payload “pressed” is received.

Generating JSON

Next we will want a node that formats the data in a format suitable for the cloud application.

The following code, when placed in a function block, does just that:

var obj = {'value' : msg.payload};
var out = {'payload' : obj};

return out;

The output is of the format {'value': 1}, ready to be sent to the cloud application.

Sending HTTP Requests

The video below shows you how to setup Node-RED to send an HTTP POST request to the https://nota-cloud/api/log/nota-1 endpoint whenever a button press event is received.

Task: Add an HTTP request block to your flow. Set the endpoint address to the one corresponding to you device name.

Cloud Scenario

Finally we want to also receive data from the cloud, to do so we will use HTTP GET requests.

We will create a new Node-RED flow that periodically sends request to the server and acts on the received data.

The video above uses an inject node that sends an empty message every five seconds to trigger a request to the server.

The response is then formatted using function block containing the following code:

var values = msg.payload['values'];
var last = values[values.length - 1];
var value = last['value'];

var out = { 'payload' : value };

return out;

Task: Replicate the flow setup shown in the video. Replace the endpoint configured in the http request node by the one corresponding to your device name.

Task: By reading the sourcecode and experimenting find out what the flow does and what the output of the “Formatter” function node is.

Task: Control a microcontroller based on the output of the “Formatter” block. E.g. turn on a light after a certain number of button presses.

Group phase

Step 1: Choose a sensor you would like to use and, using the microcontroller, MQTT, Node-RED and HTTP build a complete chain to send sensor readings to the cloud.

Step 2: Fetch sensor readings from another group from the cloud and control an actuator of your liking based on the values you get.

Example: One group builds a thermometer that publishes temperatures to the cloud. You take the readings from that sensor and, once it gets too hot, use a servo motor and a piece of paper to fan air.

Epilogue

This was it, the Network-of-Things Engineering (Nota) Lab, we hope you enjoyed it and may have even learned something.

Everything about the Nota Lab is free and open source and you are encouraged to participate in its development.

If you would like doing so you may meet us on the Nota Lab project page.