Depolying a TTN LoRa Gateway

I’ve been meaning to get round to this ever since the Pi Supply Kickstarter delivered my LoRa Gateway HAT and the LoRa Node pHAT.

They have been sat in their boxes waiting until I had some spare time (and I’d finally finished moving a few things around to free up a spare Pi).

LoRa Gateway on a Pi 3

LoRa is a long range, low bandwidth radio system that uses the unlicensed spectrum. When combined with the higher level LoRaWAN protocol it makes great IoT platform for low power devices that want to send low volumes of data in places where there is no WiFi coverage and can’t justify the cost of a cellular connection.

LoRaWAN allows you to deploy a collection of Gateway devices that can act as receivers for a large number of deployed devices. These gateways then forward on messages to central point for processing.

The Things Network

A group called The Things Network run a LoRaWAN deployment. They are aiming for as large a coverage area as possible. To do this they allow users to deploy their own gateways and join these to the network. By joining the network you get to use everybody elses gateways in exchange for letting other people use yours.

Setting up the Gateway

This was particularly easy. I just had to download an image and flash it to a SD card. Stick that into the pi along with an ethernet cable and some power.

After the pi boots up you point your browser at http://iotloragateway.local and fill in a couple of values generated when I registered the gateway on the TTN site and that was it. The gateway is now up and running and ready to send/receive packets from any devices in range.

Testing

In order to test the gateway I need to set up a Pi Zero with the LoRa Node pHAT. This was a little trickier, but not much.

Fist I had to disable the Linux serial console, this can be done using the raspi-config command. I also had to add dtoverlay=pi3-miniuart-bt /boot/config.txt.

That was all that was needed to get the hardware configured, as for the software there is a rak811 python package that supplies the api and utilities to work with pHAT.

I now needed to declare an application on The Things Network site, this is how messages get routed to be processes. Taking the values for this application I could now write the following helloWorld.py

#!/usr/bin/env python3
from rak811 import Mode, Rak811

lora = Rak811()
lora.hard_reset()
lora.mode = Mode.LoRaWan
lora.band = 'EU868'
lora.set_config(app_eui='xxxxxxxxx',
                app_key='xxxxxxxxxxxx')
lora.join_otaa()
lora.dr = 5
lora.send('Hello world')
lora.close()

Which can then be seen arriving in The Things Network console.

Data arriving and being displayed in The Things Network console.

And I can subscribe directly to that data feed via MQTT:

$ mosquitto_sub -h eu.thethings.network -u 'lora-app1-hardill-me-uk' -P 'xxxxxxxxx' -v -t '+/devices/+/up'
{
  "app_id": "lora-app1-hardill-me-uk",
  "dev_id": "test-lora-pi-zero",
  "hardware_serial": "323833356E387901",
  "port": 1,
  "counter": 0,
  "is_retry": true,
  "payload_raw": "SGVsbG8gd29ybGQ=",
  "metadata": {
    "time": "2019-08-10T15:45:07.568449769Z",
    "frequency": 867.5,
    "modulation": "LORA",
    "data_rate": "SF7BW125",
    "airtime": 61696000,
    "coding_rate": "4/5",
    "gateways": [
      {
        "gtw_id": "lora-gw1-hardill-me-uk",
        "gtw_trusted": true,
        "timestamp": 910757708,
        "time": "2019-08-10T15:45:07Z",
        "channel": 5,
        "rssi": -91,
        "snr": 7.75,
        "rf_chain": 0,
        "latitude": 51.678905,
        "longitude": -2.3549008,
        "location_source": "registry"
      }
    ]
  }
}

Next Steps

First up will be to get a better antenna for the gateway and to move the whole things up in the attic, from there it should get a good view north out towards the River Severn. After that I want to get a small battery powered LoRa/GPS board, like a TTGO T-Beam and ride round on my bike to get a feel for what the range/coverage actually is.

I’ll also be keeping an eye on the stats from the gateway to see if anybody else near by is deploying TTN LoRaWAN devices.

DIY IoT button

I’ve been looking for a project for a bunch of ESP-8266 ESP-01 boards I’ve had kicking around for a while.

The following describes a simple button that when pushed publishes a MQTT message that I can subscribe to with Node-RED to control different tasks.

It’s been done many times before, but I wanted to have a go at building my own IoT button.

Software

The code is pretty simple:

  • The MQTT PubSubClient (Thank’s Nick)
  • Some hard coded WiFi and MQTT Broker details
  • The setup function which connects to the network
  • The reconnect function that connects to the MQTT broker and publishes the message
  • The loop function which flashes the LED to show success then go into deep sleep

In order to get the best battery life you want the ESP8266 to be in deep sleep mode for as much as possible, so for this reason the loop function sends the message to signify the button has been pushed then indefinitely enters the deepest sleep state possible. This means the loop function will only run once on start up.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#define ESP8266_LED 1

const char* ssid = "WifiName";
const char* passwd = "GoodPassword";
const char* broker = "192.168.1.114";

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {

  Serial.begin(115200);
  delay(10);
  
  pinMode(ESP8266_LED, OUTPUT);

  WiFi.hostname("Button1");
  WiFi.begin(ssid, passwd);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  client.setServer(broker, 1883);
  reconnect();
}

void reconnect() {
  while(!client.connected()) {
    if (client.connect("button1")){
      client.publish("button1", "press");
    } else {
      delay(5000);
    }
  }
}

void loop() {

  if (client.connected()) {
    reconnect();
  }
  client.loop();
  
  // put your main code here, to run repeatedly:
  digitalWrite(ESP8266_LED, HIGH);
  delay(1000);
  digitalWrite(ESP8266_LED, LOW);
  delay(1000);
  ESP.deepSleep(0);
}

Hardware

By using a momentary push button wake the ESP-01 I bridged reset pin to ground the chip resets each time it’s pushed, this wakes it from the deep sleep state and runs the all the code, then drops back into the sleep state.

Button diagram

  • The green line is the Chip enable
  • The blue line is the the links reset to ground via the push button

Now I had the basic circuit and code working I needed to pick a power supply. The ESP-01 needs a 3.3v supply and most people seem to opt for using a small LiPo cell. A single cell has a nominal fully charged voltage of 3.7v which is probably close enough to use directly with the ESP-01, but the problem is you normally need to add a circuit to cut them out before the voltage gets too low as they discharge to prevent permanently damage them. You would normally add a charging circuit to allow recharging from a USB source.

This wasn’t what I was looking for, I wanted to use off the shelf batteries so I went looking for a solution using AAA batteries. The board will run directly from a fresh set of 2 AAA batteries, but the voltage can quickly drop too low. To help with this I found a part from Pololu that would take an input between 0.5v and 5v and generate a constant 3.3v. This meant that even as the batteries discharged I should be able to continue to run the device.

At first things didn’t look to work, because converter was not supplying enough current for the ESP-01 at start up, to get round this I added a 100uF capacitor across the outputs of the regulator. I don’t really know how properly to size this capacitor so I basically made a guess.

The final step was to use the soldering iron to remove the red power LED from the board as this was consuming way more power than the rest of the system.

Prototype

Next Steps

  • Make the MQTT topic based on the unique id of the ESP-01 so it isn’t hard coded
  • Look at adding Access Point mode to allow configuration of the WiFi details if the device can not connect to the existing configuration
  • Design a circuit board to hold the voltage converter, capacitor, button and the ESP-01
  • Create a case to hold it all
  • Work out just how long a set of batteries will last

Fist pass TRÅDFRI MQTT Bridge

I’ve been working on integrating the new IKEA TRÅDFRI Lights into my Home Automation system. I’d really like a native NodeJS system so I can plug it directly into Node-RED, but I’ve not found a working CoAP over DTLS setup just yet.

So in the mean time I’ve been working on a very basic MQTT to CoAP client bridge in Java using the Eclipse Californium library.

It still needs some work, but here is the first pass:

package uk.me.hardill.coap2mqtt;

import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.logging.Level;

import org.eclipse.californium.core.CaliforniumLogger;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.ScandiumLogger;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.pskstore.StaticPskStore;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * @author hardillb
 *
 */
public class Main {
  
  static {
    CaliforniumLogger.disableLogging();
    ScandiumLogger.disable();
//    ScandiumLogger.initialize();
//    ScandiumLogger.setLevel(Level.FINE);
  }
  
  private DTLSConnector dtlsConnector;
  private MqttClient mqttClient;
  private CoapEndpoint endPoint;
  
  private String ip;
  
  private HashMap<String, Integer> name2id = new HashMap<>();
  
  Main(String psk, String ip) {
    this.ip = ip;
    DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(new InetSocketAddress(0));
    builder.setPskStore(new StaticPskStore("", psk.getBytes()));
    dtlsConnector = new DTLSConnector(builder.build());
    endPoint = new CoapEndpoint(dtlsConnector, NetworkConfig.getStandard());
    
    MemoryPersistence persistence = new MemoryPersistence();
    try {
      mqttClient = new MqttClient("tcp://localhost", MqttClient.generateClientId(), persistence);
      mqttClient.connect();
      mqttClient.setCallback(new MqttCallback() {
        
        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
          // TODO Auto-generated method stub
          System.out.println(topic + " " + message.toString());
          String parts[] = topic.split("/");
          int id = name2id.get(parts[1]);
          System.out.println(id);
          String command = parts[3];
          System.out.println(command);
          try{
            JSONObject json = new JSONObject("{\"9001\":\"Living Room Light\",\"9020\":1491515804,\"9002\":1491158817,\"9003\":65537,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 opal 1000lm\",\"2\":\"\",\"3\":\"1.1.1.0-5.7.2.0\",\"6\":1},\"9019\":1,\"3311\":[{\"5850\":1,\"5851\":10,\"9003\":0}]}");
            JSONObject settings = json.getJSONArray("3311").getJSONObject(0);
            if (command.equals("dim")) {
              settings.put("5851", Integer.parseInt(message.toString()));
            } else if (command.equals("on")) {
              if (message.toString().equals("0")) {
                settings.put("5850", 0);
                settings.put("5851", 0);
              } else {
                settings.put("5850", 1);
                settings.put("5851", 128);
              }
            }
            String payload = json.toString();
            Main.this.set("coaps://" + ip + "//15001/" + id, payload);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
          // TODO Auto-generated method stub
        }
        
        @Override
        public void connectionLost(Throwable cause) {
          // TODO Auto-generated method stub
        }
      });
      mqttClient.subscribe("TRÅDFRI/+/control/+");
    } catch (MqttException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void discover() {
    try {
      URI uri = new URI("coaps://" + ip + "//15001");
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapResponse response = client.get();
      JSONArray array = new JSONArray(response.getResponseText());
      for (int i=0; i<array.length(); i++) {
        String devUri = "coaps://"+ ip + "//15001/" + array.getInt(i);
        this.watch(devUri);
      }
      client.shutdown();
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void set(String uriString, String payload) {
    System.out.println("payload\n" + payload);
    try {
      URI uri = new URI(uriString);
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapResponse response = client.put(payload, MediaTypeRegistry.TEXT_PLAIN);
      if (response.isSuccess()) {
        System.out.println("Yay");
      } else {
        System.out.println("Boo");
      }
      
      client.shutdown();
      
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void watch(String uriString) {
    
    try {
      URI uri = new URI(uriString);
      
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapHandler handler = new CoapHandler() {
        
        @Override
        public void onLoad(CoapResponse response) {
          System.out.println(response.getResponseText());
          JSONObject json = new JSONObject(response.getResponseText());
          if (json.has("3311")){
            MqttMessage message = new MqttMessage();
            int state = json.getJSONArray("3311").getJSONObject(0).getInt("5850");
            message.setPayload(Integer.toString(state).getBytes());
            message.setRetained(true);
            String topic = "TRÅDFRI/" + json.getString("9001") + "/state/on";
            String topic2 = "TRÅDFRI/" + json.getString("9001") + "/state/dim";
            name2id.put(json.getString("9001"), json.getInt("9003"));
            MqttMessage message2 = new MqttMessage();
            int dim = json.getJSONArray("3311").getJSONObject(0).getInt("5851");
            message2.setPayload(Integer.toString(dim).getBytes());
            message2.setRetained(true);
            try {
              mqttClient.publish(topic, message);
              mqttClient.publish(topic2, message2);
            } catch (MqttException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
          } else {
            System.out.println("not bulb");
          }
        }
        
        @Override
        public void onError() {
          // TODO Auto-generated method stub
          
        }
      };
      client.observe(handler);
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
  }

  /**
   * @param args
   */
  public static void main(String[] args) throws InterruptedException {
    String psk = args[0];
    String ip = args[1];
    Main m = new Main(psk, ip);
    
    m.discover();
  }

}

I’ve tagged the code onto the gist as well for now, but I’ll check the whole thing in as a separate project soon.

EDIT: now with it’s own Github repo here

GPIO buttons – Demo stand

I recently got asked to help out building a demo for one of the projects the team has been working on.

The project uses situational awareness to vary the level of authentication needed to carry out different tasks. Rather than make the person using the demo run all over the place to change location or log in and out of various systems we have stubbed out the inputs and needed a way to toggle them locally. The demo runs on a tablet so we wanted something that could sit near by and allow the inputs to be varied.

To do this I thought I’d have a play with a Raspberry Pi and the GPIO pins. The plan was to attach 5 buttons to the toggle the state of the inputs, the buttons have lights that will show the state.

This would have been a perfect use case for the new Raspberry Pi Zero as it only needs 1 USB port and the GPIO pins, but I couldn’t convince any of the guys on the team lucky enough to grab one to give it up.

The chosen buttons where 5 of these:

http://uk.rs-online.com/web/p/push-button-switches/7027140/

I’d uses something very similar in the past for the MQTT Powered Video Walls in IBM Southbank Client Centre.

The first single button prototype worked nicely after I’d worked out what was needed with pull-up resistors for the buttons and current limiting resistors for the LEDs.

Button prototype

Node-RED was set up to read the state of the buttons from the GPIO pints and to update toggle the LED output the same way.

Node-RED flow to read buttons
Node-RED flow to read buttons

To make the demo totally standalone the rPi acts as a WiFi access point that the tablet connects to. It uses this connection to load the demo and to read the updates from the buttons via MQTT over Websockets.

The USB WiFi adapter available was a Edimax EW-7811UN, The default Linux driver for this doesn’t support HostAPD, luckily there is a page with instructions and a github project with a functional version.

Mosquitto 1.4.2 was installed to act as the the MQTT broker and to support MQTT over websockets so the web app could connect directly to get the button updates.

Having finished off the wiring and configuring the rPi the whole thing was mounted in a display board put together by Andy Feltham.

And ended up looking like this, all ready for a overlay with a description and instructions.

Completed Board

Building Bluetooth LE devices

While waiting for my flic.io buttons to turn up I’ve been playing with building my own Bluetooth Low Energy devices.

Since I already had a couple of sensors hooked up to publish their values via MQTT I thought I would try and build a bridge between MQTT and BLE.

I’m using a Raspberry Pi, a Bluetooth 4.0 USB dongle and a NodeJS npm module called bleno.

It turned out to be petty easy, first a short file to set up the BLE service and connect to MQTT:

var util = require('util');
var bleno = require('bleno');
var mqtt = require('mqtt');

var BlenoPrimarySerivce = bleno.PrimaryService;

var TopicCharacteristic = require('./topicCharacteristic');

var config = require("./config");

var client = mqtt.connect(config.broker);

var topicCharacteristic = new TopicCharacteristic(config);

client.on('connect', function(){
  client.subscribe(config.topic);
});

client.on('message',function(topic, message){
  topicCharacteristic.update(message);
});

bleno.on('stateChange', function(state){
  if (state === 'poweredOn') {
    bleno.startAdvertising('mqtt', ['ba42561bb1d2440a8d040cefb43faece']);
  } else {
    bleno.stopAdvertising();
  }
});

bleno.on('advertisingStart', function(error){
  if(!error) {
  	bleno.setServices([
  	  new BlenoPrimarySerivce({
  	  	uuid: 'ba42561bb1d2440a8d040cefb43faece',
  	  	characteristics: [
  	  	  topicCharacteristic
  	  	]
  	  })
  	]);
  }
});

And then something to add the characteristic for the topic:

var util = require('util');
var bleno = require('bleno');

function TopicCharacteristic(config, client) {
	bleno.Characteristic.call(this, {
		uuid: '6bcb06e2747542a9a62a54a1f3ce11e6',
		properties: ['read', 'write', 'notify'],
		descriptors: [
			new bleno.Descriptor({
				uuid: '2901',
				value: 'Topic: ' + config.topic
			})
		]
	});

	this._value = new Buffer(0);
	this._updateValueCallback = null;
	this._client = client;
	this._topic = config.topic;
}

util.inherits(TopicCharacteristic, bleno.Characteristic);

TopicCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
	this._value = data;
	client.publish(this._topic, data);
	callback(this.RESULT_SUCCESS);
}


TopicCharacteristic.prototype.onReadRequest = function(offset, callback) {
	callback(this.RESULT_SUCCESS, this._value);
}

TopicCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
	this._updateValueCallback = updateValueCallback;
}

TopicCharacteristic.prototype.onUnsubscribe = function () {
	this._updateValueCallback = null;
}

TopicCharacteristic.prototype.update = function(value) {
	this._value = value;
	if (this._updateValueCallback) {
		this._updateValueCallback(this._value);
	}
}

module.exports = TopicCharacteristic;

I’ve used 2 randomly generated UUIDs, one for the service and one for the characteristic.

The code should allow you to read the last value published, publish a new value and subscribe to notifications when new values arrive.

I pulled together a quick Android app to subscribe to the notifications and update when a new value is published and it seams to be working well.

The code is all up on Github and on npmjs so feel free to have a play.

You can test it with the Bluez gatttoool.

[hardillb@bagend ~]$ gatttool -b 00:1A:7D:DA:71:15 -I
[00:1A:7D:DA:71:15][LE]> connect
Attempting to connect to 00:1A:7D:DA:71:15
Connection successful
[00:1A:7D:DA:71:15][LE]> primary
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0006, end grp handle: 0x0009 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x000a, end grp handle: 0x000e uuid: ba42561b-b1d2-440a-8d04-0cefb43faece
[00:1A:7D:DA:71:15][LE]> characteristics 
handle: 0x0002, char properties: 0x02, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0007, char properties: 0x20, char value handle: 0x0008, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x000b, char properties: 0x1a, char value handle: 0x000c, uuid: 6bcb06e2-7475-42a9-a62a-54a1f3ce11e6
[00:1A:7D:DA:71:15][LE]> char-write-req 0x000d 0300
Characteristic value was written successfully
Notification handle = 0x000c value: 42 61 72 
Notification handle = 0x000c value: 48 65 6c 6c 6f 57 6f 72 6c 64 
Notification handle = 0x000c value: 54 65 73 74 69 6e 67 20 31 32 33 
[00:1A:7D:DA:71:15][LE]> 
[00:1A:7D:DA:71:15][LE]> quit

The lines that start Notification handle contain the bytes published, in this case

  • Bar
  • HelloWorld
  • Testing 123

Moo Sticker engines fixed

A few years ago I create a web app to allow people to order their own sets of Moo stickers with the MQTT, Node-RED and Owntracks(at the time called MQTTitude) logos.

These worked well with people ordering about 1 pack a month until Moo changed the way they did authentication. I didn’t have time to fix it until today.

I’ve moved the app from being a J2EE app over to one written in NodeJS using express, this along with the change in Moo’s authentication method has made the code a lot shorter and easier to read. I’ve published the new code on github here.

To order a set of stickers click on the appropriate image:

Playing with Asterisk PBX

I’ve been meaning to get back and have a proper play with Asterisk again for a while. Last week Amazon sent me one of those emails about things you’ve looked at but not bought and I spotted this:

It was down from £60 to £35 so I did exactly what they wanted and bought one.

Now normally I don’t use my land line at all, it’s just there to let the internets in, it doesn’t even have a handset plugged in. But there are a few little projects kicking around the back of my mind I’ve been thinking about for a while and the OBi110 should let me play with them.

The first is to see if the (unused, never given to anybody but my ISP to set up the conection) number for the land line has ended up on any lists for scamers/spammers and people generally trying to sell me stuff. My mobile gets at least 1 call a week about payment protection and the like and even my work office number has started getting recorded calls about getting my boiler replaced.

I could have probably just used the call log on the OBi110 but I wanted to be able to potentially record these calls and a few other things so I needed something a little smarter which is were Asterisk comes in. Asterisk is a opensource VoIP PBX this basically means it acts like a telephone exchange for calls made over the internet. I’ve seen people run Asterisk on the old Linksys Slugs so I was sure it should run fine on a Raspberry Pi as long as it wasn’t dealing with too many calls and not doing much codex transcoding. As I already had a Pi running my SMS/MMS rig it seamed like a good place to put all my telephone stuff.

Installing Asterisk on the Pi was just a case of running apt-get install asterisk. It comes with a bunch of default config files (in /etc/asterisk), but there are 2 main ones that I needed to change to make some simple things work.

sip.conf
This file is where you can configure what clients can connect to your asterisk instance via the SIP protocol. To start with I’m going to set up 2 different clients, one for a softphone running on my laptop and one for the OBi110. It sets up few things, but the import bit for later is the context which controls which bit of the extentions.conf file we jump to when receiving a call from each client.

[general]
context=local
srvlookup=yes

[softphone]
defaultuser=softphone
type=friend
secret=password123
qualify=no
nat=no
host=dynamic
canreinvite=no
context=local
disallow=all ; only the sensible codecs
allow=ulaw
allow=alaw
allow=gsm

[obihai]
defaultuser=obihai
type=friend
secret=password123
qualify=yes
dtmfmode=rfc2833
canreinvite=no
context=external
disallow=all
allow=ulaw

extensions.conf
This file defines how Asterisk should handle calls, it has two contexts called local and external. The local context defines 2 paths, the first for extension 100, when this number is called from the softphone Asterisk calls out to a small python program called agi-mqtt which publishes a JSON object to the calls/local MQTT topic which contains all the information Asterisk has about the call. It then answers the call then plays audio file containing HelloWorld and finally hangs the call up. I’m mainly using this local context to testing things out before copying them over to the external context.

The second path through the local context uses a special case extension number “_0Z.”, this matches any number that starts with 0[1-9] (so won’t match against 100). This path forwards the dialed number on to the OBi110 to place the call via the PSTN line.

The external context only contains 1 path which matches the phone number of the PSTN line and currently matches the 100 extension (play HelloWorld). At some point later I’ll setup this path to forward calls to a local softphone or forward to a voicemail account.

[local]
exten => _0Z.,1,AGI(/opt/asterisk/agi-mqtt/mqtt,/opt/asterisk/agi-mqtt/mqtt.cfg,calls/local)
exten => _0Z.,2,Dial(SIP/${EXTEN}@obihai);
exten => _0Z.,3,Congestion()
exten => _0Z.,103,Congestion()
exten => t,1,Hangup()

exten => 100,1,AGI(/opt/asterisk/agi-mqtt/mqtt,/opt/asterisk/agi-mqtt/mqtt.cfg,calls/local)
exten => 100,2,Answer()
exten => 100,3,Playback(en_US/hello-world)
exten => 100,4,Hangup()

[inbound]

exten => 0123456789,1,AGI(/opt/asterisk/agi-mqtt/mqtt,/opt/asterisk/agi-mqtt/mqtt.cfg,calls/pstn-in)
exten => 0123456789,2,Answer()
exten => 0123456789,3,Playback(en_US/hello-world)
exten => 0123456789,4,Hangup()

Now Asterisk is all working properly I setup the OBi110 using the instructions found here.

After a bit of playing I have inbound and outbound calls working and some MQTT enabled logging. Next up is looking at using the SIP Client built into Android to allow calls to be made and received from my mobile phone.

Unpacking binary data from MQTT in Javascript

While doing trawl of Stackoverflow for questions I might be able to help out with I came across this interesting looking question:

Receive binary with paho mqttws31.js

The question was how to unpack binary MQTT payloads into double precision floating point numbers in javascript when using the Paho MQTT over WebSockets client.

Normally I would just send floating point numbers as strings and parse them on the receiving end, but sending them as raw binary means much smaller messages, so I thought I’d see if I could help to find a solution.

A little bit of Googling turned up this link to the Javascript typed arrays which looked like it probably be in the right direction. At that point I got called away to look at something else so I stuck a quick answer in with a link and the following code snippet.

function onMessageArrived(message) {
  var payload = message.payloadByte()
  var doubleView = new Float64Array(payload);
  var number = doubleView[0];
  console.log(number);
}

Towards the end of the day I managed to have a look back and there was a comment from the original poster saying that the sample didn’t work. At that point I decided to write a simple little testcase.

First up quick little Java app to generate the messages.

import java.nio.ByteBuffer;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

public class MessageSource {

  public static void main(String[] args) {
    try {
      MqttClient client = new MqttClient("tcp://localhost:1883", "doubleSource");
      client.connect();

      MqttMessage message = new MqttMessage();
      ByteBuffer buffer = ByteBuffer.allocate(8);
      buffer.putDouble(Math.PI);
      System.err.println(buffer.position() + "/" + buffer.limit());
      message.setPayload(buffer.array());
      client.publish("doubles", message);
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      client.disconnect();
    } catch (MqttException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

It turns out that using typed arrays is a little more complicated and requires a bit of work to populate the data structures properly. First you need to create an ArrayBuffer of the right size, then wrap it in a Uint8Array in order to populate it, before changing to the Float64Array. After a little bit of playing around I got to this:

function onMessageArrived(message) {
  var payload = message.payloadBytes
  var length = payload.length;
  var buffer = new ArrayBuffer(length);
  uint = new Uint8Array(buffer);
  for (var i=0; i<length; i++) {
	  uint[i] = payload[i];
  }
  var doubleView = new Float64Array(uint.buffer);
  var number = doubleView[0];
  console.log("onMessageArrived:"+number);
};

But this was returning 3.207375630676366e-192 instead of Pi. A little more head scratching and the idea of checking the byte order kicked in:

function onMessageArrived(message) {
  var payload = message.payloadBytes
  var length = payload.length;
  var buffer = new ArrayBuffer(length);
  uint = new Uint8Array(buffer);
  for (var i=0; i<length; i++) {
	  uint[(length-1)-i] = payload[i];
  }
  var doubleView = new Float64Array(uint.buffer);
  var number = doubleView[0];
  console.log("onMessageArrived:"+number);
};

This now gave an answer of 3.141592653589793 which looked a lot better. I still think there may be a cleaner way to do with using a DataView object, but that’s enough for a Friday night.

EDIT:

Got up this morning having slept on it and came up with this:

function onMessageArrived(message) {
  var payload = message.payloadBytes
  var length = payload.length;
  var buffer = new ArrayBuffer(length);
  uint = new Uint8Array(buffer);
  for (var i=0; i<length; i++) {
	  uint[i] = payload[i];
  }
  var dataView = new DataView(uint.buffer);
  for (var i=0; i<length/8; i++) {
      console.log(dataView.getFloat64((i*8), false));
  }
};

This better fits the original question in that it will decode an arbitrary length array of doubles and since we know that Java is big endian, we can set the little endian flag to false to get the right conversion without having to re-order the array as we copy it into the buffer (which I’m pretty sure wouldn’t have worked for more than one value).