Showing posts with label MQTT. Show all posts
Showing posts with label MQTT. Show all posts

Using a different tool for each job and linking them together with MQTT - part 4

Something needs to happen on the screen when you push the buttons you can see here. Except the buttons are connected to an Arduino not the Raspberry Pi that is connected to the screen.

The sequence goes something like this:




  1. Push the green button and the little trapdoor opens.
  2. Put a container in the revealed space and push the button again.
  3. It is drawn inside the machine.
  4. The trapdoor closes behind it.
  5. An 'analysis' of the contents of the container begins.


I did a little video while I was testing the mechanism. The mechanism is printed in natural translucent PLA and there are some Neopixels glued on the outside so that it can indicate the 'status' of the process. If you put in a container that has already been analysed it spits it out again while glowing red.


Going back to the start though, the Raspberry Pi needs to know you've pushed the green button. We've already established in parts 1 & 2 that the Arduino is plugged in to the Raspberry Pi over USB and that a small Python script looks for serial output from the Arduino, pushing anything it gets into an MQTT topic 'arduino/out/'. Push the green button and it sends 'openLid'.

The user interface is running as a set of web pages in Chrome browser. Unless something makes them they'll set there stoically until the user clicks on a link. What's great is that Paho have made a simple Javascript MQTT client library available and this can be used to automate interaction with these pages. The library uses Websockets to connect rather than a direct MQTT connection but we've already made sure the server supports this.


Using a different tool for each job and linking them together with MQTT - part 2

Now we have a working MQTT server, it's time to start making use of it.

As I was planning to use MQTT to broker messages between Web server CGI scripts, Javascript in Chrome web browser and an Arduino the obvious way to do this is push data at the USB serial port of the Arduino.

Normally this port is used for uploading the Arduino sketch to the board and sometimes people end up filling it with debug/status messages once the sketch is running. However there's a long history of it being used as an actual way to control stuff.

So I threw together a piece of 'middleware' that shuffles data between some MQTT topics and the USB serial port.

#!/usr/bin/python
import time
import os
import mosquitto
import serial
# commands to the arduino are as follow...
# R red LEDs
# G green LEDs
# B blue LEDs
# N no LEDs
# O open/go
# P power off
arduinoBootupTime = 5
arduinoIsBooted = 0
debug = True
# Define the various MQTT callback functions as this is an event driven model
def on_connect(mosq, obj, rc):
if debug:
print("rc: "+str(rc))
return
def on_message(mosq, obj, msg):
if debug:
print(msg.topic+" "+str(msg.payload))
arduino.write(str(msg.payload))
return
def on_publish(mosq, obj, mid):
if debug:
print("mid: "+str(mid))
return
def on_subscribe(mosq, obj, mid, granted_qos):
if debug:
print("Subscribed: "+str(mid)+" "+str(granted_qos))
return
def on_log(mosq, obj, level, string):
if debug:
print(string)
return
#
time.sleep(arduinoBootupTime)
# Connect to the local MQTT server to pass stuff to/from a browser
mqttc = mosquitto.Mosquitto()
mqttc.connect("localhost", 1883, 60)
mqttc.on_message = on_message
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish
mqttc.on_subscribe = on_subscribe
# Subscribe to the topic that sends commands TO the Arduino
mqttc.subscribe("arduino/in", 0)
while 1:
if debug:
print "Trying to connect to Arduino\n"
arduino = serial.Serial('/dev/ttyUSB0',115200,timeout=1)
while arduino.isOpen():
if arduinoIsBooted == 0:
if debug:
print(arduino.name) + " connected - Giving " + str(arduinoBootupTime) + "s for it to bootstrap"
time.sleep(arduinoBootupTime)
arduinoIsBooted = 1
mqttc.loop()
arduinoOut = arduino.readline()
if len(arduinoOut) > 0:
if debug:
print "Received " + arduinoOut.rstrip() + " from the Arduino"
if arduinoOut.rstrip() == 'powerDown':
powerOffFile = open('/tmp/poweroff', 'a')
powerOffFile.close()
else:
mqttc.publish("arduino/out", arduinoOut.rstrip())
arduino.close()
time.sleep(10)

This is about as simple as things can be and mostly cribbed from example scripts. It waits a while to give the Arduino time to boot as the USB port attaching to Raspberry Pi will cause it to reset. Then it tries to connect and loops round forever passing messages back and forth.

The meat of this is in two functions, the first of which gets triggered as a callback when an MQTT message arrives...

def on_message(mosq, obj, msg):
arduino.write(str(msg.payload))
return
This simply sends the message straight to the Arduino. Coming the other way is almost an exact reverse except we're using newlines to mark the end of messages so we can read them with 'readline'. This means we need to strip them off before publishing to MQTT. Python has a handy function for this in 'rstrip'.

arduinoOut = arduino.readline()
if len(arduinoOut) > 0:
if arduinoOut.rstrip() == 'powerDown':
powerOffFile = open('/tmp/poweroff', 'a')
powerOffFile.close()
else:
mqttc.publish("arduino/out", arduinoOut.rstrip())
There's another little if/else in here to handle the shutdown sequence. I used the VERY simple method of writing a file to /tmp/ if the Raspberry Pi needs to shut down, which is controlled by the Arduino. I could have had a second script subscribed to the topic for this, but this keeps all the MQTT code in one place. Because the Pi uses memory for /tmp/ instead of saving to its SD card, this safely disappears once the machine is shut down.

That's about it, things coming out of the Arduino USB serial port end up in the topic '/arduino/out/' and stuff in the topic '/arduino/in/' get sent to the serial port. The test for the existence of the Arduino serial port '/dev/ttyUSB0' means it loops round forever trying to reconnect if the Arduino get unplugged or resets.

To make this run at startup I put it into /etc/rc.local which is another quick and dirty 'make it work' solution. It works. This is not a server it's a prop, so I'm being cavalier with scalable/secure ways of doing things.

Using a different tool for each job and linking them together with MQTT - part 1

I've recently finished a quite complicated prop that needed to have a 'user interface' and thought I'd put down my thoughts on how I built it.

At a high level what we've got is a Raspberry Pi doing the user interface using Chromium web browser in kiosk mode, an Arduino Nano doing the 'physical computing' and a Teensy microcontroller acting as a custom keyboard for interaction with the Raspberry Pi.

What I've done is in principle inefficient as I could have done it all with the Raspberry Pi. However using different technologies like this is a way to compartmentalise bits of the project and use the technology you're most comfortable with for each part.

I've already used MQTT to tie things together before so it was an obvious thing to use again. It is implemented with a very simple protocol that many things are capable of understanding, including diminutive memory-constrained microcontrollers like an Arduino or ESP8266. Also, the idea of bringing data into MQTT from a hodgepodge of sources to tie things together is not a foreign concept at all, it's pretty much designed for this.

What is MQTT?

In principle the MQTT Wiki is a good point to start but like a lot of documentation in the open source community assumes a chunk of pre-knowledge and is full of gaps. So I'm going to re-invent the wheel here and describe it again in fairly plain language...

MQTT is just a way to send and receive messages. These messages can be pretty much anything you want, text, images, sounds, any arbitrary binary data. In principle they can be as large as you like but if something listening on the other end doesn't have enough memory to receive it then it'll definitely fail and probably crash or lock up.

For MQTT to work it needs a server (called a broker) that everything connects to. The broker makes sure that messages get where they need to go. A commonly used server is Mosquitto and it's what I've used. Be aware that the version which installs by default on a Raspberry Pi (probably other Linux distributions too) is old and compiled without Websockets support. You should use the current version from the Mosquitto developers or you will be limited in which clients you can use. More on that in a bit.

Topics, subscribe, publish, LWT and QoS

There are a load of clients available, Python, Javascript, Arduino, NodeMCU/Lua etc. etc. and they all use the same terminology when you want to do something with them.

Topic

A topic is a 'channel' for messages. There can be an aribtrary number of these on a broker. Unless you do specific configuration on the broker to lock things down you can create/destroy these arbitrarily by sending to or listening for data on a topic. In my application I've got topics called 'arduino/in' and 'arduino/out' for sending to and receiving from the Arduino respectively. All topics can be 'bidirectional' ie. you send and receive on the same topic from a client, but that can complicate your code as you need to process your own messages coming back at you. Which is why I'm using topics in a 'unidirectional' manner.

The broker normally handles creating topics and tidying up afterwards automatically, again unless you want to control this.

Subscribe

When you want to receive messages from a topic, you 'subscribe' to it. Depending on the programming language you use it is likely this happens as a 'callback'. This means that when you subscribe you create a function that gets run every time a message comes in on the topic. Your code needs to be able to deal with being arbitrarily interrupted when this happens.

Publish

When you want to send a message to a topic you 'publish' it. There's very little more to be said, you publish and it appears.

LWT

The broker periodically checks to make sure any clients it has are contactable. You can optionally set a 'Last Will and Testament' when you connect the client, which publishes a message to the topic of your choice if the client is no longer contactable. This is a very simple way to check if a particular client is online. I did not use this in my application.

QoS

When you publish or subscribe to a topic you can specify the 'quality of service' on the connection. This comes as...
  • QoS 0: At most once, which is unreliable and the client should receive it but this is not guaranteed
  • QoS 1: At least once, which is reliable. However the client may receive duplicates
  • QoS 2: Exactly once, which is reliable.
Given I was dealing with two clients running on the same device as the broker I left things as QoS 0 for my application. Some clients such as the NodeMCU one only support QoS 0.

Installing Mosquitto on a Raspberry Pi

Do not be tempted to install it from the standard Raspbian repository with 'apt-get install mosquitto'. This will work but some clients will fail with unhelpful errors, particularly the NodeMCU and Javascript ones. The Javascript client requires Websockets support and NodeMCU needs MQTT v3.1.1 both of which are missing in the standard build.

There's a handy guide to installing a newer version with Websockets support here, but in case this goes away here's the potted recipe for the current version of Raspbian (Jessie) at time of writing.

wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key
cd /etc/apt/sources.list.d/
sudo wget http://repo.mosquitto.org/debian/mosquitto-jessie.list
sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install mosquitto
Once this is installed, have a look in the file  /etc/mosquitto/mosquitto.conf and add the following line below the default 'listener'. We don't need Websockets yet, but we will later.
listener 1883

listener 9001
protocol websockets
Then restart the service with...
 sudo service mosquitto restart

Testing Mosquitto works

This is fairly easy, but first you need to install some Mosquitto clients...
sudo apt-get install mosquitto-clients
Start one session to your server and run the following command. It'll sit there waiting for messages to come in on the topic called "test".
mosquitto_sub -t 'test'
In another session, run the following command to send a message to the same topic.
mosquitto_pub -t "test" -m "Hello world!"
You should get "Hello world!" come up in the first window. It really is that simple to send messages back and forth, the client defaults to connecting to the local machine.

There was a lot of work to achieve this, but now you've done this you could have two wirelessly connected NodeMCU microcontrollers sending messages to each other via the broker and make things happen remotely. Or as we'll see in the next part, by clicking a button on a web page you can make things move that aren't directly connected to the web server.

This is all without having to create your own protocol to do this because MQTT is widely supported. I've done that kind of thing in the past and it was significantly time consuming.

Using ESP8266, NodeMCU and MQTT to create a wirelessly connected 'installation' - paused

It's been a while since an update. We had been due to play the game I was building for at the end of June but we've had to postpone.

My friend Mik, who is the driving force behind the game, became very ill and while we could have finished off the work and played it didn't seem right.

Nonetheless I've now got ten Wifi connected RFID readers that talk MQTT and emulate the sort of thing you see on security doors. Blinkenlight, buzzers etc.

They're a bit chunky but then they're hand built from hobbyist modules and have a 3xAA batteries in to drive them. So chunk was kind of unavoidable.

Also I've built a faux CCTV system with some Wifi IP cameras and a 'portal' that uses the same RFID cards to log you in. The portal is a single page full of JavaScript which links to the various cameras and either shows or hides them based on which card you present. So it's not really secure but it's for a game after all.

The setup uses a cheap eBay USB card reader and these just emulate a keyboard. When you present a card it sends the card ID and presses enter. A little bit of JavaScript to capture this and it looks like magic.

Implementing this all in JavaScript worked way more easily than I expected and I'm really looking forward to using this in a game.

We were throwing away two prehistoric Compaq TC1100 Wintel slate PCs from work and I managed to grab them. With Ubuntu Mate installed they work just great, even the horrible stylus is functional. What's great about them is it stops me building a 'high tech terminal' prop as they look like one without really looking exactly like a conventional laptop.

The whole thing isn't 100% finished, I'd been burning the midnight oil a bit and when it got postponed I stopped. Once we've got a new date for the game I'll get back on the horse.

In the meantime though I presented at the London Arduino Meetup about some of my previous work.

There's a video of my full presentation here, but DON'T WATCH THIS IF YOU PLAN TO PLAY THE NEXT WAYWARD SONS GAME AS THERE ARE MASSIVE SPOILERS IN THE Q&A.

Presentation Spoiler: I say 'um' too much.

There are also some nice photos on the organiser's flickr feed.

Now I'm freed up to work on other things, we've got the season of Lasertag LARP ahead and I seem to have managed to break/kill several of my 'tag weapons. I think I'll be fixing these this weekend.

Using ESP8266, NodeMCU and MQTT to create a wirelessly connected 'installation' - part 3

Producing lots of the same thing has been this week's task.

I spent a load of time messing around designing a 3D printed enclosure but kind of ended up with a box when my more ambitious design didn't really work. Still there are little pegs and clips to hold the modules loosely, which I've then fixed in place with some hot glue.

What I've got in each unit is a ESP-01 Wifi module, Arduino Pro Mini, MFRC522 RFID reader/writer, LDO regulator, Piezo sounder and a couple of big bright 10mm LEDs.

All of this is tested and working together with proof of concept code. Tomorrow I solder up the wiring looms.

Once I've done these ten, I've got another batch of similar, but slightly different stuff to design an enclosure for and assemble. I've learned a bunch of lessons while making this enclosure so it should take me much less time.

Using ESP8266, NodeMCU and MQTT to create a wirelessly connected 'installation' - part 2

This weekend was time for some field tests of the networking component.

I'd had this working at home for a while but we're setting up at an Airsoft site which is a fairly dense wood with large changes in elevation. There's also an assortment of fake buildings bashed together from wood and for some reason, old aluminium garage doors.

The site is roughly 90 acres in size and while we won't be using all of it we are going to be using the areas that have buildings. The radio tech I used last year really struggled with propagation through the trees and ended up with about a 50-100m range. This doesn't really cut it so it was imperative that I tested the new setup.

Each node is a plastic storage box filled with the following.

  • Fortigate firewall, I had two 50Bs and two 60ADSLs. Old ones like this are plentiful and cheap on eBay and I already had these. I work with these professionally so I'm 100% comfortable configuring them. You get pretty much everything you might want from a firewall/router appliance even when forced to run very old versions of FortiOS. These SOHO models even have a small network switch integrated.
  • Huawei E160G USB 3G modem. These slightly old 3G modems are also plentiful on eBay and I know they 100% work with FortiOS. Simply plug them into a USB port of the firewall and with a few lines of config you're ready to go.
  • Netgear WG102 wireless access point. I just happened to have picked four up previously and they support a point-to-multpoint bridging mode that could connect all the nodes. They're old and only support 802.11b/g over 2.4Ghz but performance isn't the thing we need.
  • 4W 2.4Ghz Wifi amplifier from China. We're working in a remote area with nobody to interfere with and I really needed the range this would give.
  • 30AH 12V sealed lead-acid battery, another thing I had four of lurking at home. With all the components working off external PSUs that supply 12V then the ~12.5-13V these kick out meant I didn't have to mess around with any other DC-DC conversion to power things.
I'm glad to say the test worked perfectly, with me able to cover the whole area of the site we intend to use acceptably in Wifi. The most distant node used the 3G backup as those Wifi amplifiers do work but trees in leaf do very bad things to radio propagation.

For those interested in the config, here's what I put on each firewall. It was done as a base script you can just copy and paste on then a second one to modify it specifically for each node. This could also mostly be done through the firewall GUI but making a command line script helps with making four identical nodes.

execute batch start
config system admin
    edit "admin"
        set password XXXXX
    next
end

config system global
    set admintimeout 90
    set hostname nodeX
    set timezone 25
    set dst enable
end

config system ntp
    set ntpsync enable
    set syncinterval 30
    config ntpserver
        edit 1
            set server pool.ntp.org
        next
    end
end

config system modem
    set status enable
    set dial-on-demand disable
    set auto-dial enable
    set idle-timer 1
    set redial 10
    set phone1 "*99#"
    set distance 100
end

config system interface
    edit "internal"
        set mode static
        unset ip
        set allowaccess ping https ssh
    next
    edit wan1
        set mode static
        unset ip
        set allowaccess ping https ssh
            config secondaryip
                edit 1
                    set detectserver "0.0.0.0"
                    set ip 192.168.0.1 255.255.255.0
                next
            end
    next
    edit wan2
        set mode static
        unset ip
        set allowaccess ping https ssh
    next
    edit "wifi_clients"
        set vdom root
        set type vlan
        set vlanid 2
        set interface wan1
        set mode static
        unset ip
        set allowaccess ping https ssh
    next
    edit "modem"
        set allowaccess ping https
        set ddns enable
        set ddns-server dyndns.org
        set ddns-domain "XXXXXXXXXX.homeip.net"
        set ddns-username "XXXXXXXXXXXXXX"
        set ddns-password XXXXXXXXXXXXXXXXX
    next
end

config system dhcp server
    delete "internal_dhcp_server"
    edit "internal"
        set default-gateway 10.254.1.1
        set start-ip 10.254.1.2
        set end-ip 10.254.1.254
        set dns-server1 8.8.8.8
        set interface "internal"
        set netmask 255.255.255.0
    next
    edit "wan1"
        set default-gateway 10.0.0.254
        set start-ip 10.0.0.5
        set end-ip 10.0.0.254
        set dns-server1 8.8.8.8
        set interface "wan1"
        set netmask 255.255.255.0
    next
    edit "wifi_clients"
        set default-gateway 10.254.3.1
        set start-ip 10.254.3.2
        set end-ip 10.254.3.254
        set dns-server1 8.8.8.8
        set interface "wifi_clients"
        set netmask 255.255.255.0
    next
end

config system dhcp reserved-address
    edit "ap1"
        set ip 10.0.0.5
        set mac 00:1b:2f:96:2b:cb
    next
    edit "ap2"
        set ip 10.0.0.6
        set mac 00:1b:2f:96:29:ab
    next
    edit "ap3"
        set ip 10.0.0.7
        set mac 00:1b:2f:98:40:d1
    next
    edit "ap4"
        set ip 10.0.0.8
        set mac 00:1e:2a:15:a4:4a
    next
end

config vpn ipsec phase1-interface
    edit "tunnel"
        set interface "modem"
        set dpd enable
        set nattraversal enable
        set proposal 3des-sha1 3des-md5
        set mode aggressive
        set remote-gw 1.2.3.4
        set psksecret XXXXXXXXXXXXX
        set localid XXXXXXXXX
        set peertype one
        set peerid XXXXXXXXXXX
    next
end

config router static
    delete 1
end

config firewall policy
    delete 1
end

config system zone
    edit this_node
        set interface "internal" "wifi_clients"
        set intrazone allow
    next
    edit elsewhere
        set interface "modem" "tunnel"
        set intrazone allow
    next
    edit mesh
        set interface "wan1"
        set intrazone allow
    next
end

config firewall address
    edit "mesh"
        set subnet 10.0.0.0 255.255.255.0
    next
    edit "wifi_clients"
        set subnet 10.1.0.0 255.255.255.0
    next
    edit "node1"
        set subnet 10.1.0.0 255.255.0.0
    next
    edit "node2"
        set subnet 10.2.0.0 255.255.0.0
    next
    edit "node3"
        set subnet 10.3.0.0 255.255.0.0
    next
    edit "node4"
        set subnet 10.4.0.0 255.255.0.0
    next
    edit "ap_default"
        set subnet 192.168.0.229 255.255.255.255
    next
end

config firewall addrgrp
    edit "this_node"
        set member "node2"
    next
    edit "other_nodes"
        set member "node1" "node3" "node4"
    next
end

config vpn ipsec phase2-interface
    edit "tunnel"
        set phase1name "tunnel"
        set keepalive enable
        set pfs enable
        set proposal 3des-sha1 3des-md5
        set src-addr-type name
        set dst-addr-type name
        set src-name "this_node"
        set dst-name "other_nodes"
        set auto-negotiate enable
    next
end


config router ospf
        config area
            edit 10.0.0.0
                set authentication md5
            next
        end
        config network
            edit 1
                set area 10.0.0.0
                set prefix 10.0.0.0 255.255.255.0
            next
        end
        config redistribute "connected"
            set status enable
        end
    set router-id 10.0.0.254
    set default-information-originate enable
    set passive-interface tunnel internal
end

config firewall policy
    edit 1
        set srcintf this_node
        set dstintf elsewhere
        set srcaddr this_node
        set dstaddr other_nodes
        set service ANY
        set action accept
        set schedule always
        set nat disable
    next
    edit 2
        set srcintf this_node
        set dstintf elsewhere
        set srcaddr this_node
        set dstaddr all
        set service ANY
        set action accept
        set schedule always
        set nat enable
    next
    edit 3
        set srcintf elsewhere
        set dstintf this_node
        set srcaddr other_nodes
        set dstaddr this_node
        set service ANY
        set action accept
        set schedule always
        set nat disable
    next
    edit 4
        set srcintf elsewhere
        set dstintf elsewhere
        set srcaddr other_nodes
        set dstaddr other_nodes
        set service ANY
        set action accept
        set schedule always
        set nat disable
    next
    edit 5
        set srcintf elsewhere
        set dstintf elsewhere
        set srcaddr other_nodes
        set dstaddr all
        set service ANY
        set action accept
        set schedule always
        set nat enable
    next
    edit 6
        set srcintf "this_node"
        set dstintf "mesh"
            set srcaddr "this_node"
            set dstaddr "ap_default"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 7
        set srcintf "this_node"
        set dstintf "mesh"
            set srcaddr "this_node"
            set dstaddr "other_nodes"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat disable
    next
    edit 8
        set srcintf "this_node"
        set dstintf "mesh"
            set srcaddr "this_node"
            set dstaddr "mesh"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 9
        set srcintf "mesh"
        set dstintf "this_node"
            set srcaddr "mesh" "other_nodes"
            set dstaddr "this_node"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat disable
    next
    edit 10
        set srcintf "mesh"
        set dstintf "elsewhere"
            set srcaddr "all"
            set dstaddr "all"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
end

config log memory setting
    set status enable
end

config log memory filter
    set event enable
    set admin enable
    set auth enable
    set cpu-memory-usage enable
    set dhcp enable
    set ha enable
    set ipsec enable
    set ldb-monitor enable
    set pattern enable
    set ppp enable
    set sslvpn-log-adm enable
    set sslvpn-log-auth enable
    set sslvpn-log-session enable
    set system enable
end

Then for each node I then had something like this.

config system global
    set hostname node1
end

config system interface
    edit "internal"
        set ip 10.1.1.1 255.255.255.0
    next
    edit "wan1"
        set ip 10.0.0.1 255.255.255.0
    next
    edit "wifi_clients"
        set ip 10.1.2.1 255.255.255.0
    next
    edit "modem"
        set ddns-domain "XXXXXXXXX.homeip.net"
    next
end

config router ospf
    set router-id 10.0.0.1
end

config system dhcp server
    edit "internal"
        set default-gateway 10.1.1.1
        set start-ip 10.1.1.2
        set end-ip 10.1.1.254
    next
    edit "wan1"
        set default-gateway 10.1.0.1
    next
    edit "wifi_clients"
        set default-gateway 10.1.2.1
        set start-ip 10.1.2.2
        set end-ip 10.1.2.254
    next
end

config vpn ipsec phase1-interface
    edit "tunnel"
        set psksecret XXXXXXXXXXXXXXXXXX
        set peerid Hub1
        set localid Node1
    next
end

config system interface
    edit "tunnel"
        set ip 10.253.0.2 255.255.255.255
        set remote-ip 10.253.0.1
        set allowaccess ping https ssh
    next
end

config router static
    edit 1
        set device "tunnel"
        set dst 10.2.0.0 255.255.0.0
        set distance 128
    next
    edit 2
        set device "tunnel"
        set dst 10.3.0.0 255.255.0.0
        set distance 128
    next
    edit 3
        set device "tunnel"
        set dst 10.4.0.0 255.255.0.0
        set distance 128
    next
end

config firewall addrgrp
    edit "this_node"
        set member "node1"
    next
    edit "other_nodes"
        set member "node2" "node3" "node4"
    next
end
execute batch end
There's no real attempt at security or firewalling the Fortinet's just being used as a router with basic OSPF and a VPN plus a couple of local networks at each node. I may tidy it up later.

At the other end they connect to there's config like this.

config vpn ipsec phase1-interface
    edit "Node1"
        set type dynamic
        set interface "portA1"
        set peertype one
        set mode aggressive
        set proposal 3des-sha1 3des-md5
        set localid "Hub1"
        set peerid "Node1"
        set psksecret XXXXXXXXXXXXXXXXXX
    next
    edit "Node2"
        set type dynamic
        set interface "portA1"
        set peertype one
        set mode aggressive
        set proposal 3des-sha1 3des-md5
        set localid "Hub2"
        set peerid "Node2"
        set psksecret XXXXXXXXXXXXXXXXXX
    next
    edit "Node3"
        set type dynamic
        set interface "portA1"
        set peertype one
        set mode aggressive
        set proposal 3des-sha1 3des-md5
        set localid "Hub3"
        set peerid "Node3"
        set psksecret XXXXXXXXXXXXXXXXXX
    next
    edit "Node4"
        set type dynamic
        set interface "portA1"
        set peertype one
        set mode aggressive
        set proposal 3des-sha1 3des-md5
        set localid "Hub4"
        set peerid "Node4"
        set psksecret XXXXXXXXXXXXXXXXXX
    next
end
config firewall address
    edit "Node1"
        set subnet 10.1.0.0 255.255.0.0
    next
    edit "Node2"
        set subnet 10.2.0.0 255.255.0.0
    next
    edit "Node3"
        set subnet 10.3.0.0 255.255.0.0
    next
    edit "Node4"
        set subnet 10.4.0.0 255.255.0.0
    next
end 
config vpn ipsec phase2-interface
    edit "Node1"
        set dst-addr-type name
        set phase1name "Node1"
        set proposal 3des-sha1 aes128-sha1
        set src-addr-type name
        set dst-name "Node1"
        set src-name "Node 2,3,4"
    next
    edit "Node2"
        set dst-addr-type name
        set phase1name "Node2"
        set proposal 3des-sha1 aes128-sha1
        set src-addr-type name
        set dst-name "Node2"
        set src-name "Node 1,3,4"
    next
    edit "Node3"
        set dst-addr-type name
        set phase1name "Node3"
        set proposal 3des-sha1 aes128-sha1
        set src-addr-type name
        set dst-name "Node3"
        set src-name "Node 1,2,4"
    next
    edit "Node4"
        set dst-addr-type name
        set phase1name "Node4"
        set proposal 3des-sha1 aes128-sha1
        set src-addr-type name
        set dst-name "Node4"
        set src-name "Node 1,2,3"
    next
end
config firewall policy
    edit 1000
        set srcintf "Node1"
        set dstintf "Internet"
            set srcaddr "Node1"
            set dstaddr "all"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 2000
        set srcintf "Node2"
        set dstintf "Internet"
            set srcaddr "Node2"
            set dstaddr "all"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 1002
        set srcintf "Node1"
        set dstintf "Node2"
            set srcaddr "Node1"
            set dstaddr "Node2"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 2001
        set srcintf "Node2"
        set dstintf "Node1"
            set srcaddr "Node2"
            set dstaddr "Node1"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 1003
        set srcintf "Node1"
        set dstintf "Node3"
            set srcaddr "Node1"
            set dstaddr "Node3"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 2003
        set srcintf "Node2"
        set dstintf "Node3"
            set srcaddr "Node2"
            set dstaddr "Node3"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 3000
        set srcintf "Node3"
        set dstintf "Internet"
            set srcaddr "Node3"
            set dstaddr "all"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 3001
        set srcintf "Node3"
        set dstintf "Node1"
            set srcaddr "Node3"
            set dstaddr "Node1"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 3002
        set srcintf "Node3"
        set dstintf "Node2"
            set srcaddr "Node3"
            set dstaddr "Node2"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 23
        set srcintf "Node1"
        set dstintf "Node4"
            set srcaddr "Node1"
            set dstaddr "Node4"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 24
        set srcintf "Node2"
        set dstintf "Node4"
            set srcaddr "Node2"
            set dstaddr "Node4"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 25
        set srcintf "Node3"
        set dstintf "Node4"
            set srcaddr "Node3"
            set dstaddr "Node4"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 26
        set srcintf "Node4"
        set dstintf "Internet"
            set srcaddr "Node4"
            set dstaddr "all"
        set action accept
        set schedule "always"
            set service "ANY"
        set nat enable
    next
    edit 27
        set srcintf "Node4"
        set dstintf "Node1"
            set srcaddr "Node4"
            set dstaddr "Node1"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 28
        set srcintf "Node4"
        set dstintf "Node2"
            set srcaddr "Node4"
            set dstaddr "Node2"
        set action accept
        set schedule "always"
            set service "ANY"
    next
    edit 29
        set srcintf "Node4"
        set dstintf "Node3"
            set srcaddr "Node4"
            set dstaddr "Node3"
        set action accept
        set schedule "always"
            set service "ANY"
    next
end

Using ESP8266, NodeMCU and MQTT to create a wirelessly connected 'installation' - part 1

Following up on my last post I'm now neck deep in building an 'installation' where most the components connect over Wifi using ESP-01 ESP8266 modules.

Kicking off the project I've been doing the usual thing of building the first steps from an unapologetic cut & paste of 'stuff found on the Internet'. There seems to be a bit of gulf between the 'hello world' examples and people wrestling with complicated issues, but that's often the case with project examples.

So I thought it might be worth putting something back and actually documenting everything I'm doing, which will cover some of that middle ground.

The installation is going to be about 25 'things' that need to be 'orchestrated' to work together. This is happening over quite a large, remote outdoor area so this is being engineered from scratch to be self-sufficient in every way. Including power.

So far here's where I am on the planning front.

  • Wifi - This will be four 'nodes' each with a dedicated firewall, 3G modem, Wifi AP capable of mesh networking and most likely a Raspberry Pi to do the 'orchestration'. Yes you could squeeze all this out of the Pi but I want to compartmentalise the components. There may be Yagi antennas to increase range.
  • Networking - Each 'node' will be connected to the others over the Wifi mesh and run OSPF to keep track of routing. There is also Internet access via the 3G. This 3G provides a backup connection for the Wifi over a hub & spoke VPN. I work in this field and have some kit in a Data Centre to connect back to. This stuff is my bread and butter so it's already tested and working, yay!
  • Power - The 'nodes' will use 12V lead-acid deep cycle batteries. The event only lasts 4-6 hours and a lot of the kit I've scrounged will run straight off the 12-13V these deliver in practice. The 'things' will be powered either by 3x AA or 1x 18650 battery depending on if they're static or carried.
  • Things - These are going to be driven by an Arduino/ESP8266 combo. The Arduino Pro Minis will do the procedural, timing sensitive stuff like drive I2C displays, talk to peripherals over SPI and so on. The ESP-01 will run Lua and act as a gateway to the Wifi and MQTT. Communication between the two will be over serial with a bit of simple message passing. In principle I could have bought a bigger ESP8266 dev board and used that. However this decision is still about compartmentalisation. The Arduino environment has a very mature set of libraries for talking to stuff, but is bad at the networking. Lua/NodeMCU simply doesn't have the libraries for the modules I'm using but seems to have decent networking support.
  • Communication with 'things' - I have settled on MQTT because it's lightweight and seemingly well supported in Lua/NodeMCU. This means I don't have to roll my own networking protocol like I did last year. I may be able to 'gateway' the 868Mhz radio kit I built last year into MQTT but that's not a job for now.
  • Orchestration of the 'things'. This will be done with some scripts on the Raspberry Pi at each node. For an MQTT broker I'll be using Mosquitto, probably with Python. The hand carried 'things' will have a bit of intelligence so can wander out of Wifi range and then update the nearest Pi when they get Wifi again. I've already tested some basic use of Mosquitto from NodeMCU.
  • Enclosures - There will be 3D printed enclosures for all the 'things'. Not the cheapest or fastest way to do it but this kind of exercise is why I bought my printer. The enclosure for one thing is pretty much finalised and I've done work on another.
  • Interaction - As well directly interacting with the installation, some people will be able to control it. For that I've acquired some old XP-era Pentium-M laptops (pictured) I am turning into web kiosks with Puppy Linux. This avoids having to leave a nice laptop sitting under a gazebo in the woods. Two of them are weird HP/Compaq TC1100 Windows Slate PCs which makes them quite a nice unusual looking thing to interact with. The batteries are obviously ruined so I will need to come up with a way to power them from 12V. I've got one inverter and one 12V powered laptop PSU, just need to find a third.
  • Monitoring - As well as controlling some components, ideally some people will be able to watch other areas over 'CCTV' using the supplied laptops. Thanks to the generosity of a friend I've pulled together five identical old IP cameras that will go straight on the Wifi.
Somehow or other I've got to make all this happen and write a web interface to it. Thankfully I've started early and have a handle on a lot of it. There's still a mountain to climb though.