Initial commit of MQTT Lua client for Eclipse Paho.
diff --git a/images/aiko_gateway.jpg b/images/aiko_gateway.jpg
new file mode 100644
index 0000000..be791a1
--- /dev/null
+++ b/images/aiko_gateway.jpg
Binary files differ
diff --git a/images/lua_mqtt_overview.dia b/images/lua_mqtt_overview.dia
new file mode 100644
index 0000000..08e4299
--- /dev/null
+++ b/images/lua_mqtt_overview.dia
Binary files differ
diff --git a/images/lua_mqtt_overview.jpg b/images/lua_mqtt_overview.jpg
new file mode 100644
index 0000000..844a104
--- /dev/null
+++ b/images/lua_mqtt_overview.jpg
Binary files differ
diff --git a/images/pebble.jpg b/images/pebble.jpg
new file mode 100644
index 0000000..f439ec3
--- /dev/null
+++ b/images/pebble.jpg
Binary files differ
diff --git a/images/playstation_portable.jpg b/images/playstation_portable.jpg
new file mode 100644
index 0000000..2fec195
--- /dev/null
+++ b/images/playstation_portable.jpg
Binary files differ
diff --git a/lua/example/example_00.lua b/lua/example/example_00.lua
new file mode 100755
index 0000000..3c2286c
--- /dev/null
+++ b/lua/example/example_00.lua
@@ -0,0 +1,84 @@
+#!/usr/bin/lua
+--
+-- example_00.lua
+-- ~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Subscribe to a topic and publish all received messages on another topic.
+--
+-- ToDo
+-- ~~~~
+-- - On failure, automatically reconnect to MQTT server.
+-- - Error handling: MQTT.client.connect()
+-- - Error handling: MQTT.client.destroy()
+-- - Error handling: MQTT.client.disconnect()
+-- - Error handling: MQTT.client.handler()
+-- - Error handling: MQTT.client.publish()
+-- - Error handling: MQTT.client.subscribe()
+-- - Error handling: MQTT.client.unsubscribe()
+-- ------------------------------------------------------------------------- --
+
+function callback(
+  topic,    -- string
+  message)  -- string
+
+  print("Topic: " .. topic .. ", message: '" .. message .. "'")
+
+  mqtt_client:publish(args.topic_p, message)
+end
+
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+if (not is_openwrt()) then require("luarocks.require") end
+local lapp = require("pl.lapp")
+
+args = lapp [[
+  Subscribe to topic_s and publish all messages on topic_p
+  -H,--host     (default localhost)   MQTT server hostname
+  -i,--id       (default example_00)  MQTT client identifier
+  -p,--port     (default 1883)        MQTT server port number
+  -s,--topic_s  (default test/1)      Subscribe topic
+  -t,--topic_p  (default test/2)      Publish topic
+]]
+
+local MQTT = require("mqtt_library")
+
+mqtt_client = MQTT.client.create(args.host, args.port, callback)
+
+mqtt_client:connect(args.id)
+
+mqtt_client:subscribe({ args.topic_s })
+
+local error_message = nil
+
+while (error_message == nil) do
+  error_message = mqtt_client:handler()
+  socket.sleep(1.0)  -- seconds
+end
+
+if (error_message == nil) then
+  mqtt_client:unsubscribe({ args.topic_s })
+  mqtt_client:destroy()
+else
+  print(error_message)
+end
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/example/example_01.lua b/lua/example/example_01.lua
new file mode 100755
index 0000000..9b8bfdd
--- /dev/null
+++ b/lua/example/example_01.lua
@@ -0,0 +1,90 @@
+#!/usr/bin/lua
+--
+-- example_01.lua
+-- ~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Subscribe to a topic on one MQTT server and publish all received messages
+-- to a topic on another MQTT server.
+--
+-- ToDo
+-- ~~~~
+-- - On failure, automatically reconnect to MQTT server(s).
+-- ------------------------------------------------------------------------- --
+
+function callback(
+  topic,    -- string
+  message)  -- string
+
+  print("Topic: " .. topic .. ", message: '" .. message .. "'")
+
+  mqtt_client2:publish(args.topic_p, message)
+end
+
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+if (not is_openwrt()) then require("luarocks.require") end
+local lapp = require("pl.lapp")
+
+args = lapp [[
+  Subscribe to topic_s and publish all messages on topic_p
+  -g,--host_s   (default localhost)   Subscribe MQTT server hostname
+  -H,--host_p   (default localhost)   Publish MQTT server hostname
+  -i,--id       (default example_01)  MQTT client identifier
+  -p,--port_s   (default 1883)        Subscribe MQTT server port number
+  -q,--port_p   (default 1883)        Publish MQTT server port number
+  -s,--topic_s  (default test/1)      Subscribe topic
+  -t,--topic_p  (default test/2)      Publish topic
+]]
+
+local MQTT = require("mqtt_library")
+
+mqtt_client1 = MQTT.client.create(args.host_s, args.port_s, callback)
+mqtt_client2 = MQTT.client.create(args.host_p, args.port_p)
+
+mqtt_client1:connect(args.id .. "a")
+mqtt_client2:connect(args.id .. "b")
+
+mqtt_client1:subscribe({ args.topic_s })
+
+local error_message1 = nil
+local error_message2 = nil
+
+while (error_message1 == nil and error_message2 == nil) do
+  error_message1 = mqtt_client1:handler()
+  error_message2 = mqtt_client2:handler()
+  socket.sleep(1.0)  -- seconds
+end
+
+if (error_message1 == nil) then
+  mqtt_client1:unsubscribe({ args.topic_s })
+  mqtt_client1:destroy()
+else
+  print(error_message1)
+end
+
+if (error_message2 == nil) then
+  mqtt_client2:destroy()
+else
+  print(error_message2)
+end
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/example/example_02.lua b/lua/example/example_02.lua
new file mode 100755
index 0000000..575c411
--- /dev/null
+++ b/lua/example/example_02.lua
@@ -0,0 +1,71 @@
+#!/usr/bin/lua
+--
+-- example_02.lua
+-- ~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Publish a sequence of messages to a specified topic.
+-- Used to control some coloured RGB LEDs.
+--
+-- ToDo
+-- ~~~~
+-- - On failure, automatically reconnect to MQTT server.
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+if (not is_openwrt()) then require("luarocks.require") end
+local lapp = require("pl.lapp")
+
+local args = lapp [[
+  Subscribe to topic1 and publish all messages on topic2
+  -H,--host   (default localhost)   MQTT server hostname
+  -i,--id     (default example_02)  MQTT client identifier
+  -p,--port   (default 1883)        MQTT server port number
+  -s,--sleep  (default 5.0)         Sleep time between commands
+  -t,--topic  (default test/2)      Topic on which to publish
+]]
+
+local MQTT = require("mqtt_library")
+
+local mqtt_client = MQTT.client.create(args.host, args.port)
+
+mqtt_client:connect(args.id)
+
+local error_message = nil
+local index = 1
+local messages = { "c010000", "c000100", "c000001" }
+
+while (error_message == nil) do
+  mqtt_client:publish(args.topic, messages[index]);
+
+  index = index + 1
+  if (index > #messages) then index = 1 end
+
+  socket.sleep(args.sleep)  -- seconds
+  error_message = mqtt_client:handler()
+end
+
+if (error_message == nil) then
+  mqtt_client:destroy()
+else
+  print(error_message)
+end
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/example/mqtt_publish.lua b/lua/example/mqtt_publish.lua
new file mode 100755
index 0000000..d39a1a7
--- /dev/null
+++ b/lua/example/mqtt_publish.lua
@@ -0,0 +1,74 @@
+#!/usr/bin/lua
+--
+-- mqtt_publish.lua
+-- ~~~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Publish an MQTT message on the specified topic with an optional last will.
+--
+-- References
+-- ~~~~~~~~~~
+-- Lapp Framework: Lua command line parsing
+--   http://lua-users.org/wiki/LappFramework
+--
+-- ToDo
+-- ~~~~
+-- None, yet.
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+print("[mqtt_publish v0.2 2012-06-01]")
+
+if (not is_openwrt()) then require("luarocks.require") end
+local lapp = require("pl.lapp")
+
+local args = lapp [[
+  Publish a message to a specified MQTT topic
+  -d,--debug                                Verbose console logging
+  -H,--host          (default localhost)    MQTT server hostname
+  -i,--id            (default mqtt_pub)     MQTT client identifier
+  -m,--message       (string)               Message to be published
+  -p,--port          (default 1883)         MQTT server port number
+  -t,--topic         (string)               Topic on which to publish
+  -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
+]]
+
+local MQTT = require("mqtt_library")
+
+if (args.debug) then MQTT.Utility.set_debug(true) end
+
+local mqtt_client = MQTT.client.create(args.host, args.port)
+
+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:publish(args.topic, args.message)
+
+mqtt_client:destroy()
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/example/mqtt_subscribe.lua b/lua/example/mqtt_subscribe.lua
new file mode 100755
index 0000000..e1c87ae
--- /dev/null
+++ b/lua/example/mqtt_subscribe.lua
@@ -0,0 +1,97 @@
+#!/usr/bin/lua
+--
+-- mqtt_subscribe.lua
+-- ~~~~~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Subscribe to an MQTT topic and display any received messages.
+--
+-- References
+-- ~~~~~~~~~~
+-- Lapp Framework: Lua command line parsing
+--   http://lua-users.org/wiki/LappFramework
+--
+-- ToDo
+-- ~~~~
+-- None, yet.
+-- ------------------------------------------------------------------------- --
+
+function callback(
+  topic,    -- string
+  message)  -- string
+
+  print("Topic: " .. topic .. ", message: '" .. message .. "'")
+end
+
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+print("[mqtt_subscribe v0.2 2012-06-01]")
+
+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
+  -H,--host          (default localhost)    MQTT server hostname
+  -i,--id            (default mqtt_sub)     MQTT client identifier
+  -k,--keepalive     (default 60)           Send MQTT PING period (seconds)
+  -p,--port          (default 1883)         MQTT server port number
+  -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
+]]
+
+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(args.host, args.port, 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(1.0)  -- seconds
+end
+
+if (error_message == nil) then
+  mqtt_client:unsubscribe({args.topic})
+  mqtt_client:destroy()
+else
+  print(error_message)
+end
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/example/mqtt_test.lua b/lua/example/mqtt_test.lua
new file mode 100755
index 0000000..33a6a82
--- /dev/null
+++ b/lua/example/mqtt_test.lua
@@ -0,0 +1,94 @@
+#!/usr/bin/lua
+--
+-- mqtt_test.lua
+-- ~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- ------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Description
+-- ~~~~~~~~~~~
+-- Repetitively publishes MQTT messages on the topic_p,
+-- until the "quit" message is received on the topic_s.
+--
+-- References
+-- ~~~~~~~~~~
+-- Lapp Framework: Lua command line parsing
+--   http://lua-users.org/wiki/LappFramework
+--
+-- ToDo
+-- ~~~~
+-- - On failure, automatically reconnect to MQTT server.
+-- ------------------------------------------------------------------------- --
+
+function callback(
+  topic,    -- string
+  payload)  -- string
+
+  print("mqtt_test:callback(): " .. topic .. ": " .. payload)
+
+  if (payload == "quit") then running = false end
+end
+
+-- ------------------------------------------------------------------------- --
+
+function is_openwrt()
+  return(os.getenv("USER") == "root")  -- Assume logged in as "root" on OpenWRT
+end
+
+-- ------------------------------------------------------------------------- --
+
+print("[mqtt_test v0.2 2012-06-01]")
+
+if (not is_openwrt()) then require("luarocks.require") end
+local lapp = require("pl.lapp")
+
+local args = lapp [[
+  Test Lua MQTT client library
+  -d,--debug                         Verbose console logging
+  -i,--id       (default mqtt_test)  MQTT client identifier
+  -p,--port     (default 1883)       MQTT server port number
+  -s,--topic_s  (default test/2)     Subscribe topic
+  -t,--topic_p  (default test/1)     Publish topic
+  <host>        (default localhost)  MQTT server hostname
+]]
+
+local MQTT = require("mqtt_library")
+
+if (args.debug) then MQTT.Utility.set_debug(true) end
+
+local mqtt_client = MQTT.client.create(args.host, args.port, callback)
+
+mqtt_client:connect(args.id)
+
+mqtt_client:publish(args.topic_p, "*** Lua test start ***")
+mqtt_client:subscribe({ args.topic_s })
+
+local error_message = nil
+local running = true
+
+while (error_message == nil and running) do
+  error_message = mqtt_client:handler()
+
+  if (error_message == nil) then
+    mqtt_client:publish(args.topic_p, "*** Lua test message ***")
+    socket.sleep(1.0)  -- seconds
+  end
+end
+
+if (error_message == nil) then
+  mqtt_client:unsubscribe({ args.topic_s })
+  mqtt_client:destroy()
+else
+  print(error_message)
+end
+
+-- ------------------------------------------------------------------------- --
diff --git a/lua/mqtt_library.lua b/lua/mqtt_library.lua
new file mode 100644
index 0000000..1f53c2e
--- /dev/null
+++ b/lua/mqtt_library.lua
@@ -0,0 +1,777 @@
+-- mqtt_library.lua
+-- ~~~~~~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- -------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial API and implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Documentation
+-- ~~~~~~~~~~~~~
+-- MQTT Lua web-site
+--   http://eclipse.org/paho/...  -- FILL ME IN !
+--
+-- References
+-- ~~~~~~~~~~
+-- MQTT web-site
+--   http://mqtt.org
+
+-- MQTT protocol specification 3.1
+--   https://www.ibm.com/developerworks/webservices/library/ws-mqtt
+--   http://mqtt.org/wiki/doku.php/mqtt_protocol   # Clarifications
+--
+-- Notes
+-- ~~~~~
+-- - Always assumes MQTT connection "clean session" enabled.
+-- - Supports connection last will and testament message.
+-- - Does not support connection username and password.
+-- - Fixed message header byte 1, only implements the "message type".
+-- - Only supports QOS level 0.
+-- - Maximum payload length is 268,435,455 bytes (as per specification).
+-- - Publish message doesn't support "message identifier".
+-- - Subscribe acknowledgement messages don't check granted QOS level.
+-- - Outstanding subscribe acknowledgement messages aren't escalated.
+-- - Works on the Sony PlayStation Portable (aka Sony PSP) ...
+--     See http://en.wikipedia.org/wiki/Lua_Player_HM
+--
+-- ToDo
+-- ~~~~
+-- * Consider when payload needs to be an array of bytes (not characters).
+-- * Maintain both "last_activity_out" and "last_activity_in".
+-- * - http://mqtt.org/wiki/doku.php/keepalive_for_the_client
+-- * Update "last_activity_in" when messages are received.
+-- * When a PINGREQ is sent, must check for a PINGRESP, within KEEP_ALIVE_TIME..
+--   * Otherwise, fail the connection.
+-- * When connecting, wait for CONACK, until KEEP_ALIVE_TIME, before failing.
+-- * Should MQTT.client:connect() be asynchronous with a callback ?
+-- * Review all public APIs for asynchronous callback behaviour.
+-- * Implement parse PUBACK message.
+-- * Handle failed subscriptions, i.e no subscription acknowledgement received.
+-- * Fix problem when KEEP_ALIVE_TIME is short, e.g. mqtt_publish -k 1
+--     MQTT.client:handler(): Message length mismatch
+-- - On socket error, optionally try reconnection to MQTT server.
+-- - Consider use of assert() and pcall() ?
+-- - Only expose public API functions, don't expose internal API functions.
+-- - Refactor "if self.connected()" to "self.checkConnected(error_message)".
+-- - Maintain and publish messaging statistics.
+-- - Memory heap/stack monitoring.
+-- - When debugging, why isn't mosquitto sending back CONACK error code ?
+-- - Subscription callbacks invoked by topic name (including wildcards).
+-- - Implement asynchronous state machine, rather than single-thread waiting.
+--   - After CONNECT, expect and wait for a CONACK.
+-- - Implement complete MQTT broker (server).
+-- - Consider using Copas http://keplerproject.github.com/copas/manual.html
+-- ------------------------------------------------------------------------- --
+
+function isPsp() return(Socket ~= nil) end
+
+if (not isPsp()) then
+  require("socket")
+  require("io")
+  require("ltn12")
+--require("ssl")
+end
+
+local MQTT = {}
+
+MQTT.Utility = require("utility")
+
+MQTT.VERSION = 0x03
+
+MQTT.ERROR_TERMINATE = false      -- Message handler errors terminate process ?
+
+MQTT.DEFAULT_BROKER_HOSTNAME = "localhost"
+
+MQTT.client = {}
+MQTT.client.__index = MQTT.client
+
+MQTT.client.DEFAULT_PORT       = 1883
+MQTT.client.KEEP_ALIVE_TIME    = 60 -- seconds (maximum is 65535)
+MQTT.client.MAX_PAYLOAD_LENGTH = 268435455 -- bytes
+
+-- MQTT 3.1 Specification: Section 2.1: Fixed header, Message type
+
+MQTT.message = {}
+MQTT.message.TYPE_RESERVED    = 0x00
+MQTT.message.TYPE_CONNECT     = 0x01
+MQTT.message.TYPE_CONACK      = 0x02
+MQTT.message.TYPE_PUBLISH     = 0x03
+MQTT.message.TYPE_PUBACK      = 0x04
+MQTT.message.TYPE_PUBREC      = 0x05
+MQTT.message.TYPE_PUBREL      = 0x06
+MQTT.message.TYPE_PUBCOMP     = 0x07
+MQTT.message.TYPE_SUBSCRIBE   = 0x08
+MQTT.message.TYPE_SUBACK      = 0x09
+MQTT.message.TYPE_UNSUBSCRIBE = 0x0a
+MQTT.message.TYPE_UNSUBACK    = 0x0b
+MQTT.message.TYPE_PINGREQ     = 0x0c
+MQTT.message.TYPE_PINGRESP    = 0x0d
+MQTT.message.TYPE_DISCONNECT  = 0x0e
+MQTT.message.TYPE_RESERVED    = 0x0f
+
+-- MQTT 3.1 Specification: Section 3.2: CONACK acknowledge connection errors
+-- http://mqtt.org/wiki/doku.php/extended_connack_codes
+
+MQTT.CONACK = {}
+MQTT.CONACK.error_message = {          -- CONACK return code used as the index
+  "Unacceptable protocol version",
+  "Identifer rejected",
+  "Server unavailable",
+  "Bad user name or password",
+  "Not authorized"
+--"Invalid will topic"                 -- Proposed
+}
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Create an MQTT client instance
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+function MQTT.client.create(                                      -- Public API
+  hostname,  -- string:   Host name or address of the MQTT broker
+  port,      -- integer:  Port number of the MQTT broker (default: 1883)
+  callback)  -- function: Invoked when subscribed topic messages received
+             -- return:   mqtt_client table
+
+  local mqtt_client = {}
+
+  setmetatable(mqtt_client, MQTT.client)
+
+  mqtt_client.callback = callback  -- function(topic, payload)
+  mqtt_client.hostname = hostname
+  mqtt_client.port     = port or MQTT.client.DEFAULT_PORT
+
+  mqtt_client.connected     = false
+  mqtt_client.destroyed     = false
+  mqtt_client.last_activity = 0
+  mqtt_client.message_id    = 0
+  mqtt_client.outstanding   = {}
+  mqtt_client.socket_client = nil
+
+  return(mqtt_client)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Client request a connection to an MQTT broker (server)
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.1: CONNECT
+
+function MQTT.client:connect(                                     -- Public API
+  identifier,    -- string: MQTT client identifier (maximum 23 characters)
+  will_topic,    -- string: Last will and testament topic
+  will_qos,      -- byte:   Last will and testament Quality Of Service
+  will_retain,   -- byte:   Last will and testament retention status
+  will_message)  -- string: Last will and testament message
+                 -- return: nil or error message
+
+  if (self.connected) then
+    return("MQTT.client:connect(): Already connected")
+  end
+
+  MQTT.Utility.debug("MQTT.client:connect(): " .. identifier)
+
+  self.socket_client = socket.connect(self.hostname, self.port)
+
+  if (self.socket_client == nil) then
+    return("MQTT.client:connect(): Couldn't open MQTT broker connection")
+  end
+
+  MQTT.Utility.socket_wait_connected(self.socket_client)
+
+  self.connected = true
+
+-- Construct CONNECT variable header fields (bytes 1 through 9)
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  local payload
+  payload = MQTT.client.encode_utf8("MQIsdp")
+  payload = payload .. string.char(MQTT.VERSION)
+
+-- Connect flags (byte 10)
+-- ~~~~~~~~~~~~~
+-- bit    7: Username flag =  0  -- recommended no more than 12 characters
+-- bit    6: Password flag =  0  -- ditto
+-- bit    5: Will retain   =  0
+-- bits 4,3: Will QOS      = 00
+-- bit    2: Will flag     =  0
+-- bit    1: Clean session =  1
+-- bit    0: Unused        =  0
+
+  if (will_topic == nil) then
+    payload = payload .. string.char(0x02)       -- Clean session, no last will
+  else
+    local flags
+    flags = MQTT.Utility.shift_left(will_retain, 5)
+    flags = flags + MQTT.Utility.shift_left(will_qos, 3) + 0x06
+    payload = payload .. string.char(flags)
+  end
+
+-- Keep alive timer (bytes 11 LSB and 12 MSB, unit is seconds)
+-- ~~~~~~~~~~~~~~~~~
+  payload = payload .. string.char(math.floor(MQTT.client.KEEP_ALIVE_TIME / 256))
+  payload = payload .. string.char(MQTT.client.KEEP_ALIVE_TIME % 256)
+
+-- Client identifier
+-- ~~~~~~~~~~~~~~~~~
+  payload = payload .. MQTT.client.encode_utf8(identifier)
+
+-- Last will and testament
+-- ~~~~~~~~~~~~~~~~~~~~~~~
+  if (will_topic ~= nil) then
+    payload = payload .. MQTT.client.encode_utf8(will_topic)
+    payload = payload .. MQTT.client.encode_utf8(will_message)
+  end
+
+-- Send MQTT message
+-- ~~~~~~~~~~~~~~~~~
+  return(self:message_write(MQTT.message.TYPE_CONNECT, payload))
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Destroy an MQTT client instance
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+function MQTT.client:destroy()                                    -- Public API
+  MQTT.Utility.debug("MQTT.client:destroy()")
+
+  if (self.destroyed == false) then
+    self.destroyed = true         -- Avoid recursion when message_write() fails
+
+    if (self.connected) then self:disconnect() end
+
+    self.callback = nil
+    self.outstanding = nil
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Disconnect message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.14: Disconnect notification
+--
+-- bytes 1,2: Fixed message header, see MQTT.client:message_write()
+
+function MQTT.client:disconnect()                                 -- Public API
+  MQTT.Utility.debug("MQTT.client:disconnect()")
+
+  if (self.connected) then
+    self:message_write(MQTT.message.TYPE_DISCONNECT, nil)
+    self.socket_client:close()
+    self.connected = false
+  else
+    error("MQTT.client:disconnect(): Already disconnected")
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Encode a message string using UTF-8 (for variable header)
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 2.5: MQTT and UTF-8
+--
+-- byte  1:   String length MSB
+-- byte  2:   String length LSB
+-- bytes 3-n: String encoded as UTF-8
+
+function MQTT.client.encode_utf8(                               -- Internal API
+  input)  -- string
+
+  local output
+  output = string.char(math.floor(#input / 256))
+  output = output .. string.char(#input % 256)
+  output = output .. input
+
+  return(output)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Handle received messages and maintain keep-alive PING messages
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- This function must be invoked periodically (more often than the
+-- MQTT.client.KEEP_ALIVE_TIME) which maintains the connection and
+-- services the incoming subscribed topic messages.
+
+function MQTT.client:handler()                                    -- Public API
+  if (self.connected == false) then
+    error("MQTT.client:handler(): Not connected")
+  end
+
+  MQTT.Utility.debug("MQTT.client:handler()")
+
+-- Transmit MQTT PING message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.13: PING request
+--
+-- bytes 1,2: Fixed message header, see MQTT.client:message_write()
+
+  local activity_timeout = self.last_activity + MQTT.client.KEEP_ALIVE_TIME
+
+  if (MQTT.Utility.get_time() > activity_timeout) then
+    MQTT.Utility.debug("MQTT.client:handler(): PINGREQ")
+
+    self:message_write(MQTT.message.TYPE_PINGREQ, nil)
+  end
+
+-- Check for available client socket data
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  local ready = MQTT.Utility.socket_ready(self.socket_client)
+
+  if (ready) then
+    local error_message, buffer =
+      MQTT.Utility.socket_receive(self.socket_client)
+
+    if (error_message ~= nil) then
+      self:destroy()
+      error_message = "socket_client:receive(): " .. error_message
+      MQTT.Utility.debug(error_message)
+      return(error_message)
+    end
+
+    if (buffer ~= nil and #buffer > 0) then
+      local index = 1
+
+      -- Parse individual messages (each must be at least 2 bytes long)
+      -- Decode "remaining length" (MQTT v3.1 specification pages 6 and 7)
+
+      while (index < #buffer) do
+        local message_type_flags = string.byte(buffer, index)
+        local multiplier = 1
+        local remaining_length = 0
+
+        repeat
+          index = index + 1
+          local digit = string.byte(buffer, index)
+          remaining_length = remaining_length + ((digit % 128) * multiplier)
+          multiplier = multiplier * 128
+        until digit < 128                              -- check continuation bit
+
+        local message = string.sub(buffer, index + 1, index + remaining_length)
+
+        if (#message == remaining_length) then
+          self:parse_message(message_type_flags, remaining_length, message)
+        else
+          MQTT.Utility.debug(
+            "MQTT.client:handler(): Incorrect remaining length: " ..
+            remaining_length .. " ~= message length: " .. #message
+          )
+        end
+
+        index = index + remaining_length + 1
+      end
+
+      -- Check for any left over bytes, i.e. partial message received
+
+      if (index ~= (#buffer + 1)) then
+        local error_message =
+          "MQTT.client:handler(): Partial message received" ..
+          index .. " ~= " .. (#buffer + 1)
+
+        if (MQTT.ERROR_TERMINATE) then         -- TODO: Refactor duplicate code
+          self:destroy()
+          error(error_message)
+        else
+          MQTT.Utility.debug(error_message)
+        end
+      end
+    end
+  end
+
+  return(nil)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit an MQTT message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 2.1: Fixed header
+--
+-- byte  1:   Message type and flags (DUP, QOS level, and Retain) fields
+-- bytes 2-5: Remaining length field (between one and four bytes long)
+-- bytes m- : Optional variable header and payload
+
+function MQTT.client:message_write(                             -- Internal API
+  message_type,  -- enumeration
+  payload)       -- string
+                 -- return: nil or error message
+
+-- TODO: Complete implementation of fixed header byte 1
+
+  local message = string.char(MQTT.Utility.shift_left(message_type, 4))
+
+  if (payload == nil) then
+    message = message .. string.char(0)  -- Zero length, no payload
+  else
+    if (#payload > MQTT.client.MAX_PAYLOAD_LENGTH) then
+      return(
+        "MQTT.client:message_write(): Payload length = " .. #payload ..
+        " exceeds maximum of " .. MQTT.client.MAX_PAYLOAD_LENGTH
+      )
+    end
+
+    -- Encode "remaining length" (MQTT v3.1 specification pages 6 and 7)
+
+    local remaining_length = #payload
+
+    repeat
+      local digit = remaining_length % 128
+      remaining_length = math.floor(remaining_length / 128)
+      if (remaining_length > 0) then digit = digit + 128 end -- continuation bit
+      message = message .. string.char(digit)
+    until remaining_length == 0
+
+    message = message .. payload
+  end
+
+  local status, error_message = self.socket_client:send(message)
+
+  if (status == nil) then
+    self:destroy()
+    return("MQTT.client:message_write(): " .. error_message)
+  end
+
+  self.last_activity = MQTT.Utility.get_time()
+  return(nil)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT message
+-- ~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 2.1: Fixed header
+--
+-- byte  1:   Message type and flags (DUP, QOS level, and Retain) fields
+-- bytes 2-5: Remaining length field (between one and four bytes long)
+-- bytes m- : Optional variable header and payload
+--
+-- The message type/flags and remaining length are already parsed and
+-- removed from the message by the time this function is invoked.
+-- Leaving just the optional variable header and payload.
+
+function MQTT.client:parse_message(                             -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string: Optional variable header and payload
+
+  local message_type = MQTT.Utility.shift_right(message_type_flags, 4)
+
+-- TODO: MQTT.message.TYPE table should include "parser handler" function.
+--       This would nicely collapse the if .. then .. elseif .. end.
+
+  if (message_type == MQTT.message.TYPE_CONACK) then
+    self:parse_message_conack(message_type_flags, remaining_length, message)
+
+  elseif (message_type == MQTT.message.TYPE_PUBLISH) then
+    self:parse_message_publish(message_type_flags, remaining_length, message)
+
+  elseif (message_type == MQTT.message.TYPE_PUBACK) then
+    print("MQTT.client:parse_message(): PUBACK -- UNIMPLEMENTED --")    -- TODO
+
+  elseif (message_type == MQTT.message.TYPE_SUBACK) then
+    self:parse_message_suback(message_type_flags, remaining_length, message)
+
+  elseif (message_type == MQTT.message.TYPE_UNSUBACK) then
+    self:parse_message_unsuback(message_type_flags, remaining_length, message)
+
+  elseif (message_type == MQTT.message.TYPE_PINGREQ) then
+    self:ping_response()
+
+  elseif (message_type == MQTT.message.TYPE_PINGRESP) then
+    self:parse_message_pingresp(message_type_flags, remaining_length, message)
+
+  else
+    local error_message =
+      "MQTT.client:parse_message(): Unknown message type: " .. message_type
+
+    if (MQTT.ERROR_TERMINATE) then             -- TODO: Refactor duplicate code
+      self:destroy()
+      error(error_message)
+    else
+      MQTT.Utility.debug(error_message)
+    end
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT CONACK message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.2: CONACK Acknowledge connection
+--
+-- byte 1: Reserved value
+-- byte 2: Connect return code, see MQTT.CONACK.error_message[]
+
+function MQTT.client:parse_message_conack(                      -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string
+
+  local me = "MQTT.client:parse_message_conack()"
+  MQTT.Utility.debug(me)
+
+  if (remaining_length ~= 2) then
+    error(me .. ": Invalid remaining length")
+  end
+
+  local return_code = string.byte(message, 2)
+
+  if (return_code ~= 0) then
+    local error_message = "Unknown return code"
+
+    if (return_code <= table.getn(MQTT.CONACK.error_message)) then
+      error_message = MQTT.CONACK.error_message[return_code]
+    end
+
+    error(me .. ": Connection refused: " .. error_message)
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT PINGRESP message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.13: PING response
+
+function MQTT.client:parse_message_pingresp(                    -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string
+
+  local me = "MQTT.client:parse_message_pingresp()"
+  MQTT.Utility.debug(me)
+
+  if (remaining_length ~= 0) then
+    error(me .. ": Invalid remaining length")
+  end
+
+-- ToDo: self.ping_response_outstanding = false
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT PUBLISH message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.3: Publish message
+--
+-- Variable header ..
+-- bytes 1- : Topic name and optional Message Identifier (if QOS > 0)
+-- bytes m- : Payload
+
+function MQTT.client:parse_message_publish(                     -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string
+
+  local me = "MQTT.client:parse_message_publish()"
+  MQTT.Utility.debug(me)
+
+  if (self.callback ~= nil) then
+    if (remaining_length < 3) then
+      error(me .. ": Invalid remaining length: " .. remaining_length)
+    end
+
+    local topic_length = string.byte(message, 1) * 256
+    topic_length = topic_length + string.byte(message, 2)
+    local topic  = string.sub(message, 3, topic_length + 2)
+    local index  = topic_length + 3
+
+-- Handle optional Message Identifier, for QOS levels 1 and 2
+-- TODO: Enable Subscribe with QOS and deal with PUBACK, etc.
+
+    local qos = MQTT.Utility.shift_right(message_type_flags, 1) % 3
+
+    if (qos > 0) then
+      local message_id = string.byte(message, index) * 256
+      message_id = message_id + string.byte(message, index + 1)
+      index = index + 2
+    end
+
+    local payload_length = remaining_length - index + 1
+    local payload = string.sub(message, index, index + payload_length - 1)
+
+    self.callback(topic, payload)
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT SUBACK message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.9: SUBACK Subscription acknowledgement
+--
+-- bytes 1,2: Message Identifier
+-- bytes 3- : List of granted QOS for each subscribed topic
+
+function MQTT.client:parse_message_suback(                      -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string
+
+  local me = "MQTT.client:parse_message_suback()"
+  MQTT.Utility.debug(me)
+
+  if (remaining_length < 3) then
+    error(me .. ": Invalid remaining length: " .. remaining_length)
+  end
+
+  local message_id  = string.byte(message, 1) * 256 + string.byte(message, 2)
+  local outstanding = self.outstanding[message_id]
+
+  if (outstanding == nil) then
+    error(me .. ": No outstanding message: " .. message_id)
+  end
+
+  self.outstanding[message_id] = nil
+
+  if (outstanding[1] ~= "subscribe") then
+    error(me .. ": Outstanding message wasn't SUBSCRIBE")
+  end
+
+  local topic_count = table.getn(outstanding[2])
+
+  if (topic_count ~= remaining_length - 2) then
+    error(me .. ": Didn't received expected number of topics: " .. topic_count)
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Parse MQTT UNSUBACK message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.11: UNSUBACK Unsubscription acknowledgement
+--
+-- bytes 1,2: Message Identifier
+
+function MQTT.client:parse_message_unsuback(                    -- Internal API
+  message_type_flags,  -- byte
+  remaining_length,    -- integer
+  message)             -- string
+
+  local me = "MQTT.client:parse_message_unsuback()"
+  MQTT.Utility.debug(me)
+
+  if (remaining_length ~= 2) then
+    error(me .. ": Invalid remaining length")
+  end
+
+  local message_id = string.byte(message, 1) * 256 + string.byte(message, 2)
+
+  local outstanding = self.outstanding[message_id]
+
+  if (outstanding == nil) then
+    error(me .. ": No outstanding message: " .. message_id)
+  end
+
+  self.outstanding[message_id] = nil
+
+  if (outstanding[1] ~= "unsubscribe") then
+    error(me .. ": Outstanding message wasn't UNSUBSCRIBE")
+  end
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Ping response message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.13: PING response
+
+function MQTT.client:ping_response()                            -- Internal API
+  MQTT.Utility.debug("MQTT.client:ping_response()")
+
+  if (self.connected == false) then
+    error("MQTT.client:ping_response(): Not connected")
+  end
+
+  self:message_write(MQTT.message.TYPE_PINGRESP, nil)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Publish message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.3: Publish message
+--
+-- bytes 1,2: Fixed message header, see MQTT.client:message_write()
+-- Variable header ..
+-- bytes 3- : Topic name and optional Message Identifier (if QOS > 0)
+-- bytes m- : Payload
+
+function MQTT.client:publish(                                     -- Public API
+  topic,    -- string
+  payload)  -- string
+
+  if (self.connected == false) then
+    error("MQTT.client:publish(): Not connected")
+  end
+
+  MQTT.Utility.debug("MQTT.client:publish(): " .. topic)
+
+  local message = MQTT.client.encode_utf8(topic) .. payload
+
+  self:message_write(MQTT.message.TYPE_PUBLISH, message)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Subscribe message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.8: Subscribe to named topics
+--
+-- bytes 1,2: Fixed message header, see MQTT.client:message_write()
+-- Variable header ..
+-- bytes 3,4: Message Identifier
+-- bytes 5- : List of topic names and their QOS level
+
+function MQTT.client:subscribe(                                   -- Public API
+  topics)  -- table of strings
+
+  if (self.connected == false) then
+    error("MQTT.client:subscribe(): Not connected")
+  end
+
+  self.message_id = self.message_id + 1
+
+  local message
+  message = string.char(math.floor(self.message_id / 256))
+  message = message .. string.char(self.message_id % 256)
+
+  for index, topic in ipairs(topics) do
+    MQTT.Utility.debug("MQTT.client:subscribe(): " .. topic)
+    message = message .. MQTT.client.encode_utf8(topic)
+    message = message .. string.char(0)  -- QOS level 0
+  end
+
+  self:message_write(MQTT.message.TYPE_SUBSCRIBE, message)
+
+  self.outstanding[self.message_id] = { "subscribe", topics }
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --
+-- Transmit MQTT Unsubscribe message
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- MQTT 3.1 Specification: Section 3.10: Unsubscribe from named topics
+--
+-- bytes 1,2: Fixed message header, see MQTT.client:message_write()
+-- Variable header ..
+-- bytes 3,4: Message Identifier
+-- bytes 5- : List of topic names
+
+
+function MQTT.client:unsubscribe(                                 -- Public API
+  topics)  -- table of strings
+
+  if (self.connected == false) then
+    error("MQTT.client:unsubscribe(): Not connected")
+  end
+
+  self.message_id = self.message_id + 1
+
+  local message
+  message = string.char(math.floor(self.message_id / 256))
+  message = message .. string.char(self.message_id % 256)
+
+  for index, topic in ipairs(topics) do
+    MQTT.Utility.debug("MQTT.client:unsubscribe(): " .. topic)
+    message = message .. MQTT.client.encode_utf8(topic)
+  end
+
+  self:message_write(MQTT.message.TYPE_UNSUBSCRIBE, message)
+
+  self.outstanding[self.message_id] = { "unsubscribe", topics }
+end
+
+-- For ... MQTT = require("mqtt_library")
+
+return(MQTT)
diff --git a/lua/utility.lua b/lua/utility.lua
new file mode 100644
index 0000000..385cb5a
--- /dev/null
+++ b/lua/utility.lua
@@ -0,0 +1,170 @@
+-- utility.lua
+-- ~~~~~~~~~~~
+-- Version: 0.2 2012-06-01
+-- -------------------------------------------------------------------------- --
+-- Copyright (c) 2011-2012 Geekscape Pty. Ltd.
+-- All rights reserved. This program and the accompanying materials
+-- are made available under the terms of the Eclipse Public License v1.0
+-- which accompanies this distribution, and is available at
+-- http://www.eclipse.org/legal/epl-v10.html
+--
+-- Contributors:
+--    Andy Gelme - Initial API and implementation
+-- -------------------------------------------------------------------------- --
+--
+-- Notes
+-- ~~~~~
+-- - Works on the Sony PlayStation Portable (aka Sony PSP) ...
+--     See http://en.wikipedia.org/wiki/Lua_Player_HM
+--
+-- ToDo
+-- ~~~~
+-- - shift_left() should mask bits past the 8, 16, 32 and 64-bit boundaries.
+-- ------------------------------------------------------------------------- --
+
+local function isPsp() return(Socket ~= nil) end
+
+if (isPsp()) then socket = Socket end                        -- Compatibility !
+
+-- ------------------------------------------------------------------------- --
+
+local debug_flag = false
+
+local function set_debug(value) debug_flag = value end
+
+local function debug(message)
+  if (debug_flag) then print(message) end
+end
+
+-- ------------------------------------------------------------------------- --
+
+local function dump_string(value)
+  local index
+
+  for index = 1, string.len(value) do
+    print(string.format("%d: %02x", index, string.byte(value, index)))
+  end
+end
+
+-- ------------------------------------------------------------------------- --
+
+local timer
+
+if (isPsp()) then
+  timer = Timer.new()
+  timer:start()
+end
+
+local function get_time()
+  if (isPsp()) then
+    return(timer:time() / 1000)
+  else
+    return(socket.gettime())
+  end
+end
+
+local function expired(last_time, duration, type)
+  local time_expired = get_time() >= (last_time + duration)
+
+  if (time_expired) then debug("Event: " .. type) end
+  return(time_expired)
+end
+
+-- ------------------------------------------------------------------------- --
+
+local function shift_left(value, shift)
+  return(value * 2 ^ shift)
+end
+
+local function shift_right(value, shift)
+  return(math.floor(value / 2 ^ shift))
+end
+
+-- ------------------------------------------------------------------------- --
+
+local function socket_ready(socket_client)
+  local ready, read_sockets, write_sockets, error_state = true, nil, nil, nil
+
+  if (not isPsp()) then
+    read_sockets, write_sockets, error_state =
+      socket.select({socket_client}, nil, 0.001)
+
+    if (#read_sockets == 0) then ready = false end
+  end
+
+  return(ready)
+end
+
+local function socket_receive(socket_client, byte_count)
+  local response, buffer, error_message = nil, nil, nil
+
+  byte_count = byte_count or 128                                     -- default
+
+  if (isPsp()) then
+    buffer = socket_client:recv(byte_count)
+  else
+    response, error_message, buffer = socket_client:receive("*a")
+
+    if (error_message == "timeout") then error_message = nil end
+  end
+
+  return(error_message), (buffer)                            -- nil or "closed"
+end
+
+local function socket_wait_connected(socket_client)
+  if (isPsp()) then
+    while (socket_client:isConnected() == false) do
+      System.sleep(100)
+    end
+  else
+    socket_client:settimeout(0.001)     -- So that socket.recieve doesn't block
+  end
+end
+
+-- ------------------------------------------------------------------------- --
+
+local function table_to_string(table)
+  local result = ''
+
+  if (type(table) == 'table') then
+    result = '{ '
+
+    for index = 1, #table do
+      result = result .. table_to_string(table[index])
+      if (index ~= #table) then
+        result = result .. ', '
+      end
+    end
+
+    result = result .. ' }'
+  else
+    result = tostring(table)
+  end
+
+  return(result)
+end
+
+-- ------------------------------------------------------------------------- --
+-- Define Utility "module"
+-- ~~~~~~~~~~~~~~~~~~~~~~~
+
+local Utility = {}
+
+Utility.isPsp = isPsp
+Utility.set_debug = set_debug
+Utility.debug = debug
+Utility.dump_string = dump_string
+Utility.get_time = get_time
+Utility.expired = expired
+Utility.shift_left = shift_left
+Utility.shift_right = shift_right
+Utility.socket_ready = socket_ready
+Utility.socket_receive = socket_receive
+Utility.socket_wait_connected = socket_wait_connected
+Utility.table_to_string = table_to_string
+
+-- For ... Utility = require("utility")
+
+return(Utility)
+
+-- ------------------------------------------------------------------------- --
diff --git a/readme.markdown b/readme.markdown
new file mode 100644
index 0000000..d8de103
--- /dev/null
+++ b/readme.markdown
@@ -0,0 +1,346 @@
+Lua MQTT client library (version 0.2 2012-06-01)
+=======================
+
+This project is part of the
+[Eclipse Paho project](http://eclipse.org/paho)
+
+Contents
+--------
+- [Introduction](#introduction)
+- [Protocol implementation and restrictions](#restrictions)
+- [Download](#download)
+- [Feedback and issues](#feedback)
+- [Installation](#installation)
+- [Usage](#usage)
+- [Example code](#example)
+- [Library API](#api)
+- [Known problems](#problems)
+
+<a name="introduction" />
+Introduction
+------------
+This project provides a client-side (only) implementation of the
+[MQTT protocol](http://mqtt.org),
+plus command-line utilities for publishing and subscribing to MQTT topics.
+Typically, one or more MQTT servers, such as
+[mosquitto](http://mosquitto.org) or
+[rsmb](http://www.alphaworks.ibm.com/tech/rsmb)
+will be running on host systems, with which the Lua MQTT client can interact.
+
+MQTT stands for "Message Queue Telemetry Transport", a protocol authored by
+[Dr. Andy Stanford-Clark](http://wikipedia.org/wiki/Andy_Stanford-Clark)
+and Arlen Nipper.
+The protocol is a message-based, publish/subscribe transport layer,
+which is optimized for simple telemetry applications running on small
+micro-controllers, such as an [Arduino](http://arduino.cc),
+over low-bandwidth connections.
+[MQTT libraries exist](http://mqtt.org/software)
+for most popular programming languages, so you can utilize MQTT
+on whatever server or device that you require.
+
+The Lua MQTT client library implements the client-side subset of the
+[MQTT protocol specification 3.1](https://www.ibm.com/developerworks/webservices/library/ws-mqtt).
+
+![Lua MQTT overview](https://github.com/geekscape/mqtt_lua/raw/master/images/lua_mqtt_overview.jpg)
+
+A good use-case for this library is running on constrained systems, such as
+[OpenWRT](http://openwrt.org),
+and acting as a gateway between non-MQTT clients and MQTT servers.
+An advantage of using Lua is that only a text editor is required for rapid
+development of simple MQTT client applications on platforms such as OpenWRT.
+In constrast, working with the C programming language would comparatively
+require more effort, due to requiring a cross-platform development environment.
+
+The Lua MQTT client library also runs (unmodified) on a Sony PlayStation
+Portable using the
+[Lua Player HM](http://en.wikipedia.org/wiki/Lua_Player_HM)
+_(which requires your PSP to be able to run unsigned executables)._
+
+![PlayStation Portable](https://github.com/geekscape/mqtt_lua/raw/master/images/playstation_portable.jpg)
+
+<a name="restrictions" />
+Protocol implementation and restrictions
+----------------------------------------
+- Always assumes MQTT connection "clean session" enabled.
+- Supports connection last will and testament message.
+- Does not support connection username and password.
+- Fixed message header byte 1, only implements the "message type".
+- Only supports QOS (Quality Of Service) level 0.
+- Maximum payload length is 127 bytes (easily increased).
+- Publish message doesn't support "message identifier".
+- Subscribe acknowledgement messages don't check granted QOS level.
+- Outstanding subscribe acknowledgement messages aren't escalated.
+- Works on the Sony PlayStation Portable, using
+  [Lua Player HM](http://en.wikipedia.org/wiki/Lua_Player_HM).
+
+<a name="download" />
+Download
+--------
+The Lua MQTT client library is cross-platform and should work on any
+platform that supports the Lua programming language and network sockets.
+
+- [Download Lua MQTT client library](https://github.com/geekscape/mqtt_lua/archives/master)
+
+<a name="feedback" />
+Feedback and issues
+-------------------
+Tracking is managed via GitHub ...
+
+- [Enhancements requests and issue tracking](https://github.com/geekscape/mqtt_lua/issues)
+
+<a name="installation" />
+Installation
+------------
+You may choose to install an MQTT server either on the same or a different
+system from the Lua MQTT client library, depending upon your deployment
+scenario.
+
+Prerequisites ...
+
+- Install [Mosquitto MQTT server](http://mosquitto.org/download)
+or any other MQTT server
+- Install [Lua programming language](http://www.lua.org/download.html)
+- Install [LuaRocks package manager](http://luarocks.org/en/Download)
+- Install [LuaSocket](http://w3.impa.br/~diego/software/luasocket)
+- Install [PenLight](https://github.com/stevedonovan/Penlight)
+
+On Linux, Lua and LuaRocks can be installed via your Linux distribution
+package manager.
+On Mac OS X, Lua and LuaRocks can be installed viarDarwin ports.
+After that, LuaSocket and PenLight can be installed via LuaRocks.
+
+Lua MQTT client library (source code) from GitHub ...
+
+* TODO
+
+<a name="usage" />
+Usage
+-----
+The Lua MQTT client library comes with three command line utilites,
+which are useful for testing the library and acting as example code.
+These utilities require that Lua Penlight has been installed.
+
+#### mqtt\_test: Test publish and receive messages on different topics
+
+This command periodically publishes a message on topic "test/1" and
+subscribes to the topic "test/2".  The command exits when the message
+"quit" is published on topic "test/2".
+
+      cd $(LUA_MQTT_LIB)              // where Lua MQTT library is installed
+      example/mqtt_test -d localhost  // Assume MQTT server is on "localhost"
+
+      -d,--debug                       Verbose console logging
+      -i,--id     (default MQTT test)  MQTT client identifier
+      -p,--port   (default 1883)       MQTT server port number
+      <host>      (default localhost)  MQTT server hostname
+
+#### mqtt\_publish: Publish a single message to a specified topic
+
+This command publishes a single message and then exits.
+
+      example/mqtt_publish -d -t test/1 -m "Test message"
+
+Only the _--topic_ and _--message_ parameters are required.
+
+      -d,--debug                               Verbose console logging
+      -H,--host         (default localhost)    MQTT server hostname
+      -i,--id           (default MQTT client)  MQTT client identifier
+      -m,--message      (string)               Message to be published
+      -p,--port         (default 1883)         MQTT server port number
+      -t,--topic        (string)               Topic on which to publish
+      -w,--will_message                        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                          Last will and testament topic
+
+#### mqtt\_subscribe: Subscribe to a topic
+
+This command subscribes to a topic and listens indefinitely for messages.
+Use ^C (or similar) to stop execution.
+
+      example/mqtt_subscribe -d -t test/1
+
+Only the _--topic_ parameter is required.
+
+      -d,--debug                               Verbose console logging
+      -H,--host         (default localhost)    MQTT server hostname
+      -i,--id           (default MQTT client)  MQTT client identifier
+      -k,--keepalive    (default 60)           Send MQTT PING period (seconds)
+      -p,--port         (default 1883)         MQTT server port number
+      -t,--topic        (string)               Subscription topic
+      -w,--will_message                        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                          Last will and testament topic
+
+<a name="example" />
+Example code
+------------
+The complete functioning code can be viewed here ...
+[mqtt_lua/lua/example/mqtt\_test.lua](https://github.com/geekscape/mqtt_lua/blob/master/lua/example/mqtt_test.lua)
+
+    -- Define a function which is called by mqtt_client:handler(),
+    -- whenever messages are received on the subscribed topics
+
+      function callback(topic, message)
+        print("Received: " .. topic .. ": " .. message)
+        if (message == "quit") then running = false end
+      end
+
+    -- Create an MQTT client instance, connect to the MQTT server and
+    -- subscribe to the topic called "test/2"
+
+      MQTT = require("mqtt_library")
+      MQTT.Utility.set_debug(true)
+      mqtt_client = MQTT.client.create("localhost", nil, callback)
+      mqtt_client:connect("lua mqtt client"))
+      mqtt_client:subscribe({"test/2"})
+
+    -- Continously invoke mqtt_client:handler() to process the MQTT protocol and
+    -- handle any received messages.  Also, publish a message on topic "test/1"
+
+      running = true
+
+      while (running) do
+        mqtt_client:handler()
+        mqtt_client:publish("test/1", "test message")
+        socket.sleep(1.0)  -- seconds
+      end
+
+    -- Clean-up by unsubscribing from the topic and closing the MQTT connection
+
+      mqtt_client:unsubscribe({"test/2"})
+      mqtt_client:destroy()
+
+There are also a number of Lua MQTT client examples in the _example/_ directory.
+They can be run from the _lua/_ parent directory, as follow ...
+
+      cd mqtt_client/lua
+      example/example_00.lua
+
+<a name="api" />
+MQTT client Library API
+-----------------------
+Once the MQTT client library has been included (via _require_), one or more
+MQTT server connections can be created.  Using a server connection, the client
+may then publish messages directly on a specified topic.  Or, subscribe to one
+or more topics, where received messages are passed to a callback function
+(defined when creating an MQTT client instance).  Finally, the client can
+unsubscribe from one or more topics and disconnect from the MQTT server.
+
+Use the Lua _require_ statement to load the MQTT client library ...
+
+      MQTT = require("mqtt_library")
+
+#### MQTT.Utility.set_debug(): Library debug console logging
+
+The following statement enables debug console logging for diagnosis.
+
+      MQTT.Utility.set_debug(true)
+
+#### MQTT.client.create(): Create an MQTT client instance
+
+Create an MQTT client that will be connected to the specified host.
+
+      mqtt_client = MQTT.client.create(hostname, port, callback)
+
+The _hostname_ must be provided, but both the _port_ and _callback function_
+parameters are optional.  This function returns an MQTT client instance
+that must be used for all subsequent MQTT operations for that server connection.
+
+      hostname string:   Host name or address of the MQTT broker
+      port     integer:  Port number of the MQTT broker (default: 1883)
+      callback function: Invoked when subscribed topic messages received
+
+The _callback function_ is defined as follows ...
+
+      function callback(topic, payload)
+      -- application specific code
+      end
+
+      topic   -- string: Topic for the received message
+      payload -- string: Message data
+
+#### MQTT.client:destroy(): Destroy an MQTT client instance
+
+When finished with a server connection, this statement cleans-up all resources
+allocated by the client.
+
+      mqtt_client:destroy()
+
+#### MQTT.client:connect(): Make a connection to an MQTT server
+
+Before messages can be transmitted, the MQTT client must connect to the server.
+
+      mqtt_client:connect(identifier)
+
+Each individual client connection must use a unique identifier.
+Only the _identifier_ parameter is required, the remaining parameters
+are optional.
+
+      mqtt_client:connect(identifier, will_topic, will_qos, will_retain, will_message)
+
+MQTT also provides a "last will and testament" for clients, which is a message
+automatically sent by the server on behalf of the client, should the connection
+fail.
+
+      identifier   -- string: MQTT client identifier (maximum 23 characters)
+      will_topic   -- string: Last will and testament topic
+      will_qos     -- byte:   Last will and testament Quality Of Service
+      will_retain  -- byte:   Last will and testament retention status
+      will_message -- string: Last will and testament message
+
+#### MQTT.client:disconnect(): Transmit MQTT Disconnect message
+
+Transmit an MQTT disconnect message to the server.
+
+      mqtt_client:disconnect()
+
+#### MQTT.client:publish(): Transmit MQTT publish message
+
+Transmit a message on a specified topic.
+
+      mqtt_client:publish(topic, payload)
+
+      topic   -- string: Topic for the published message
+      payload -- string: Message data
+
+#### MQTT.client:subscribe(): Transmit MQTT Subscribe message
+
+Subscribe to one or more topics.  Whenever a message is published to one of
+those topics, the callback function (defined above) will be invoked.
+
+      mqtt_client:subscribe(topics)
+
+      topics -- table of strings, e.g. { "topic1", "topic2" }
+
+#### MQTT.client:handler(): Handle received messages, maintain keep-alive messages
+
+The _handler()_ function must be called periodically to service incoming
+messages and to ensure that keep-alive messages (PING) are being sent
+when required.
+
+The default _KEEP\_ALIVE\_TIME_ is 60 seconds, therefore _handler()_ must be
+invoked more often than once per minute.
+
+Should any messages be received on the subscribed topics, then _handler()_
+will invoke the callback function (defined above).
+
+      mqtt_client:handler()
+
+#### MQTT.client:unsubscribe(): Transmit MQTT Unsubscribe message
+
+Unsubscribe from one or more topics, so that messages published to those
+topics are no longer received.
+
+      topics -- table of strings, e.g. { "topic1", "topic2" }
+
+<a name="problems" />
+Known problems
+--------------
+- Occasional "MQTT.client:handler(): Message length mismatch" errors,
+  particularly when subscribed topics are transmitting many messages.
+
+- Not really a problem, but if you find that the MQTT socket connection is
+  being closed for no apparent reason, particularly for subscribers ...
+  then check that all MQTT clients are using a unique client identifier.