Initial commit
This commit is contained in:
commit
43a8ebf800
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.zip
|
||||
.vscode
|
3
controller-client/main.lua
Normal file
3
controller-client/main.lua
Normal file
@ -0,0 +1,3 @@
|
||||
function love.draw2()
|
||||
love.graphics.setBackgroundColor(0, 1, 0)
|
||||
end
|
5
controller-host/conf.lua
Normal file
5
controller-host/conf.lua
Normal file
@ -0,0 +1,5 @@
|
||||
function love.conf(t)
|
||||
t.version = "11.4"
|
||||
t.window.title = "Spider Controller"
|
||||
t.window.resizable = true
|
||||
end
|
130
controller-host/main.lua
Normal file
130
controller-host/main.lua
Normal file
@ -0,0 +1,130 @@
|
||||
love.mqtt = {}
|
||||
|
||||
local mqttEventChannel
|
||||
local mqttCommandChannel
|
||||
local errorMessage = nil
|
||||
|
||||
local oldPrint = print
|
||||
|
||||
function print(...)
|
||||
local string = string.format(...)
|
||||
love.mqtt.send("controller/stdout", string)
|
||||
oldPrint(...)
|
||||
end
|
||||
|
||||
function printTable(table, indentation)
|
||||
indentation = indentation or ""
|
||||
for name, value in pairs(table) do
|
||||
print(indentation .. tostring(name) .. ": " .. tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
function love.draw(...)
|
||||
if love.draw2 then
|
||||
love.draw2(...)
|
||||
else
|
||||
local text = "Awaiting payload..."
|
||||
local font = love.graphics.getFont()
|
||||
|
||||
if errorMessage then
|
||||
text = errorMessage
|
||||
end
|
||||
|
||||
-- Calculate the center of the screen
|
||||
local centerX = love.graphics.getWidth() / 2
|
||||
local centerY = love.graphics.getHeight() / 2
|
||||
|
||||
-- Calculate textX and textY
|
||||
local textX = math.floor(centerX - (font:getWidth(text) / 2))
|
||||
local textY = math.floor(centerY - (font:getHeight(text) / 2))
|
||||
|
||||
local realText
|
||||
if errorMessage then
|
||||
realText = errorMessage
|
||||
else
|
||||
realText = "Awaiting payload" .. ("."):rep(math.floor(love.timer.getTime() * 4 % 4))
|
||||
end
|
||||
|
||||
-- Render text
|
||||
if errorMessage then
|
||||
love.graphics.setBackgroundColor(0.2, 0, 0, 0)
|
||||
else
|
||||
love.graphics.setBackgroundColor(0, 0, 0.2, 0)
|
||||
end
|
||||
love.graphics.print(realText, textX, textY)
|
||||
end
|
||||
end
|
||||
|
||||
function love.update(...)
|
||||
local message = mqttEventChannel:pop()
|
||||
if message then
|
||||
love.mqtt[message.target](unpack(message.args))
|
||||
end
|
||||
|
||||
if love.update2 then
|
||||
love.update2(...)
|
||||
end
|
||||
end
|
||||
|
||||
function love.mqtt.connect(connack)
|
||||
if connack.rc ~= 0 then
|
||||
print("Connection to broker failed:", connack:reason_string())
|
||||
end
|
||||
|
||||
love.mqtt.subscribe("controller/payload")
|
||||
print("Connected to MQTT")
|
||||
printTable(connack)
|
||||
end
|
||||
|
||||
function love.mqtt.message2(topic, payload)
|
||||
if topic == "controller/payload" then
|
||||
local success = love.filesystem.unmount("client.zip")
|
||||
if not success then
|
||||
print("Could not unmount client.zip")
|
||||
end
|
||||
local archive = love.filesystem.newFileData(payload, "client.zip")
|
||||
local success = love.filesystem.mount(archive, "client", true)
|
||||
if not success then
|
||||
print("Failed to mount archive")
|
||||
return
|
||||
end
|
||||
print("Archive mounted")
|
||||
local chunk, errormsg = love.filesystem.load("client/main.lua", "bt")
|
||||
if errormsg then
|
||||
print(errormsg)
|
||||
errorMessage = errormsg
|
||||
return
|
||||
end
|
||||
love.load = nil
|
||||
chunk()
|
||||
if love.load then
|
||||
love.load()
|
||||
end
|
||||
elseif love.mqtt.message then
|
||||
love.mqtt.message(topic, payload)
|
||||
end
|
||||
end
|
||||
|
||||
function love.mqtt.send(topic, arg)
|
||||
mqttCommandChannel:push {
|
||||
command = "send",
|
||||
topic = topic,
|
||||
arg = arg
|
||||
}
|
||||
end
|
||||
|
||||
function love.mqtt.subscribe(topic)
|
||||
mqttCommandChannel:push {
|
||||
command = "subscribe",
|
||||
topic = topic,
|
||||
}
|
||||
end
|
||||
|
||||
function love.load()
|
||||
local requirePaths = love.filesystem.getRequirePath()
|
||||
love.filesystem.setRequirePath(requirePaths .. ";client/?.lua;client/?/init.lua")
|
||||
local mqttThread = love.thread.newThread("mqttthread.lua")
|
||||
mqttThread:start()
|
||||
mqttEventChannel = love.thread.getChannel("mqtt_event")
|
||||
mqttCommandChannel = love.thread.getChannel("mqtt_command")
|
||||
end
|
18
controller-host/mqtt/bit53.lua
Normal file
18
controller-host/mqtt/bit53.lua
Normal file
@ -0,0 +1,18 @@
|
||||
-- implementing some functions from BitOp (http://bitop.luajit.org/) on Lua 5.3
|
||||
|
||||
return {
|
||||
lshift = function(x, n)
|
||||
return x << n
|
||||
end,
|
||||
rshift = function(x, n)
|
||||
return x >> n
|
||||
end,
|
||||
bor = function(x1, x2)
|
||||
return x1 | x2
|
||||
end,
|
||||
band = function(x1, x2)
|
||||
return x1 & x2
|
||||
end,
|
||||
}
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
11
controller-host/mqtt/bitwrap.lua
Normal file
11
controller-host/mqtt/bitwrap.lua
Normal file
@ -0,0 +1,11 @@
|
||||
-- wrapper around BitOp module
|
||||
|
||||
if _VERSION == "Lua 5.1" or type(jit) == "table" then -- Lua 5.1 or LuaJIT (based on Lua 5.1)
|
||||
return require("bit") -- custom module https://luarocks.org/modules/luarocks/luabitop
|
||||
elseif _VERSION == "Lua 5.2" then
|
||||
return require("bit32") -- standard Lua 5.2 module
|
||||
else
|
||||
return require("mqtt.bit53")
|
||||
end
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
1208
controller-host/mqtt/client.lua
Normal file
1208
controller-host/mqtt/client.lua
Normal file
File diff suppressed because it is too large
Load Diff
87
controller-host/mqtt/init.lua
Normal file
87
controller-host/mqtt/init.lua
Normal file
@ -0,0 +1,87 @@
|
||||
--- MQTT module
|
||||
-- @module mqtt
|
||||
|
||||
--[[
|
||||
MQTT protocol DOC: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html
|
||||
|
||||
CONVENTIONS:
|
||||
|
||||
* errors:
|
||||
* passing invalid arguments (like number instead of string) to function in this library will raise exception
|
||||
* all other errors will be returned in format: false, "error-text"
|
||||
* you can wrap function call into standard lua assert() to raise exception
|
||||
|
||||
]]
|
||||
|
||||
--- Module table
|
||||
-- @field v311 MQTT v3.1.1 protocol version constant
|
||||
-- @field v50 MQTT v5.0 protocol version constant
|
||||
-- @field _VERSION luamqtt version string
|
||||
-- @table mqtt
|
||||
local mqtt = {
|
||||
-- supported MQTT protocol versions
|
||||
v311 = 4, -- supported protocol version, MQTT v3.1.1
|
||||
v50 = 5, -- supported protocol version, MQTT v5.0
|
||||
|
||||
-- mqtt library version
|
||||
_VERSION = "3.4.3",
|
||||
}
|
||||
|
||||
-- load required stuff
|
||||
local type = type
|
||||
local select = select
|
||||
local require = require
|
||||
|
||||
local client = require("mqtt.client")
|
||||
local client_create = client.create
|
||||
|
||||
local ioloop_get = require("mqtt.ioloop").get
|
||||
|
||||
--- Create new MQTT client instance
|
||||
-- @param ... Same as for mqtt.client.create(...)
|
||||
-- @see mqtt.client.client_mt:__init
|
||||
function mqtt.client(...)
|
||||
return client_create(...)
|
||||
end
|
||||
|
||||
--- Returns default ioloop instance
|
||||
-- @function mqtt.get_ioloop
|
||||
mqtt.get_ioloop = ioloop_get
|
||||
|
||||
--- Run default ioloop for given MQTT clients or functions
|
||||
-- @param ... MQTT clients or lopp functions to add to ioloop
|
||||
-- @see mqtt.ioloop.get
|
||||
-- @see mqtt.ioloop.run_until_clients
|
||||
function mqtt.run_ioloop(...)
|
||||
local loop = ioloop_get()
|
||||
for i = 1, select("#", ...) do
|
||||
local cl = select(i, ...)
|
||||
loop:add(cl)
|
||||
if type(cl) ~= "function" then
|
||||
cl:start_connecting()
|
||||
end
|
||||
end
|
||||
return loop:run_until_clients()
|
||||
end
|
||||
|
||||
--- Run synchronous input/output loop for only one given MQTT client.
|
||||
-- Provided client's connection will be opened.
|
||||
-- Client reconnect feature will not work, and keep_alive too.
|
||||
-- @param cl MQTT client instance to run
|
||||
function mqtt.run_sync(cl)
|
||||
local ok, err = cl:start_connecting()
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
while cl.connection do
|
||||
ok, err = cl:_sync_iteration()
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return mqtt
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
180
controller-host/mqtt/ioloop.lua
Normal file
180
controller-host/mqtt/ioloop.lua
Normal file
@ -0,0 +1,180 @@
|
||||
--- ioloop module
|
||||
-- @module mqtt.ioloop
|
||||
-- @alias ioloop
|
||||
|
||||
--[[
|
||||
ioloop module
|
||||
|
||||
In short: allowing you to work with several MQTT clients in one script, and allowing them to maintain
|
||||
a long-term connection to broker, using PINGs.
|
||||
|
||||
NOTE: this module will work only with MQTT clients using standard luasocket/luasocket_ssl connectors.
|
||||
|
||||
In long:
|
||||
Providing an IO loop instance dealing with efficient (as much as possible in limited lua IO) network communication
|
||||
for several MQTT clients in the same OS thread.
|
||||
The main idea is that you are creating an ioloop instance, then adding created and connected MQTT clients to it.
|
||||
The ioloop instance is setting a non-blocking mode for sockets in MQTT clients and setting a small timeout
|
||||
for their receive/send operations. Then ioloop is starting an endless loop trying to receive/send data for all added MQTT clients.
|
||||
You may add more or remove some MQTT clients from the ioloop after it's created and started.
|
||||
|
||||
Using that ioloop is allowing you to run a MQTT client for long time, through sending PINGREQ packets to broker
|
||||
in keepAlive interval to maintain long-term connection.
|
||||
|
||||
Also, any function can be added to the ioloop instance, and it will be called in the same endless loop over and over
|
||||
alongside with added MQTT clients to provide you a piece of processor time to run your own logic (like running your own
|
||||
network communications or any other thing good working in an io-loop)
|
||||
]]
|
||||
|
||||
-- module table
|
||||
local ioloop = {}
|
||||
|
||||
-- load required stuff
|
||||
local next = next
|
||||
local type = type
|
||||
local ipairs = ipairs
|
||||
local require = require
|
||||
local setmetatable = setmetatable
|
||||
|
||||
local table = require("table")
|
||||
local tbl_remove = table.remove
|
||||
|
||||
--- ioloop instances metatable
|
||||
-- @type ioloop_mt
|
||||
local ioloop_mt = {}
|
||||
ioloop_mt.__index = ioloop_mt
|
||||
|
||||
--- Initialize ioloop instance
|
||||
-- @tparam table args ioloop creation arguments table
|
||||
-- @tparam[opt=0.005] number args.timeout network operations timeout in seconds
|
||||
-- @tparam[opt=0] number args.sleep sleep interval after each iteration
|
||||
-- @tparam[opt] function args.sleep_function custom sleep function to call after each iteration
|
||||
-- @treturn ioloop_mt ioloop instance
|
||||
function ioloop_mt:__init(args)
|
||||
args = args or {}
|
||||
args.timeout = args.timeout or 0.005
|
||||
args.sleep = args.sleep or 0
|
||||
args.sleep_function = args.sleep_function or require("socket").sleep
|
||||
self.args = args
|
||||
self.clients = {}
|
||||
self.running = false --ioloop running flag, used by MQTT clients which are adding after this ioloop started to run
|
||||
end
|
||||
|
||||
--- Add MQTT client or a loop function to the ioloop instance
|
||||
-- @tparam client_mt|function client MQTT client or a loop function to add to ioloop
|
||||
-- @return true on success or false and error message on failure
|
||||
function ioloop_mt:add(client)
|
||||
local clients = self.clients
|
||||
if clients[client] then
|
||||
return false, "such MQTT client or loop function is already added to this ioloop"
|
||||
end
|
||||
clients[#clients + 1] = client
|
||||
clients[client] = true
|
||||
|
||||
-- associate ioloop with adding MQTT client
|
||||
if type(client) ~= "function" then
|
||||
client:set_ioloop(self)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Remove MQTT client or a loop function from the ioloop instance
|
||||
-- @tparam client_mt|function client MQTT client or a loop function to remove from ioloop
|
||||
-- @return true on success or false and error message on failure
|
||||
function ioloop_mt:remove(client)
|
||||
local clients = self.clients
|
||||
if not clients[client] then
|
||||
return false, "no such MQTT client or loop function was added to ioloop"
|
||||
end
|
||||
clients[client] = nil
|
||||
|
||||
-- search an index of client to remove
|
||||
for i, item in ipairs(clients) do
|
||||
if item == client then
|
||||
tbl_remove(clients, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- unlink ioloop from MQTT client
|
||||
if type(client) ~= "function" then
|
||||
client:set_ioloop(nil)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Perform one ioloop iteration
|
||||
function ioloop_mt:iteration()
|
||||
self.timeouted = false
|
||||
for _, client in ipairs(self.clients) do
|
||||
if type(client) ~= "function" then
|
||||
client:_ioloop_iteration()
|
||||
else
|
||||
client()
|
||||
end
|
||||
end
|
||||
-- sleep a bit
|
||||
local args = self.args
|
||||
local sleep = args.sleep
|
||||
if sleep > 0 then
|
||||
args.sleep_function(sleep)
|
||||
end
|
||||
end
|
||||
|
||||
--- Perform sleep if no one of the network operation in current iteration was not timeouted
|
||||
function ioloop_mt:can_sleep()
|
||||
if not self.timeouted then
|
||||
local args = self.args
|
||||
args.sleep_function(args.timeout)
|
||||
self.timeouted = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Run ioloop until at least one client are in ioloop
|
||||
function ioloop_mt:run_until_clients()
|
||||
self.running = true
|
||||
while next(self.clients) do
|
||||
self:iteration()
|
||||
end
|
||||
self.running = false
|
||||
end
|
||||
|
||||
-------
|
||||
|
||||
--- Create IO loop instance with given options
|
||||
-- @see ioloop_mt:__init
|
||||
-- @treturn ioloop_mt ioloop instance
|
||||
local function ioloop_create(args)
|
||||
local inst = setmetatable({}, ioloop_mt)
|
||||
inst:__init(args)
|
||||
return inst
|
||||
end
|
||||
ioloop.create = ioloop_create
|
||||
|
||||
-- Default ioloop instance
|
||||
local ioloop_instance
|
||||
|
||||
--- Returns default ioloop instance
|
||||
-- @tparam[opt=true] boolean autocreate Automatically create ioloop instance
|
||||
-- @tparam[opt] table args Arguments for creating ioloop instance
|
||||
-- @treturn ioloop_mt ioloop instance
|
||||
function ioloop.get(autocreate, args)
|
||||
if autocreate == nil then
|
||||
autocreate = true
|
||||
end
|
||||
if autocreate then
|
||||
if not ioloop_instance then
|
||||
ioloop_instance = ioloop_create(args)
|
||||
end
|
||||
end
|
||||
return ioloop_instance
|
||||
end
|
||||
|
||||
-------
|
||||
|
||||
-- export module table
|
||||
return ioloop
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
48
controller-host/mqtt/luasocket-copas.lua
Normal file
48
controller-host/mqtt/luasocket-copas.lua
Normal file
@ -0,0 +1,48 @@
|
||||
-- DOC: https://keplerproject.github.io/copas/
|
||||
-- NOTE: you will need to install copas like this: luarocks install copas
|
||||
|
||||
-- module table
|
||||
local connector = {}
|
||||
|
||||
local socket = require("socket")
|
||||
local copas = require("copas")
|
||||
|
||||
-- Open network connection to .host and .port in conn table
|
||||
-- Store opened socket to conn table
|
||||
-- Returns true on success, or false and error text on failure
|
||||
function connector.connect(conn)
|
||||
local sock, err = socket.connect(conn.host, conn.port)
|
||||
if not sock then
|
||||
return false, "socket.connect failed: "..err
|
||||
end
|
||||
conn.sock = sock
|
||||
return true
|
||||
end
|
||||
|
||||
-- Shutdown network connection
|
||||
function connector.shutdown(conn)
|
||||
conn.sock:shutdown()
|
||||
end
|
||||
|
||||
-- Send data to network connection
|
||||
function connector.send(conn, data, i, j)
|
||||
local ok, err = copas.send(conn.sock, data, i, j)
|
||||
return ok, err
|
||||
end
|
||||
|
||||
-- Receive given amount of data from network connection
|
||||
function connector.receive(conn, size)
|
||||
local ok, err = copas.receive(conn.sock, size)
|
||||
return ok, err
|
||||
end
|
||||
|
||||
-- Set connection's socket to non-blocking mode and set a timeout for it
|
||||
function connector.settimeout(conn, timeout)
|
||||
conn.timeout = timeout
|
||||
conn.sock:settimeout(0)
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return connector
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
52
controller-host/mqtt/luasocket.lua
Normal file
52
controller-host/mqtt/luasocket.lua
Normal file
@ -0,0 +1,52 @@
|
||||
-- DOC: http://w3.impa.br/~diego/software/luasocket/tcp.html
|
||||
|
||||
-- module table
|
||||
local luasocket = {}
|
||||
|
||||
local socket = require("socket")
|
||||
|
||||
-- Open network connection to .host and .port in conn table
|
||||
-- Store opened socket to conn table
|
||||
-- Returns true on success, or false and error text on failure
|
||||
function luasocket.connect(conn)
|
||||
local sock, err = socket.connect(conn.host, conn.port)
|
||||
if not sock then
|
||||
return false, "socket.connect failed: "..err
|
||||
end
|
||||
conn.sock = sock
|
||||
return true
|
||||
end
|
||||
|
||||
-- Shutdown network connection
|
||||
function luasocket.shutdown(conn)
|
||||
conn.sock:shutdown()
|
||||
end
|
||||
|
||||
-- Send data to network connection
|
||||
function luasocket.send(conn, data, i, j)
|
||||
local ok, err = conn.sock:send(data, i, j)
|
||||
-- print(" luasocket.send:", ok, err, require("mqtt.tools").hex(data))
|
||||
return ok, err
|
||||
end
|
||||
|
||||
-- Receive given amount of data from network connection
|
||||
function luasocket.receive(conn, size)
|
||||
local ok, err = conn.sock:receive(size)
|
||||
-- if ok then
|
||||
-- print(" luasocket.receive:", size, require("mqtt.tools").hex(ok))
|
||||
-- elseif err ~= "timeout" then
|
||||
-- print(" luasocket.receive:", ok, err)
|
||||
-- end
|
||||
return ok, err
|
||||
end
|
||||
|
||||
-- Set connection's socket to non-blocking mode and set a timeout for it
|
||||
function luasocket.settimeout(conn, timeout)
|
||||
conn.timeout = timeout
|
||||
conn.sock:settimeout(timeout, "t")
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return luasocket
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
56
controller-host/mqtt/luasocket_ssl.lua
Normal file
56
controller-host/mqtt/luasocket_ssl.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- DOC: http://w3.impa.br/~diego/software/luasocket/tcp.html
|
||||
|
||||
-- module table
|
||||
local luasocket_ssl = {}
|
||||
|
||||
local type = type
|
||||
local assert = assert
|
||||
local luasocket = require("mqtt.luasocket")
|
||||
|
||||
-- Open network connection to .host and .port in conn table
|
||||
-- Store opened socket to conn table
|
||||
-- Returns true on success, or false and error text on failure
|
||||
function luasocket_ssl.connect(conn)
|
||||
assert(type(conn.secure_params) == "table", "expecting .secure_params to be a table")
|
||||
|
||||
-- open usual TCP connection
|
||||
local ok, err = luasocket.connect(conn)
|
||||
if not ok then
|
||||
return false, "luasocket connect failed: "..err
|
||||
end
|
||||
local wrapped
|
||||
|
||||
-- load right ssl module
|
||||
local ssl = require(conn.ssl_module or "ssl")
|
||||
|
||||
-- TLS/SSL initialization
|
||||
wrapped, err = ssl.wrap(conn.sock, conn.secure_params)
|
||||
if not wrapped then
|
||||
conn.sock:shutdown()
|
||||
return false, "ssl.wrap() failed: "..err
|
||||
end
|
||||
ok = wrapped:dohandshake()
|
||||
if not ok then
|
||||
conn.sock:shutdown()
|
||||
return false, "ssl dohandshake failed"
|
||||
end
|
||||
|
||||
-- replace sock in connection table with wrapped secure socket
|
||||
conn.sock = wrapped
|
||||
return true
|
||||
end
|
||||
|
||||
-- Shutdown network connection
|
||||
function luasocket_ssl.shutdown(conn)
|
||||
conn.sock:close()
|
||||
end
|
||||
|
||||
-- Copy original methods from mqtt.luasocket module
|
||||
luasocket_ssl.send = luasocket.send
|
||||
luasocket_ssl.receive = luasocket.receive
|
||||
luasocket_ssl.settimeout = luasocket.settimeout
|
||||
|
||||
-- export module table
|
||||
return luasocket_ssl
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
55
controller-host/mqtt/ngxsocket.lua
Normal file
55
controller-host/mqtt/ngxsocket.lua
Normal file
@ -0,0 +1,55 @@
|
||||
-- module table
|
||||
-- thanks to @irimiab: https://github.com/xHasKx/luamqtt/issues/13
|
||||
local ngxsocket = {}
|
||||
|
||||
-- load required stuff
|
||||
local string_sub = string.sub
|
||||
local ngx_socket_tcp = ngx.socket.tcp -- luacheck: ignore
|
||||
|
||||
-- Open network connection to .host and .port in conn table
|
||||
-- Store opened socket to conn table
|
||||
-- Returns true on success, or false and error text on failure
|
||||
function ngxsocket.connect(conn)
|
||||
local socket = ngx_socket_tcp()
|
||||
socket:settimeout(0x7FFFFFFF)
|
||||
local sock, err = socket:connect(conn.host, conn.port)
|
||||
if not sock then
|
||||
return false, "socket:connect failed: "..err
|
||||
end
|
||||
if conn.secure then
|
||||
socket:sslhandshake()
|
||||
end
|
||||
conn.sock = socket
|
||||
return true
|
||||
end
|
||||
|
||||
-- Shutdown network connection
|
||||
function ngxsocket.shutdown(conn)
|
||||
conn.sock:close()
|
||||
end
|
||||
|
||||
-- Send data to network connection
|
||||
function ngxsocket.send(conn, data, i, j)
|
||||
if i then
|
||||
return conn.sock:send(string_sub(data, i, j))
|
||||
else
|
||||
return conn.sock:send(data)
|
||||
end
|
||||
end
|
||||
|
||||
-- Receive given amount of data from network connection
|
||||
function ngxsocket.receive(conn, size)
|
||||
return conn.sock:receive(size)
|
||||
end
|
||||
|
||||
-- Set connection's socket to non-blocking mode and set a timeout for it
|
||||
function ngxsocket.settimeout(conn, timeout)
|
||||
if not timeout then
|
||||
conn.sock:settimeout(0x7FFFFFFF)
|
||||
else
|
||||
conn.sock:settimeout(timeout * 1000)
|
||||
end
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return ngxsocket
|
525
controller-host/mqtt/protocol.lua
Normal file
525
controller-host/mqtt/protocol.lua
Normal file
@ -0,0 +1,525 @@
|
||||
--- MQTT generic protocol components module
|
||||
-- @module mqtt.protocol
|
||||
|
||||
--[[
|
||||
|
||||
Here is a generic implementation of MQTT protocols of all supported versions.
|
||||
|
||||
MQTT v3.1.1 documentation (DOCv3.1.1):
|
||||
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html
|
||||
|
||||
MQTT v5.0 documentation (DOCv5.0):
|
||||
http://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html
|
||||
|
||||
CONVENTIONS:
|
||||
|
||||
* read_func - function to read data from some stream-like object (like network connection).
|
||||
We are calling it with one argument: number of bytes to read.
|
||||
Use currying/closures to pass other arguments to this function.
|
||||
This function should return string of given size on success.
|
||||
On failure it should return false/nil and an error message.
|
||||
|
||||
]]
|
||||
|
||||
-- module table
|
||||
local protocol = {}
|
||||
|
||||
-- load required stuff
|
||||
local type = type
|
||||
local error = error
|
||||
local assert = assert
|
||||
local require = require
|
||||
local _VERSION = _VERSION -- lua interpreter version, not a mqtt._VERSION
|
||||
local tostring = tostring
|
||||
local setmetatable = setmetatable
|
||||
|
||||
|
||||
local table = require("table")
|
||||
local tbl_concat = table.concat
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local string = require("string")
|
||||
local str_sub = string.sub
|
||||
local str_char = string.char
|
||||
local str_byte = string.byte
|
||||
local str_format = string.format
|
||||
|
||||
local bit = require("mqtt.bitwrap")
|
||||
local bor = bit.bor
|
||||
local band = bit.band
|
||||
local lshift = bit.lshift
|
||||
local rshift = bit.rshift
|
||||
|
||||
local tools = require("mqtt.tools")
|
||||
local div = tools.div
|
||||
|
||||
-- Create uint8 value data
|
||||
local function make_uint8(val)
|
||||
if val < 0 or val > 0xFF then
|
||||
error("value is out of range to encode as uint8: "..tostring(val))
|
||||
end
|
||||
return str_char(val)
|
||||
end
|
||||
protocol.make_uint8 = make_uint8
|
||||
|
||||
-- Create uint16 value data
|
||||
local function make_uint16(val)
|
||||
if val < 0 or val > 0xFFFF then
|
||||
error("value is out of range to encode as uint16: "..tostring(val))
|
||||
end
|
||||
return str_char(rshift(val, 8), band(val, 0xFF))
|
||||
end
|
||||
protocol.make_uint16 = make_uint16
|
||||
|
||||
-- Create uint32 value data
|
||||
function protocol.make_uint32(val)
|
||||
if val < 0 or val > 0xFFFFFFFF then
|
||||
error("value is out of range to encode as uint32: "..tostring(val))
|
||||
end
|
||||
return str_char(rshift(val, 24), band(rshift(val, 16), 0xFF), band(rshift(val, 8), 0xFF), band(val, 0xFF))
|
||||
end
|
||||
|
||||
-- Create UTF-8 string data
|
||||
-- DOCv3.1.1: 1.5.3 UTF-8 encoded strings
|
||||
-- DOCv5.0: 1.5.4 UTF-8 Encoded String
|
||||
function protocol.make_string(str)
|
||||
return make_uint16(str:len())..str
|
||||
end
|
||||
|
||||
-- Returns bytes of given integer value encoded as variable length field
|
||||
-- DOCv3.1.1: 2.2.3 Remaining Length
|
||||
-- DOCv5.0: 2.1.4 Remaining Length
|
||||
local function make_var_length(len)
|
||||
if len < 0 or len > 268435455 then
|
||||
error("value is invalid for encoding as variable length field: "..tostring(len))
|
||||
end
|
||||
local bytes = {}
|
||||
local i = 1
|
||||
repeat
|
||||
local byte = len % 128
|
||||
len = div(len, 128)
|
||||
if len > 0 then
|
||||
byte = bor(byte, 128)
|
||||
end
|
||||
bytes[i] = byte
|
||||
i = i + 1
|
||||
until len <= 0
|
||||
return unpack(bytes)
|
||||
end
|
||||
protocol.make_var_length = make_var_length
|
||||
|
||||
-- Make data for 1-byte property with only 0 or 1 value
|
||||
function protocol.make_uint8_0_or_1(value)
|
||||
if value ~= 0 and value ~= 1 then
|
||||
error("expecting 0 or 1 as value")
|
||||
end
|
||||
return make_uint8(value)
|
||||
end
|
||||
|
||||
-- Make data for 2-byte property with nonzero value check
|
||||
function protocol.make_uint16_nonzero(value)
|
||||
if value == 0 then
|
||||
error("expecting nonzero value")
|
||||
end
|
||||
return make_uint16(value)
|
||||
end
|
||||
|
||||
-- Make data for variable length property with nonzero value check
|
||||
function protocol.make_var_length_nonzero(value)
|
||||
if value == 0 then
|
||||
error("expecting nonzero value")
|
||||
end
|
||||
return make_var_length(value)
|
||||
end
|
||||
|
||||
-- Read string using given read_func function
|
||||
-- Returns false plus error message on failure
|
||||
-- Returns parsed string on success
|
||||
function protocol.parse_string(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local len, err = read_func(2)
|
||||
if not len then
|
||||
return false, "failed to read string length: "..err
|
||||
end
|
||||
-- convert len string from 2-byte integer
|
||||
local byte1, byte2 = str_byte(len, 1, 2)
|
||||
len = bor(lshift(byte1, 8), byte2)
|
||||
-- and return string if parsed length
|
||||
return read_func(len)
|
||||
end
|
||||
|
||||
-- Parse uint8 value using given read_func
|
||||
local function parse_uint8(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local value, err = read_func(1)
|
||||
if not value then
|
||||
return false, "failed to read 1 byte for uint8: "..err
|
||||
end
|
||||
return str_byte(value, 1, 1)
|
||||
end
|
||||
protocol.parse_uint8 = parse_uint8
|
||||
|
||||
-- Parse uint8 value with only 0 or 1 value
|
||||
function protocol.parse_uint8_0_or_1(read_func)
|
||||
local value, err = parse_uint8(read_func)
|
||||
if not value then
|
||||
return false, err
|
||||
end
|
||||
if value ~= 0 and value ~= 1 then
|
||||
return false, "expecting only 0 or 1 but got: "..value
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Parse uint16 value using given read_func
|
||||
local function parse_uint16(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local value, err = read_func(2)
|
||||
if not value then
|
||||
return false, "failed to read 2 byte for uint16: "..err
|
||||
end
|
||||
local byte1, byte2 = str_byte(value, 1, 2)
|
||||
return lshift(byte1, 8) + byte2
|
||||
end
|
||||
protocol.parse_uint16 = parse_uint16
|
||||
|
||||
-- Parse uint16 non-zero value using given read_func
|
||||
function protocol.parse_uint16_nonzero(read_func)
|
||||
local value, err = parse_uint16(read_func)
|
||||
if not value then
|
||||
return false, err
|
||||
end
|
||||
if value == 0 then
|
||||
return false, "expecting non-zero value"
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Parse uint32 value using given read_func
|
||||
function protocol.parse_uint32(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local value, err = read_func(4)
|
||||
if not value then
|
||||
return false, "failed to read 4 byte for uint32: "..err
|
||||
end
|
||||
local byte1, byte2, byte3, byte4 = str_byte(value, 1, 4)
|
||||
if _VERSION < "Lua 5.3" then
|
||||
return byte1 * (2 ^ 24) + lshift(byte2, 16) + lshift(byte3, 8) + byte4
|
||||
else
|
||||
return lshift(byte1, 24) + lshift(byte2, 16) + lshift(byte3, 8) + byte4
|
||||
end
|
||||
end
|
||||
|
||||
-- Max variable length integer value
|
||||
local max_mult = 128 * 128 * 128
|
||||
|
||||
-- Returns variable length field value calling read_func function read data, DOC: 2.2.3 Remaining Length
|
||||
local function parse_var_length(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local mult = 1
|
||||
local val = 0
|
||||
repeat
|
||||
local byte, err = read_func(1)
|
||||
if not byte then
|
||||
return false, err
|
||||
end
|
||||
byte = str_byte(byte, 1, 1)
|
||||
val = val + band(byte, 127) * mult
|
||||
if mult > max_mult then
|
||||
return false, "malformed variable length field data"
|
||||
end
|
||||
mult = mult * 128
|
||||
until band(byte, 128) == 0
|
||||
return val
|
||||
end
|
||||
protocol.parse_var_length = parse_var_length
|
||||
|
||||
-- Parse Variable Byte Integer with non-zero constraint
|
||||
function protocol.parse_var_length_nonzero(read_func)
|
||||
local value, err = parse_var_length(read_func)
|
||||
if not value then
|
||||
return false, err
|
||||
end
|
||||
if value == 0 then
|
||||
return false, "expecting non-zero value"
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Create fixed packet header data
|
||||
-- DOCv3.1.1: 2.2 Fixed header
|
||||
-- DOCv5.0: 2.1.1 Fixed Header
|
||||
function protocol.make_header(ptype, flags, len)
|
||||
local byte1 = bor(lshift(ptype, 4), band(flags, 0x0F))
|
||||
return str_char(byte1, make_var_length(len))
|
||||
end
|
||||
|
||||
-- Returns true if given value is a valid QoS
|
||||
function protocol.check_qos(val)
|
||||
return (val == 0) or (val == 1) or (val == 2)
|
||||
end
|
||||
|
||||
-- Returns true if given value is a valid Packet Identifier
|
||||
-- DOCv3.1.1: 2.3.1 Packet Identifier
|
||||
-- DOCv5.0: 2.2.1 Packet Identifier
|
||||
function protocol.check_packet_id(val)
|
||||
return val >= 1 and val <= 0xFFFF
|
||||
end
|
||||
|
||||
-- Returns the next Packet Identifier value relative to given current value
|
||||
-- DOCv3.1.1: 2.3.1 Packet Identifier
|
||||
-- DOCv5.0: 2.2.1 Packet Identifier
|
||||
function protocol.next_packet_id(curr)
|
||||
if not curr then
|
||||
return 1
|
||||
end
|
||||
assert(type(curr) == "number", "expecting curr to be a number")
|
||||
assert(curr >= 1, "expecting curr to be >= 1")
|
||||
curr = curr + 1
|
||||
if curr > 0xFFFF then
|
||||
curr = 1
|
||||
end
|
||||
return curr
|
||||
end
|
||||
|
||||
-- MQTT protocol fixed header packet types
|
||||
-- DOCv3.1.1: 2.2.1 MQTT Control Packet type
|
||||
-- DOCv5.0: 2.1.2 MQTT Control Packet type
|
||||
local packet_type = {
|
||||
CONNECT = 1,
|
||||
CONNACK = 2,
|
||||
PUBLISH = 3,
|
||||
PUBACK = 4,
|
||||
PUBREC = 5,
|
||||
PUBREL = 6,
|
||||
PUBCOMP = 7,
|
||||
SUBSCRIBE = 8,
|
||||
SUBACK = 9,
|
||||
UNSUBSCRIBE = 10,
|
||||
UNSUBACK = 11,
|
||||
PINGREQ = 12,
|
||||
PINGRESP = 13,
|
||||
DISCONNECT = 14,
|
||||
AUTH = 15, -- NOTE: new in MQTTv5.0
|
||||
[1] = "CONNECT",
|
||||
[2] = "CONNACK",
|
||||
[3] = "PUBLISH",
|
||||
[4] = "PUBACK",
|
||||
[5] = "PUBREC",
|
||||
[6] = "PUBREL",
|
||||
[7] = "PUBCOMP",
|
||||
[8] = "SUBSCRIBE",
|
||||
[9] = "SUBACK",
|
||||
[10] = "UNSUBSCRIBE",
|
||||
[11] = "UNSUBACK",
|
||||
[12] = "PINGREQ",
|
||||
[13] = "PINGRESP",
|
||||
[14] = "DISCONNECT",
|
||||
[15] = "AUTH", -- NOTE: new in MQTTv5.0
|
||||
}
|
||||
protocol.packet_type = packet_type
|
||||
|
||||
-- Packet types requiring packet identifier field
|
||||
-- DOCv3.1.1: 2.3.1 Packet Identifier
|
||||
-- DOCv5.0: 2.2.1 Packet Identifier
|
||||
local packets_requiring_packet_id = {
|
||||
[packet_type.PUBACK] = true,
|
||||
[packet_type.PUBREC] = true,
|
||||
[packet_type.PUBREL] = true,
|
||||
[packet_type.PUBCOMP] = true,
|
||||
[packet_type.SUBSCRIBE] = true,
|
||||
[packet_type.SUBACK] = true,
|
||||
[packet_type.UNSUBSCRIBE] = true,
|
||||
[packet_type.UNSUBACK] = true,
|
||||
}
|
||||
|
||||
-- CONNACK return code/reason code strings
|
||||
local connack_rc = {
|
||||
-- MQTT v3.1.1 Connect return codes, DOCv3.1.1: 3.2.2.3 Connect Return code
|
||||
[0] = "Connection Accepted",
|
||||
[1] = "Connection Refused, unacceptable protocol version",
|
||||
[2] = "Connection Refused, identifier rejected",
|
||||
[3] = "Connection Refused, Server unavailable",
|
||||
[4] = "Connection Refused, bad user name or password",
|
||||
[5] = "Connection Refused, not authorized",
|
||||
|
||||
-- MQTT v5.0 Connect reason codes, DOCv5.0: 3.2.2.2 Connect Reason Code
|
||||
[0x80] = "Unspecified error",
|
||||
[0x81] = "Malformed Packet",
|
||||
[0x82] = "Protocol Error",
|
||||
[0x83] = "Implementation specific error",
|
||||
[0x84] = "Unsupported Protocol Version",
|
||||
[0x85] = "Client Identifier not valid",
|
||||
[0x86] = "Bad User Name or Password",
|
||||
[0x87] = "Not authorized",
|
||||
[0x88] = "Server unavailable",
|
||||
[0x89] = "Server busy",
|
||||
[0x8A] = "Banned",
|
||||
[0x8C] = "Bad authentication method",
|
||||
[0x90] = "Topic Name invalid",
|
||||
[0x95] = "Packet too large",
|
||||
[0x97] = "Quota exceeded",
|
||||
[0x99] = "Payload format invalid",
|
||||
[0x9A] = "Retain not supported",
|
||||
[0x9B] = "QoS not supported",
|
||||
[0x9C] = "Use another server",
|
||||
[0x9D] = "Server moved",
|
||||
[0x9F] = "Connection rate exceeded",
|
||||
}
|
||||
protocol.connack_rc = connack_rc
|
||||
|
||||
-- Returns true if Packet Identifier field are required for given packet
|
||||
function protocol.packet_id_required(args)
|
||||
assert(type(args) == "table", "expecting args to be a table")
|
||||
assert(type(args.type) == "number", "expecting .type to be a number")
|
||||
local ptype = args.type
|
||||
if ptype == packet_type.PUBLISH and args.qos and args.qos > 0 then
|
||||
return true
|
||||
end
|
||||
return packets_requiring_packet_id[ptype]
|
||||
end
|
||||
|
||||
-- Metatable for combined data packet, should looks like a string
|
||||
local combined_packet_mt = {
|
||||
-- Convert combined data packet to string
|
||||
__tostring = function(self)
|
||||
local strings = {}
|
||||
for i, part in ipairs(self) do
|
||||
strings[i] = tostring(part)
|
||||
end
|
||||
return tbl_concat(strings)
|
||||
end,
|
||||
|
||||
-- Get length of combined data packet
|
||||
len = function(self)
|
||||
local len = 0
|
||||
for _, part in ipairs(self) do
|
||||
len = len + part:len()
|
||||
end
|
||||
return len
|
||||
end,
|
||||
|
||||
-- Append part to the end of combined data packet
|
||||
append = function(self, part)
|
||||
self[#self + 1] = part
|
||||
end
|
||||
}
|
||||
|
||||
-- Make combined_packet_mt table works like a class
|
||||
combined_packet_mt.__index = function(_, key)
|
||||
return combined_packet_mt[key]
|
||||
end
|
||||
|
||||
-- Combine several data parts into one
|
||||
function protocol.combine(...)
|
||||
return setmetatable({...}, combined_packet_mt)
|
||||
end
|
||||
|
||||
-- Convert any value to string, respecting strings and tables
|
||||
local function value_tostring(value)
|
||||
local t = type(value)
|
||||
if t == "string" then
|
||||
return str_format("%q", value)
|
||||
elseif t == "table" then
|
||||
local res = {}
|
||||
for k, v in pairs(value) do
|
||||
if type(k) == "number" then
|
||||
res[#res + 1] = value_tostring(v)
|
||||
else
|
||||
if k:match("^[a-zA-Z_][_%w]*$") then
|
||||
res[#res + 1] = str_format("%s=%s", k, value_tostring(v))
|
||||
else
|
||||
res[#res + 1] = str_format("[%q]=%s", k, value_tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
return str_format("{%s}", tbl_concat(res, ", "))
|
||||
else
|
||||
return tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
-- Convert packet to string representation
|
||||
local function packet_tostring(packet)
|
||||
local res = {}
|
||||
for k, v in pairs(packet) do
|
||||
res[#res + 1] = str_format("%s=%s", k, value_tostring(v))
|
||||
end
|
||||
return str_format("%s{%s}", tostring(packet_type[packet.type]), tbl_concat(res, ", "))
|
||||
end
|
||||
protocol.packet_tostring = packet_tostring
|
||||
|
||||
-- Parsed packet metatable
|
||||
protocol.packet_mt = {
|
||||
__tostring = packet_tostring,
|
||||
}
|
||||
|
||||
-- Parsed CONNACK packet metatable
|
||||
protocol.connack_packet_mt = {
|
||||
__tostring = packet_tostring,
|
||||
}
|
||||
protocol.connack_packet_mt.__index = protocol.connack_packet_mt
|
||||
|
||||
--- Returns reason string for CONNACK packet
|
||||
-- @treturn string Reason string according packet's rc field
|
||||
function protocol.connack_packet_mt:reason_string()
|
||||
return connack_rc[self.rc]
|
||||
end
|
||||
|
||||
--- Start parsing a new packet
|
||||
-- @tparam function read_func - function to read data from the network connection
|
||||
-- @treturn number packet_type
|
||||
-- @treturn number flags
|
||||
-- @treturn table input - a table with fields "read_func" and "available" representing a stream-like object
|
||||
-- to read already received packet data in chunks
|
||||
-- @return false and error_message on failure
|
||||
function protocol.start_parse_packet(read_func)
|
||||
assert(type(read_func) == "function", "expecting read_func to be a function")
|
||||
local byte1, err, len, data
|
||||
|
||||
-- parse fixed header
|
||||
-- DOC[v3.1.1]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180832
|
||||
-- DOC[v5.0]: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901020
|
||||
byte1, err = read_func(1)
|
||||
if not byte1 then
|
||||
return false, "failed to read first byte: "..err
|
||||
end
|
||||
byte1 = str_byte(byte1, 1, 1)
|
||||
local ptype = rshift(byte1, 4)
|
||||
local flags = band(byte1, 0xF)
|
||||
len, err = parse_var_length(read_func)
|
||||
if not len then
|
||||
return false, "failed to parse remaining length: "..err
|
||||
end
|
||||
|
||||
-- create packet parser instance (aka input)
|
||||
local input = {1, available = 0} -- input data offset and available size
|
||||
if len > 0 then
|
||||
data, err = read_func(len)
|
||||
else
|
||||
data = ""
|
||||
end
|
||||
if not data then
|
||||
return false, "failed to read packet data: "..err
|
||||
end
|
||||
input.available = data:len()
|
||||
|
||||
-- read data function for the input instance
|
||||
input.read_func = function(size)
|
||||
if size > input.available then
|
||||
return false, "not enough data to read size: "..size
|
||||
end
|
||||
local off = input[1]
|
||||
local res = str_sub(data, off, off + size - 1)
|
||||
input[1] = off + size
|
||||
input.available = input.available - size
|
||||
return res
|
||||
end
|
||||
|
||||
return ptype, flags, input
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return protocol
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
427
controller-host/mqtt/protocol4.lua
Normal file
427
controller-host/mqtt/protocol4.lua
Normal file
@ -0,0 +1,427 @@
|
||||
--[[
|
||||
|
||||
Here is a MQTT v3.1.1 protocol implementation
|
||||
|
||||
MQTT v3.1.1 documentation (DOC):
|
||||
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html
|
||||
|
||||
]]
|
||||
|
||||
-- module table
|
||||
local protocol4 = {}
|
||||
|
||||
-- load required stuff
|
||||
local type = type
|
||||
local error = error
|
||||
local assert = assert
|
||||
local require = require
|
||||
local tostring = tostring
|
||||
local setmetatable = setmetatable
|
||||
|
||||
local bit = require("mqtt.bitwrap")
|
||||
local bor = bit.bor
|
||||
local band = bit.band
|
||||
local lshift = bit.lshift
|
||||
local rshift = bit.rshift
|
||||
|
||||
local protocol = require("mqtt.protocol")
|
||||
local make_uint8 = protocol.make_uint8
|
||||
local make_uint16 = protocol.make_uint16
|
||||
local make_string = protocol.make_string
|
||||
local make_header = protocol.make_header
|
||||
local check_qos = protocol.check_qos
|
||||
local check_packet_id = protocol.check_packet_id
|
||||
local combine = protocol.combine
|
||||
local packet_type = protocol.packet_type
|
||||
local packet_mt = protocol.packet_mt
|
||||
local connack_packet_mt = protocol.connack_packet_mt
|
||||
local start_parse_packet = protocol.start_parse_packet
|
||||
local parse_uint8 = protocol.parse_uint8
|
||||
local parse_uint16 = protocol.parse_uint16
|
||||
|
||||
-- Create Connect Flags data, DOC: 3.1.2.3 Connect Flags
|
||||
local function make_connect_flags(args)
|
||||
local byte = 0 -- bit 0 should be zero
|
||||
-- DOC: 3.1.2.4 Clean Session
|
||||
if args.clean ~= nil then
|
||||
assert(type(args.clean) == "boolean", "expecting .clean to be a boolean")
|
||||
if args.clean then
|
||||
byte = bor(byte, lshift(1, 1))
|
||||
end
|
||||
end
|
||||
-- DOC: 3.1.2.5 Will Flag
|
||||
if args.will ~= nil then
|
||||
-- check required args are presented
|
||||
assert(type(args.will) == "table", "expecting .will to be a table")
|
||||
assert(type(args.will.payload) == "string", "expecting .will.payload to be a string")
|
||||
assert(type(args.will.topic) == "string", "expecting .will.topic to be a string")
|
||||
if args.will.qos ~= nil then
|
||||
assert(type(args.will.qos) == "number", "expecting .will.qos to be a number")
|
||||
assert(check_qos(args.will.qos), "expecting .will.qos to be a valid QoS value")
|
||||
end
|
||||
if args.will.retain ~= nil then
|
||||
assert(type(args.will.retain) == "boolean", "expecting .will.retain to be a boolean")
|
||||
end
|
||||
-- will flag should be set to 1
|
||||
byte = bor(byte, lshift(1, 2))
|
||||
-- DOC: 3.1.2.6 Will QoS
|
||||
byte = bor(byte, lshift(args.will.qos or 0, 3))
|
||||
-- DOC: 3.1.2.7 Will Retain
|
||||
if args.will.retain then
|
||||
byte = bor(byte, lshift(1, 5))
|
||||
end
|
||||
end
|
||||
-- DOC: 3.1.2.8 User Name Flag
|
||||
if args.username ~= nil then
|
||||
assert(type(args.username) == "string", "expecting .username to be a string")
|
||||
byte = bor(byte, lshift(1, 7))
|
||||
end
|
||||
-- DOC: 3.1.2.9 Password Flag
|
||||
if args.password ~= nil then
|
||||
assert(type(args.password) == "string", "expecting .password to be a string")
|
||||
assert(args.username, "the .username is required to set .password")
|
||||
byte = bor(byte, lshift(1, 6))
|
||||
end
|
||||
return make_uint8(byte)
|
||||
end
|
||||
|
||||
-- Create CONNECT packet, DOC: 3.1 CONNECT – Client requests a connection to a Server
|
||||
local function make_packet_connect(args)
|
||||
-- check args
|
||||
assert(type(args.id) == "string", "expecting .id to be a string with MQTT client id")
|
||||
-- DOC: 3.1.2.10 Keep Alive
|
||||
local keep_alive_ival = 0
|
||||
if args.keep_alive then
|
||||
assert(type(args.keep_alive) == "number")
|
||||
keep_alive_ival = args.keep_alive
|
||||
end
|
||||
-- DOC: 3.1.2 Variable header
|
||||
local variable_header = combine(
|
||||
make_string("MQTT"), -- DOC: 3.1.2.1 Protocol Name
|
||||
make_uint8(4), -- DOC: 3.1.2.2 Protocol Level (4 is for MQTT v3.1.1)
|
||||
make_connect_flags(args), -- DOC: 3.1.2.3 Connect Flags
|
||||
make_uint16(keep_alive_ival) -- DOC: 3.1.2.10 Keep Alive
|
||||
)
|
||||
-- DOC: 3.1.3 Payload
|
||||
-- DOC: 3.1.3.1 Client Identifier
|
||||
local payload = combine(
|
||||
make_string(args.id)
|
||||
)
|
||||
if args.will then
|
||||
-- DOC: 3.1.3.2 Will Topic
|
||||
assert(type(args.will.topic) == "string", "expecting will.topic to be a string")
|
||||
payload:append(make_string(args.will.topic))
|
||||
-- DOC: 3.1.3.3 Will Message
|
||||
assert(args.will.payload == nil or type(args.will.payload) == "string", "expecting will.payload to be a string or nil")
|
||||
payload:append(make_string(args.will.payload or ""))
|
||||
end
|
||||
if args.username then
|
||||
-- DOC: 3.1.3.4 User Name
|
||||
payload:append(make_string(args.username))
|
||||
if args.password then
|
||||
-- DOC: 3.1.3.5 Password
|
||||
payload:append(make_string(args.password))
|
||||
end
|
||||
end
|
||||
-- DOC: 3.1.1 Fixed header
|
||||
local header = make_header(packet_type.CONNECT, 0, variable_header:len() + payload:len())
|
||||
return combine(header, variable_header, payload)
|
||||
end
|
||||
|
||||
-- Create PUBLISH packet, DOC: 3.3 PUBLISH – Publish message
|
||||
local function make_packet_publish(args)
|
||||
-- check args
|
||||
assert(type(args.topic) == "string", "expecting .topic to be a string")
|
||||
if args.payload ~= nil then
|
||||
assert(type(args.payload) == "string", "expecting .payload to be a string")
|
||||
end
|
||||
if args.qos ~= nil then
|
||||
assert(type(args.qos) == "number", "expecting .qos to be a number")
|
||||
assert(check_qos(args.qos), "expecting .qos to be a valid QoS value")
|
||||
end
|
||||
if args.retain ~= nil then
|
||||
assert(type(args.retain) == "boolean", "expecting .retain to be a boolean")
|
||||
end
|
||||
if args.dup ~= nil then
|
||||
assert(type(args.dup) == "boolean", "expecting .dup to be a boolean")
|
||||
end
|
||||
-- DOC: 3.3.1 Fixed header
|
||||
local flags = 0
|
||||
-- 3.3.1.3 RETAIN
|
||||
if args.retain then
|
||||
flags = bor(flags, 0x1)
|
||||
end
|
||||
-- DOC: 3.3.1.2 QoS
|
||||
flags = bor(flags, lshift(args.qos or 0, 1))
|
||||
-- DOC: 3.3.1.1 DUP
|
||||
if args.dup then
|
||||
flags = bor(flags, lshift(1, 3))
|
||||
end
|
||||
-- DOC: 3.3.2 Variable header
|
||||
local variable_header = combine(
|
||||
make_string(args.topic)
|
||||
)
|
||||
-- DOC: 3.3.2.2 Packet Identifier
|
||||
if args.qos and args.qos > 0 then
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
variable_header:append(make_uint16(args.packet_id))
|
||||
end
|
||||
local payload
|
||||
if args.payload then
|
||||
payload = args.payload
|
||||
else
|
||||
payload = ""
|
||||
end
|
||||
-- DOC: 3.3.1 Fixed header
|
||||
local header = make_header(packet_type.PUBLISH, flags, variable_header:len() + payload:len())
|
||||
return combine(header, variable_header, payload)
|
||||
end
|
||||
|
||||
-- Create PUBACK packet, DOC: 3.4 PUBACK – Publish acknowledgement
|
||||
local function make_packet_puback(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
-- DOC: 3.4.1 Fixed header
|
||||
local header = make_header(packet_type.PUBACK, 0, 2)
|
||||
-- DOC: 3.4.2 Variable header
|
||||
local variable_header = make_uint16(args.packet_id)
|
||||
return combine(header, variable_header)
|
||||
end
|
||||
|
||||
-- Create PUBREC packet, DOC: 3.5 PUBREC – Publish received (QoS 2 publish received, part 1)
|
||||
local function make_packet_pubrec(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
-- DOC: 3.5.1 Fixed header
|
||||
local header = make_header(packet_type.PUBREC, 0, 2)
|
||||
-- DOC: 3.5.2 Variable header
|
||||
local variable_header = make_uint16(args.packet_id)
|
||||
return combine(header, variable_header)
|
||||
end
|
||||
|
||||
-- Create PUBREL packet, DOC: 3.6 PUBREL – Publish release (QoS 2 publish received, part 2)
|
||||
local function make_packet_pubrel(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
-- DOC: 3.6.1 Fixed header
|
||||
local header = make_header(packet_type.PUBREL, 0x2, 2) -- flags are 0x2 == 0010 bits (fixed value)
|
||||
-- DOC: 3.6.2 Variable header
|
||||
local variable_header = make_uint16(args.packet_id)
|
||||
return combine(header, variable_header)
|
||||
end
|
||||
|
||||
-- Create PUBCOMP packet, DOC: 3.7 PUBCOMP – Publish complete (QoS 2 publish received, part 3)
|
||||
local function make_packet_pubcomp(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
-- DOC: 3.7.1 Fixed header
|
||||
local header = make_header(packet_type.PUBCOMP, 0, 2)
|
||||
-- DOC: 3.7.2 Variable header
|
||||
local variable_header = make_uint16(args.packet_id)
|
||||
return combine(header, variable_header)
|
||||
end
|
||||
|
||||
-- Create SUBSCRIBE packet, DOC: 3.8 SUBSCRIBE - Subscribe to topics
|
||||
local function make_packet_subscribe(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
assert(type(args.subscriptions) == "table", "expecting .subscriptions to be a table")
|
||||
assert(#args.subscriptions > 0, "expecting .subscriptions to be a non-empty array")
|
||||
-- DOC: 3.8.2 Variable header
|
||||
local variable_header = combine(
|
||||
make_uint16(args.packet_id)
|
||||
)
|
||||
-- DOC: 3.8.3 Payload
|
||||
local payload = combine()
|
||||
for i, subscription in ipairs(args.subscriptions) do
|
||||
assert(type(subscription) == "table", "expecting .subscriptions["..i.."] to be a table")
|
||||
assert(type(subscription.topic) == "string", "expecting .subscriptions["..i.."].topic to be a string")
|
||||
if subscription.qos ~= nil then
|
||||
assert(type(subscription.qos) == "number", "expecting .subscriptions["..i.."].qos to be a number")
|
||||
assert(check_qos(subscription.qos), "expecting .subscriptions["..i.."].qos to be a valid QoS value")
|
||||
end
|
||||
payload:append(make_string(subscription.topic))
|
||||
payload:append(make_uint8(subscription.qos or 0))
|
||||
end
|
||||
-- DOC: 3.8.1 Fixed header
|
||||
local header = make_header(packet_type.SUBSCRIBE, 2, variable_header:len() + payload:len()) -- NOTE: fixed flags value 0x2
|
||||
return combine(header, variable_header, payload)
|
||||
end
|
||||
|
||||
-- Create UNSUBSCRIBE packet, DOC: 3.10 UNSUBSCRIBE – Unsubscribe from topics
|
||||
local function make_packet_unsubscribe(args)
|
||||
-- check args
|
||||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||||
assert(type(args.subscriptions) == "table", "expecting .subscriptions to be a table")
|
||||
assert(#args.subscriptions > 0, "expecting .subscriptions to be a non-empty array")
|
||||
-- DOC: 3.10.2 Variable header
|
||||
local variable_header = combine(
|
||||
make_uint16(args.packet_id)
|
||||
)
|
||||
-- DOC: 3.10.3 Payload
|
||||
local payload = combine()
|
||||
for i, subscription in ipairs(args.subscriptions) do
|
||||
assert(type(subscription) == "string", "expecting .subscriptions["..i.."] to be a string")
|
||||
payload:append(make_string(subscription))
|
||||
end
|
||||
-- DOC: 3.10.1 Fixed header
|
||||
local header = make_header(packet_type.UNSUBSCRIBE, 2, variable_header:len() + payload:len()) -- NOTE: fixed flags value 0x2
|
||||
return combine(header, variable_header, payload)
|
||||
end
|
||||
|
||||
-- Create packet of given {type: number} in args
|
||||
function protocol4.make_packet(args)
|
||||
assert(type(args) == "table", "expecting args to be a table")
|
||||
assert(type(args.type) == "number", "expecting .type number in args")
|
||||
local ptype = args.type
|
||||
if ptype == packet_type.CONNECT then
|
||||
return make_packet_connect(args)
|
||||
elseif ptype == packet_type.PUBLISH then
|
||||
return make_packet_publish(args)
|
||||
elseif ptype == packet_type.PUBACK then
|
||||
return make_packet_puback(args)
|
||||
elseif ptype == packet_type.PUBREC then
|
||||
return make_packet_pubrec(args)
|
||||
elseif ptype == packet_type.PUBREL then
|
||||
return make_packet_pubrel(args)
|
||||
elseif ptype == packet_type.PUBCOMP then
|
||||
return make_packet_pubcomp(args)
|
||||
elseif ptype == packet_type.SUBSCRIBE then
|
||||
return make_packet_subscribe(args)
|
||||
elseif ptype == packet_type.UNSUBSCRIBE then
|
||||
return make_packet_unsubscribe(args)
|
||||
elseif ptype == packet_type.PINGREQ then
|
||||
-- DOC: 3.12 PINGREQ – PING request
|
||||
return combine("\192\000") -- 192 == 0xC0, type == 12, flags == 0
|
||||
elseif ptype == packet_type.DISCONNECT then
|
||||
-- DOC: 3.14 DISCONNECT – Disconnect notification
|
||||
return combine("\224\000") -- 224 == 0xD0, type == 14, flags == 0
|
||||
else
|
||||
error("unexpected packet type to make: "..ptype)
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse packet using given read_func
|
||||
-- Returns packet on success or false and error message on failure
|
||||
function protocol4.parse_packet(read_func)
|
||||
local ptype, flags, input = start_parse_packet(read_func)
|
||||
if not ptype then
|
||||
return false, flags
|
||||
end
|
||||
-- parse readed data according type in fixed header
|
||||
if ptype == packet_type.CONNACK then
|
||||
-- DOC: 3.2 CONNACK – Acknowledge connection request
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
local byte1, byte2 = parse_uint8(input.read_func), parse_uint8(input.read_func)
|
||||
local sp = (band(byte1, 0x1) ~= 0)
|
||||
return setmetatable({type=ptype, sp=sp, rc=byte2}, connack_packet_mt)
|
||||
elseif ptype == packet_type.PUBLISH then
|
||||
-- DOC: 3.3 PUBLISH – Publish message
|
||||
-- DOC: 3.3.1.1 DUP
|
||||
local dup = (band(flags, 0x8) ~= 0)
|
||||
-- DOC: 3.3.1.2 QoS
|
||||
local qos = band(rshift(flags, 1), 0x3)
|
||||
-- DOC: 3.3.1.3 RETAIN
|
||||
local retain = (band(flags, 0x1) ~= 0)
|
||||
-- DOC: 3.3.2.1 Topic Name
|
||||
if input.available < 2 then
|
||||
return false, "expecting data of length at least 2 bytes"
|
||||
end
|
||||
local topic_len = parse_uint16(input.read_func)
|
||||
if input.available < topic_len then
|
||||
return false, "malformed PUBLISH packet: not enough data to parse topic"
|
||||
end
|
||||
local topic = input.read_func(topic_len)
|
||||
-- DOC: 3.3.2.2 Packet Identifier
|
||||
local packet_id
|
||||
if qos > 0 then
|
||||
-- DOC: 3.3.2.2 Packet Identifier
|
||||
if input.available < 2 then
|
||||
return false, "malformed PUBLISH packet: not enough data to parse packet_id"
|
||||
end
|
||||
packet_id = parse_uint16(input.read_func)
|
||||
end
|
||||
-- DOC: 3.3.3 Payload
|
||||
local payload
|
||||
if input.available > 0 then
|
||||
payload = input.read_func(input.available)
|
||||
end
|
||||
return setmetatable({type=ptype, dup=dup, qos=qos, retain=retain, packet_id=packet_id, topic=topic, payload=payload}, packet_mt)
|
||||
elseif ptype == packet_type.PUBACK then
|
||||
-- DOC: 3.4 PUBACK – Publish acknowledgement
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
-- DOC: 3.4.2 Variable header
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
return setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
|
||||
elseif ptype == packet_type.PUBREC then
|
||||
-- DOC: 3.5 PUBREC – Publish received (QoS 2 publish received, part 1)
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
-- DOC: 3.5.2 Variable header
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
return setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
|
||||
elseif ptype == packet_type.PUBREL then
|
||||
-- DOC: 3.6 PUBREL – Publish release (QoS 2 publish received, part 2)
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
-- also flags should be checked to equals 2 by the server
|
||||
-- DOC: 3.6.2 Variable header
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
return setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
|
||||
elseif ptype == packet_type.PUBCOMP then
|
||||
-- 3.7 PUBCOMP – Publish complete (QoS 2 publish received, part 3)
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
-- DOC: 3.7.2 Variable header
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
return setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
|
||||
elseif ptype == packet_type.SUBACK then
|
||||
-- DOC: 3.9 SUBACK – Subscribe acknowledgement
|
||||
if input.available < 3 then
|
||||
return false, "expecting data of length at least 3 bytes"
|
||||
end
|
||||
-- DOC: 3.9.2 Variable header
|
||||
-- DOC: 3.9.3 Payload
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
local rc = {} -- DOC: The payload contains a list of return codes.
|
||||
while input.available > 0 do
|
||||
rc[#rc + 1] = parse_uint8(input.read_func)
|
||||
end
|
||||
return setmetatable({type=ptype, packet_id=packet_id, rc=rc}, packet_mt)
|
||||
elseif ptype == packet_type.UNSUBACK then
|
||||
-- DOC: 3.11 UNSUBACK – Unsubscribe acknowledgement
|
||||
if input.available ~= 2 then
|
||||
return false, "expecting data of length 2 bytes"
|
||||
end
|
||||
-- DOC: 3.11.2 Variable header
|
||||
local packet_id = parse_uint16(input.read_func)
|
||||
return setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
|
||||
elseif ptype == packet_type.PINGRESP then
|
||||
-- DOC: 3.13 PINGRESP – PING response
|
||||
if input.available ~= 0 then
|
||||
return false, "expecting data of length 0 bytes"
|
||||
end
|
||||
return setmetatable({type=ptype}, packet_mt)
|
||||
else
|
||||
return false, "unexpected packet type received: "..tostring(ptype)
|
||||
end
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return protocol4
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
1039
controller-host/mqtt/protocol5.lua
Normal file
1039
controller-host/mqtt/protocol5.lua
Normal file
File diff suppressed because it is too large
Load Diff
35
controller-host/mqtt/tools.lua
Normal file
35
controller-host/mqtt/tools.lua
Normal file
@ -0,0 +1,35 @@
|
||||
-- module table
|
||||
local tools = {}
|
||||
|
||||
-- load required stuff
|
||||
local require = require
|
||||
|
||||
local string = require("string")
|
||||
local str_format = string.format
|
||||
local str_byte = string.byte
|
||||
|
||||
local table = require("table")
|
||||
local tbl_concat = table.concat
|
||||
|
||||
local math = require("math")
|
||||
local math_floor = math.floor
|
||||
|
||||
|
||||
-- Returns string encoded as HEX
|
||||
function tools.hex(str)
|
||||
local res = {}
|
||||
for i = 1, #str do
|
||||
res[i] = str_format("%02X", str_byte(str, i))
|
||||
end
|
||||
return tbl_concat(res)
|
||||
end
|
||||
|
||||
-- Integer division function
|
||||
function tools.div(x, y)
|
||||
return math_floor(x / y)
|
||||
end
|
||||
|
||||
-- export module table
|
||||
return tools
|
||||
|
||||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|
70
controller-host/mqttthread.lua
Normal file
70
controller-host/mqttthread.lua
Normal file
@ -0,0 +1,70 @@
|
||||
-- Load MQTT library
|
||||
local mqtt = require("mqtt")
|
||||
local client
|
||||
|
||||
local eventChannel = love.thread.getChannel("mqtt_event")
|
||||
local commandChannel = love.thread.getChannel("mqtt_command")
|
||||
|
||||
local function call(target, ...)
|
||||
local args = {...}
|
||||
eventChannel:push {
|
||||
target = target,
|
||||
args = args
|
||||
}
|
||||
end
|
||||
|
||||
local function onConnect(connack)
|
||||
print("On connect")
|
||||
call("connect", connack)
|
||||
end
|
||||
|
||||
local function onMessage(message)
|
||||
if message.topic:sub(0, 7) == "spider/" then
|
||||
message.topic = message.topic:sub(8)
|
||||
end
|
||||
print(message.topic)
|
||||
call("message2", message.topic, message.payload)
|
||||
end
|
||||
|
||||
local function onCommand(command)
|
||||
if command.command == "send" then
|
||||
client:publish {
|
||||
topic = "spider/" .. command.topic,
|
||||
payload = command.arg,
|
||||
qos = 0
|
||||
}
|
||||
elseif command.command == "subscribe" then
|
||||
local topic = "spider/" .. command.topic
|
||||
assert(client:subscribe {
|
||||
topic = topic
|
||||
})
|
||||
print("Subribed to " .. topic)
|
||||
end
|
||||
end
|
||||
|
||||
local function main()
|
||||
client = mqtt.client {
|
||||
uri = "mqtt.seeseepuff.be",
|
||||
username = "mqtt_controller",
|
||||
clean = true,
|
||||
reconnect = 5,
|
||||
}
|
||||
|
||||
client:on {
|
||||
connect = onConnect,
|
||||
message = onMessage
|
||||
}
|
||||
|
||||
print("Connecting")
|
||||
local ioloop = mqtt.get_ioloop()
|
||||
local i = 0
|
||||
ioloop:add(function()
|
||||
local command = commandChannel:pop()
|
||||
if command then
|
||||
onCommand(command)
|
||||
end
|
||||
end)
|
||||
mqtt.run_ioloop(client)
|
||||
end
|
||||
|
||||
main()
|
54
upload.lua
Normal file
54
upload.lua
Normal file
@ -0,0 +1,54 @@
|
||||
package.path = package.path .. ';./controller-host/?/init.lua;./controller-host/?.lua'
|
||||
local mqtt = require("mqtt")
|
||||
local client
|
||||
|
||||
local file = ...
|
||||
local fh = io.open(file, "rb")
|
||||
local contents = fh:read("a")
|
||||
fh:close()
|
||||
|
||||
function printTable(table, indentation)
|
||||
indentation = indentation or ""
|
||||
for name, value in pairs(table) do
|
||||
print(indentation .. tostring(name) .. ": " .. tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
local function onMessage(data)
|
||||
print(data.payload)
|
||||
end
|
||||
|
||||
local function onConnect(connack)
|
||||
if connack.rc ~= 0 then
|
||||
print("Connection to broker failed:", connack:reason_string())
|
||||
os.exit(1)
|
||||
end
|
||||
print("Connected to MQTT")
|
||||
|
||||
assert(client:subscribe{
|
||||
topic = "spider/controller/stdout"
|
||||
})
|
||||
|
||||
io.write("Sending payload...")
|
||||
assert(client:publish {
|
||||
topic = "spider/controller/payload",
|
||||
payload = contents,
|
||||
qos = 0
|
||||
})
|
||||
print(" DONE!")
|
||||
end
|
||||
|
||||
client = mqtt.client {
|
||||
uri = "mqtt.seeseepuff.be",
|
||||
username = "mqtt_controller",
|
||||
clean = true,
|
||||
reconnect = 5,
|
||||
}
|
||||
|
||||
client:on {
|
||||
connect = onConnect,
|
||||
message = onMessage,
|
||||
}
|
||||
|
||||
print("Connecting")
|
||||
mqtt.run_ioloop(client)
|
Loading…
x
Reference in New Issue
Block a user