From d9da54abcc784ecca5e1c0c415820a32e68c2296 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 11 Oct 2015 14:07:42 +0200 Subject: o Initial import of diller firmware. --- .gitignore | 2 + Makefile | 12 +++++ diller/README.md | 103 ++++++++++++++++++++++++++++++++++++++++++ diller/inter.lua | 73 ++++++++++++++++++++++++++++++ diller/main.lua | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ diller/mq.lua | 61 +++++++++++++++++++++++++ 6 files changed, 385 insertions(+) create mode 100644 Makefile create mode 100644 diller/README.md create mode 100644 diller/inter.lua create mode 100644 diller/main.lua create mode 100644 diller/mq.lua diff --git a/.gitignore b/.gitignore index 0218843..203c76d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ nodemcu-uploader cfg-* .cookie + +*.lc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad8dd73 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: + @echo Check out README.md + +%.lc:%.lua + @luac -s -o $@ $< + +compile-%: + @$(MAKE) -j 8 $(patsubst %.lua,%.lc,$(wildcard $(patsubst compile-%,%,$@)/*.lua)) + +upload-%: + @$(MAKE) $(patsubst upload-%,compile-%,$@) + ./upload.sh $(patsubst upload-%,%,$@) diff --git a/diller/README.md b/diller/README.md new file mode 100644 index 0000000..322ec45 --- /dev/null +++ b/diller/README.md @@ -0,0 +1,103 @@ +# TODOs / Notes + +Is registration required? Can't the firmware just send an update without a value on every boot? + +# MQTT + +## Device / Sensor hierarchy + + / + /sensors + / + /value + /type (retained) + /name (retained) + /description (retained) + /type TODO: remove? + /firmware For updating the device's firmware + +## Device registration flow + + sub: /registration/ + pub: /registration {} + +# Diller serial API + +## Network settings + +Request: + +Update network settings. If no paramters are given, no changes are done. + + network [ip=..] [gateway=..] [ssid=..] + +Response: + +The command will always return the current values. If the ssid was recently set, the ip, gateway and netmask fields may be missing. + + ok ip=.. gateway=.. netmask=.. ssid=.. + +## Register property + +Request: + + register-property id=.. type=.. name=.. description=.. + +Type examples: + +* `temperature` +* `switch` - boolean switches +* `humidity` +* `rtc` + +Response: + + ok + +## Publish property value + +Request: + + set-property id=.. [value=..] [name=..] [description=..] + +Response: + + ok + +The value might not be updated directly, but may be buffered on the device if it is not yet connected. + +## Publish raw MQTT message + +Request: + + publish topic=.. [qos=..] [retain=..] + + +Response: + + ok + +## Reset the device + +Request: + + reset + +Response + + ok + +# Example session + + require('main').main(); + +Get the current network configuration to update the Arduino's LCD display + + > network + < ok ip=1.3.3.7 netmask=255.255.255.0 gateway=1.3.3.1 ssid=awesome + +Register the properties. This is done on every boot to keep the server in sync with the firmware's features. Old properties will not be removed. + + > set-property id=temp-0 value=12.3 type=temperature name=Water + < ok + diff --git a/diller/inter.lua b/diller/inter.lua new file mode 100644 index 0000000..725edbd --- /dev/null +++ b/diller/inter.lua @@ -0,0 +1,73 @@ +local P = {} + +function P.parse(line) + -- print("inter_parse: line="..line) + + line = string.sub(line, 1, string.find(line, "\r")) + line = line:gsub("%s+", "") + + local args = {} + local i = string.find(line, " ") + if not i then +-- print("inter_parse: invalid line: no command, line="..line) + return string.len(line) > 0 and line or nil, args + end + local cmd = string.sub(line, 1, i - 1) + -- print("inter_parse: cmd="..cmd) + + local last = i + 1 + while i do + local key + local value + + i = string.find(line, " ", last) + arg = string.sub(line, last, i) + arg = arg:gsub("%s+", "") + + if #arg > 0 then +-- print("inter_parse: arg: "..arg) + j = string.find(arg, "=") + if not j then + if #arg > 0 then + key = arg + args[key] = true +-- print("inter_parse: key="..key.."=true") + end + else + key = string.sub(arg, 1, j-1) + value = string.sub(arg, j+1) + args[key] = value +-- print("inter_parse: key="..key..", value="..value) + end + end + + if not i then + break + end + last = i + 1 + end + + return cmd, args +end + +local cb +function inter_on_line(line) +-- print("line:"..line) + + local cmd, args = P.parse(line) + if not cmd then + return + end + + if cb then + cb(cmd, args) + end +end + +function P.init(callback) + cb = callback + + uart.on("data", "\r", inter_on_line, 0) +end + +return P diff --git a/diller/main.lua b/diller/main.lua new file mode 100644 index 0000000..112284c --- /dev/null +++ b/diller/main.lua @@ -0,0 +1,134 @@ +function panic(reason) + print("PANIC: "..reason) + -- This will trigger a restart, but not immediately + node.restart() +end + +local P = {} + +local inter = require('inter') +local mq = require('mq') + +local function on_cmd(cmd, args) + if not cmd then + return + end + + print("on_cmd: '"..cmd.."', #args="..tostring(table.getn(args))) + for k, v in pairs(args) do + print(k.."="..tostring(v)) + end + + if cmd == "reset" then + print("ok") + panic("Reset requested") + elseif cmd == "wlan" then + local ssid = args.ssid password = args.password + + if args.ssid then + wifi.sta.config(args.ssid, args.password, 1) + end + ssid, password, bssid_set, bssid = wifi.sta.getconfig() + print("ok ssid="..(ssid or '')) + elseif cmd == "network" then + local ip = args.ip nm = args.netmask gw = args.gateway + + if args.ip then + wifi.sta.setip({ip=ip, netmask=nm, gateway=gw}) + end + + ip, nm, gw = wifi.sta.getip() + print("ok ip="..(ip or '').." netmask="..(nm or '').." gateway="..(gw or '')) + elseif cmd == "set-property" then + local id = args.id + if not id then + print("fail status=missing-id") + else + if args.value then + mq.publish(args.id.."/value", args.value) + end + if args.name then + mq.publish(args.id.."/name", args.name) + end + if args.description then + mq.publish(args.id.."/description", args.description) + end + end + elseif cmd == "publish" then +-- print("Publishing, topic="..tostring(cmd.topic)..", payload="..tostring(cmd.payload)) + ok, msg = mq.publish(cmd.topic, cmd.payload) + if ok then + print("ok status="..msg) + else + print("failed status="..msg) + end + else + print("failed status=unknown-command") + end +end + +local function read_cfg(name) + local filename = "cfg-"..name + if not file.open(filename, "r") then + panic("Could not read configuration file: "..filename) + end + local value = file.readline() + file.close() + if value == nil or #value == 0 then + panic("Empty configuration file: "..filename) + end + return string.sub(value, 1, -2) +end + +local function print_status() + print("System Status") + print("Uptime : "..tmr.time()) + print("Heap left: "..node.heap()) + ip, nm, gw = wifi.sta.getip() + print("IP : "..tostring(ip)) + print("Netmask : "..tostring(nm)) + print("Gateway : "..tostring(gw)) +end + +-- uart.setup(id, baud, databits, parity, stopbits, echo) +-- uart.setup(0, 115200, 8, 0, 1, 0) +-- uart.setup(0, 9600, 8, 0, 1, 0) + +function P.main() + local timers = { + status = 0, + inter = 1, + mqtt = 2 + } + + local wlan_ssid = read_cfg("wlan-ssid") + local wlan_password = read_cfg("wlan-password") + print("Connecting to SSID: "..wlan_ssid) + wifi.setmode(wifi.STATION) + wifi.sta.config(wlan_ssid, wlan_password) + + local client_id = "esp8266-"..wifi.sta.getmac() + mq.init(timers.mqtt, client_id) + + inter.init(on_cmd) + + tmr.alarm(timers.status, 10 * 1000, 1, print_status) + +-- local majorVer, minorVer, devVer, chipId, flashId, flashSize, flashMode, flashSpeed, buildDate = node.info() +-- payload = '{"version": "'..majorVer..'.'..minorVer..'.'..devVer..'", "chipId":'..chipId..', "flashId":'..flashId..', "flashSize":'..flashSize..', "flashMode":'..flashMode..', "flashSpeed":'..flashSpeed +-- +-- if buildDate then +-- payload = payload..', "buildDate": "'..buildDate..'"' +-- end +-- +-- if node.info_versions then +-- major, minor, dev, buildDate, sdkVersion = node.info_versions() +-- payload = payload..'", versions": {"major": '..major..', "minor": '..minor..', "dev": '..dev..', "buildDate": "'..buildDate..'", "sdk": "'..sdkVersion..'"}' +-- end +-- payload = payload.."}" +-- P.publish("firmware", payload) + + print("init done") +end + +return P diff --git a/diller/mq.lua b/diller/mq.lua new file mode 100644 index 0000000..0b4a884 --- /dev/null +++ b/diller/mq.lua @@ -0,0 +1,61 @@ +local P = {} +local m, topic, cid + +local function p(msg) + print("MQTT: "..msg) +end + +local function mq_client_connected(con) + p("connected") +end + +function mq_on_timer() + -- This crashes the module: + -- local status = wifi.sta.status() + -- p("checking status, status="..status) + + local status = wifi.sta.getip() + + if status and status ~= "0.0.0.0" then + if not m then + p("connecting") + + -- client id, keepalive, username, password + m = mqtt.Client(cid, 120) + -- m:on("connect", mq_connected) + m:on("offline", function() m:close(); m = nil end) + + -- host, port, secure, auto_reconnect, function(client), ssl=8883 + m:connect("trygvis.io", 1883, 0, 0, mq_client_connected) + end + else + if m then + p("Lost wifi connection, disconnecting") + m:close() + m = nil + end + end +end + +function P.init(timer_id, client_id) + cid = client_id + + topic = "/esp8266/"..client_id + + tmr.alarm(timer_id, 3 * 1000, 1, mq_on_timer) +end + +function P.publish(path, payload) + if not m then + print("Not connected, dropping message to "..path) + return + end + + path = topic.."/"..path + print("path="..path) + m:publish(path, payload, 0, 0) + + return true, "yo?" +end + +return P -- cgit v1.2.3