MQTT Client on the Carambola using Lua

No electronics today… all about the code!

With the Carambola i2c IO board hardware built, time to turn to the software. My first thoughts were to run a Python web server that would listen on the network for requests to turn on and off the relays.  But a while ago I was watching a video by Jon Oxer [ SuperHouse.tv ] about home automation, in which he mentions MQTT [Message Queuing Telemetry Transport].

I’ve not done much with Python on the Carambola, any thing that I’ve read says it to heavy to be of any use, and that Lua is the way to go. So what about a Lua MQTT client?  Well there is one, a guy called Andy Gelme has done the business, and the code is on GitHub, mqtt_lua.

Took a little playing around but I got it installed on my Carambola.  Here is what you’ll need to do:

Install PenLight

  1. Download this Lua Penlight package: https://github.com/stevedonovan/Penlight/archive/master.zip
  2. On desktop machine unzip it and go into “/Penlight-master/lua/” directory.
  3. Copy the “pl” directory to the following location on your Carambola: /usr/lib/lua

Install Lua MQTT

  1. Download this Lua MQTT package: https://github.com/geekscape/mqtt_lua/archive/master.zip
  2. On desktop machine extract zip file and go into the /mqtt_lua-master/lua/ directory
  3. Copy the “mqtt-library.lua” and “utility.lua” files to “/usr/lib/lua”

Your good to go!

Now your going to have to install a MQTT server on a box somewhere.  I have Ubuntu running on a server here so I installed Mosquitto on it. The really nice thing about MQTT is it’s simplicity.. I searched the net for HowTos and any information on setting MQTT up but it’s really easy, I guess that is why there is so few intros. Just jump in and give it a go!

So here is the client code (or the subscriber code) [ Only new to Lua so there are probably far nicer ways of doing this... but hay it works. It's based on one of the examples that Andy has on his Git page ]:

#!/usr/bin/lua
-- A little lua script that subscribes to a MQTT server
-- and waits for instructions from a topic
--
-- Usage:
-- mqtt_relays.lua -t relay/#
-- 
-- MQTT server is hard coded.. look toward the end of this file
--
-- MMcK (20130306)

function hasbit(x, p)
  return x % (p + p) >= p       
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x
end

function getRelayStatus( )
  -- get current i2c relay status
  local i2c_cmd = "i2cget -y 0 0x27 0x12 > relay_status"
  os.execute( i2c_cmd )
  local relay_status_file = io.open("relay_status", "r")
  local file_data = relay_status_file:read("*all")
  relay_status_file:close()
  local status = string.sub( file_data, 3, 4 );
  local status_int = tonumber(status, 16)
  return(status_int)
end

function num2hex(num)
  local hexstr = '0123456789abcdef'
  local s = ''
  while num > 0 do
    local mod = math.fmod(num, 16)
    s = string.sub(hexstr, mod+1, mod+1) .. s
    num = math.floor(num / 16)
  end
  if s == '' then s = '0' end
  if string.len(s)==1 then
      return('0x0' .. s)
      end
  return ('0x' .. s)
end

function callback(
  topic,    -- string
  message)  -- string

  relay_status = getRelayStatus()
--  print ( "Current Status :" .. relay_status )

  message = string.upper( message )
  relay_no = string.sub( topic, -1 )
  relay_no = tonumber( relay_no )
--  print("Topic: " .. topic .. ", message: '" .. message .. "'" .. ", Relay no: " .. relay_no)
  relay_no = ( 2 ^ (relay_no-1) )
--  print ( "Relay power no: " .. relay_no )
  if hasbit(relay_status, relay_no) and message=='OFF' then 
--    print ( "Off time for relay: " .. relay_no )
    relay_status = clearbit(relay_status, relay_no)
    end
  if not hasbit(relay_status,relay_no) and message=='ON' then
--    print ( "On time for relay: " .. relay_no )
    relay_status = setbit(relay_status,relay_no)
    end
--  print ( "New status: " .. relay_status )
  i2c_cmd = "i2cset -y 0 0x27 0x12 " .. num2hex(relay_status)
--  print ( "Cmd: " .. i2c_cmd )
  os.execute( i2c_cmd )
end

-- ------------------------------------------------------------------------- --

function is_openwrt()
  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
end

-- ------------------------------------------------------------------------- --

-- print("[mqtt_relays v0.1 2013-03-06]")

if (not is_openwrt()) then require("luarocks.require") end
local lapp = require("pl.lapp")

local args = lapp [[
  Subscribe to a specified MQTT topic
  -d,--debug                                Verbose console logging
  -i,--id            (default mqtt_sub)     MQTT client identifier
  -k,--keepalive     (default 60)           Send MQTT PING period (seconds)
  -t,--topic         (string)               Subscription topic
  -w,--will_message  (default .)            Last will and testament message
  -w,--will_qos      (default 0)            Last will and testament QOS
  -w,--will_retain   (default 0)            Last will and testament retention
  -w,--will_topic    (default .)            Last will and testament topic
]]

-- initialise the MCP23017
-- port A all outputs
i2c_cmd = "i2cset -y 0 0x27 0x00 0x00"
os.execute( i2c_cmd )

local MQTT = require("mqtt_library")

if (args.debug) then MQTT.Utility.set_debug(true) end

if (args.keepalive) then MQTT.client.KEEP_ALIVE_TIME = args.keepalive end

local mqtt_client = MQTT.client.create( '<MQTT server IP>', 1883, callback)

if (args.will_message == "."  or  args.will_topic == ".") then
  mqtt_client:connect(args.id)
else
  mqtt_client:connect(
    args.id, args.will_topic, args.will_qos, args.will_retain, args.will_message
  )
end

mqtt_client:subscribe({args.topic})

local error_message = nil

while (error_message == nil) do
  error_message = mqtt_client:handler()
  socket.sleep(0.5)  -- seconds
end

if (error_message == nil) then
  mqtt_client:unsubscribe({args.topic})
  mqtt_client:destroy()
else
  print(error_message)
end

-- ------------------------------------------------------------------------- --

This script is run with a “-t” option, which sets the topic that it will listen to.  For this example I’ve set the topic to “relay/#”, which listens for all “relay” topics.

On my desktop machine I type the following:

mosquitto_pub -h <MQTT server IP> -t relay/1 -m "on"

And the first relay comes on..

mosquitto_pub -h <MQTT server IP> -t relay/1 -m "off"

And it goes off again.  For the other relays just change the -t option: relay/2 – second relay, relay/3 – third relay, etc.  So simple it’s gift.