Carambola i2c IO board with relays – V1.1

I’ve been working on a new version of my Carambola IO board.  I’ve added an interrupt line from the MCP23017 back to the Carambola to allow for a trigger when an input changes (save on having to poll the MCP23017 to check for changes!).

One other small change was the addition of a decouple cap close to the pin headers for the Carambola module.  It helps to keep the +5V in order, on the first version there was a lot on noise on the 5V line. You can get the new schematic here: Carambola IO Board

I re-generated the artwork, broke out the light box and development kit and got to work “spinning” a new PCB.  I’m happy with my timings for exposure and development now and seem to have a repeatable process in place.

Carambola IO board - main components placed

Carambola IO board – main components placed

The double sided PCB above took about 2 hours from PC screen to etched PCB (not drilled!). Got a good clean PCB with well defined tracks.

Carambola IO board - close up of I2C extender

Carambola IO board – close up of I2C extender

Getting the new Carambola module configured was easy.  Once connected to the network I could simple copy my Lua MQTT code from my original board over SSH.

Carambola IO board - ready for testing

Carambola IO board – ready for testing

Now I have to write some more Lua code to handle the interrupt pin and post the changes on the inputs via MQTT!

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.

Carambola i2c IO board – PCB

Late last week the parts arrived from my Carambola i2c IO board and I was busy over the weekend making and assembling the PCB.  Here are the results!

I used KiCad to create the artwork and my ink-jet printer to print it out onto OHP film.  Got better results with my ink-jet than my laser printer, the ink is a lot darker than toner.  As this is a double sided PCB I spent a good while getting the alignment correct, which was tricky!

For a UV source, I got an external 24W CLF bulkhead light which at a five minute exposure time worked the business. The back of the PCB worked out fine apart from the dodgy cheep FR4 board I got from Radionics.  The photo resist film on the copper was really crappy as you can see in the photo below (look in the centre of the PCB, the artwork was black but you can see the Cu has been etched in patches).

Carambola i2c IO Board PCB back side

Carambola i2c IO Board PCB back side

The top side was a little better apart from the PCB area at the top right of the board where there was a big blotch of resist.. Also the alignment of the top and bottom artwork was a little out on the right despite my best efforts.

Carambola i2c IO Board PCB front side

Carambola i2c IO Board PCB front side

Here is a close up of the track detail where they are at their closest, nice crisp tracks.

Carambola i2c IO Board PCB track detail

Carambola i2c IO Board PCB track detail

The switcher PSU with the 3.3V regulator:

Carambola i2c IO Board PSU

Carambola i2c IO Board PSU

And finally the assembled PCB..

Carambola i2c IO Board working

Carambola i2c IO Board working

The good news: it works!

Well I haven’t tried the Ethernet UTP connection yet, but the WiFi, PSU, input stage and relays work fine.

Carambola IO board with 8 relays and 8 inputs

Time to interface this great Carambola board I have. The I2C board I made up in the last post worked really nicely with the Carambola development board, so this seemed the natural progression.. Piggyback the Carambola on the 8 channel IO board, give it some power, an Ethernet jack, and this should be a nice little board for control / monitoring.  The board can work either wired or wireless, and by using a LM2575 Buck switching regulator, the board can run from 5V to 40V.

Here is first sheet of the schematic:

Schematic for Carambola IO Board with relays (1 of 2)

Schematic for Carambola IO Board with relays (1 of 2)

The second:

Schematic for Carambola IO Board with relays (2 of 2)

Schematic for Carambola IO Board with relays (2 of 2)

All done in KiCad. Below is the PCB layout.  Might have got it onto a single sided PCB with a few links, but pushed the boat and went double sided for this one! The components are all through hole so this should be easy to make when the parts arrive. I’ve designed in a header on the right hand side that would allow a 1-wire DS18B20 to be connected along with breaking out the i2c, SPI and UART. The PCB layout fits neatly on a Eurocard PCB (100 x 160mm).

Two layered PCB for Carambola IO Board with relays

Two layered PCB for Carambola IO Board with relays

All the info you need should be here: Carambola IO Board.tar (KiCad project files, BOM for Farnell parts, etc)