Skip to content

Commit

Permalink
Merge pull request #101 from konnected-io/aws-iot
Browse files Browse the repository at this point in the history
2.3.0: Cloud Connectivity, OTA Updates, foundational support for MQTT
  • Loading branch information
heythisisnate authored Aug 12, 2019
2 parents a85ed93 + 6a52291 commit 19848d7
Show file tree
Hide file tree
Showing 25 changed files with 634 additions and 164 deletions.
2 changes: 1 addition & 1 deletion firmware/2.2.0/app/include/user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
#define CLIENT_SSL_ENABLE
//#define MD2_ENABLE
#define SHA2_ENABLE
#define SSL_BUFFER_SIZE 4196
#define SSL_BUFFER_SIZE 5376


// GPIO_INTERRUPT_ENABLE needs to be defined if your application uses the
Expand Down
12 changes: 6 additions & 6 deletions firmware/2.2.0/app/include/user_modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
//#define LUA_USE_MODULES_ADXL345
//#define LUA_USE_MODULES_AM2320
//#define LUA_USE_MODULES_APA102
//#define LUA_USE_MODULES_BIT
#define LUA_USE_MODULES_BIT
//#define LUA_USE_MODULES_BLOOM
//#define LUA_USE_MODULES_BMP085
//#define LUA_USE_MODULES_BME280
//#define LUA_USE_MODULES_BME680
//#define LUA_USE_MODULES_COAP
//#define LUA_USE_MODULES_COLOR_UTILS
//#define LUA_USE_MODULES_CRON
//#define LUA_USE_MODULES_CRYPTO
#define LUA_USE_MODULES_CRYPTO
#define LUA_USE_MODULES_DHT
#define LUA_USE_MODULES_DS18B20
//#define LUA_USE_MODULES_ENCODER
Expand All @@ -37,7 +37,7 @@
//#define LUA_USE_MODULES_L3G4200D
//#define LUA_USE_MODULES_MCP4725
//#define LUA_USE_MODULES_MDNS
//#define LUA_USE_MODULES_MQTT
#define LUA_USE_MODULES_MQTT
#define LUA_USE_MODULES_NET
#define LUA_USE_MODULES_NODE
//#define LUA_USE_MODULES_OW
Expand All @@ -49,11 +49,11 @@
//#define LUA_USE_MODULES_ROTARY
//#define LUA_USE_MODULES_RTCFIFO
//#define LUA_USE_MODULES_RTCMEM
//#define LUA_USE_MODULES_RTCTIME
#define LUA_USE_MODULES_RTCTIME
//#define LUA_USE_MODULES_SI7021
//#define LUA_USE_MODULES_SIGMA_DELTA
#define LUA_USE_MODULES_SJSON
//#define LUA_USE_MODULES_SNTP
#define LUA_USE_MODULES_SNTP
//#define LUA_USE_MODULES_SOMFY
//#define LUA_USE_MODULES_SPI
//#define LUA_USE_MODULES_SQLITE3
Expand All @@ -67,7 +67,7 @@
#define LUA_USE_MODULES_UART
//#define LUA_USE_MODULES_U8G2
//#define LUA_USE_MODULES_UCG
//#define LUA_USE_MODULES_WEBSOCKET
#define LUA_USE_MODULES_WEBSOCKET
#define LUA_USE_MODULES_WIFI
//#define LUA_USE_MODULES_WIFI_MONITOR
//#define LUA_USE_MODULES_WPS
Expand Down
4 changes: 2 additions & 2 deletions firmware/2.2.0/app/include/user_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
#define NODE_VERSION_STR(x) #x
#define NODE_VERSION_XSTR(x) NODE_VERSION_STR(x)

#define NODE_VERSION "Konnected firmware 2.2.7\r\nNodeMCU " ESP_SDK_VERSION_STRING "." NODE_VERSION_XSTR(NODE_VERSION_INTERNAL)
#define NODE_VERSION "Konnected firmware 2.3.0\r\nNodeMCU " ESP_SDK_VERSION_STRING "." NODE_VERSION_XSTR(NODE_VERSION_INTERNAL)

#ifndef BUILD_DATE
#define BUILD_DATE "20190424"
#define BUILD_DATE "20190805"
#endif

extern char SDK_VERSION[];
Expand Down
Binary file added firmware/konnected-filesystem-0x100000-2-3-0.img
Binary file not shown.
Binary file added firmware/konnected-firmware-2-3-0.bin
Binary file not shown.
9 changes: 5 additions & 4 deletions scripts/build-firmware
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/bin/bash

FIRMWARE_PATH=${PWD}/../nodemcu-firmware
IMAGE_NAME=konnected-firmware-2-2-7
IMAGE_NAME=konnected-firmware-2-3-0

# Copy firmware configuration from this repository to the nodemcu-firmware repo
cp firmware/2.2.0/app/include/* $FIRMWARE_PATH/app/include/

rm $FIRMWARE_PATH/local/fs/*

# Build NodeMCU firmware image
Build NodeMCU firmware image
docker run -e "INTEGER_ONLY=1" \
-e "IMAGE_NAME=${IMAGE_NAME}" \
--rm -ti -v $FIRMWARE_PATH:/opt/nodemcu-firmware marcelstoer/nodemcu-build build
Expand All @@ -18,7 +18,8 @@ docker run -e "IMAGE_NAME=lfs" \
--rm -ti -v $FIRMWARE_PATH:/opt/nodemcu-firmware \
-v ${PWD}/src/lfs:/opt/lua marcelstoer/nodemcu-build lfs-image

mv src/lfs/LFS_float_lfs.img src/lfs/lfs.img
mv src/lfs/LFS_integer_lfs.img src/lfs/lfs.img
rm src/lfs/LFS_float_lfs.img

# Build SPIFFS image
docker run \
Expand All @@ -27,4 +28,4 @@ docker run \
-v ${PWD}/src:/opt/lua marcelstoer/nodemcu-build bash /scripts/build-spiffs

cp ${FIRMWARE_PATH}/bin/nodemcu_integer_${IMAGE_NAME}.bin firmware/${IMAGE_NAME}.bin
cp ${FIRMWARE_PATH}/bin/konnected-filesystem-0x100000.img firmware/konnected-filesystem-0x100000-2-2-7.img
cp ${FIRMWARE_PATH}/bin/konnected-filesystem-0x100000.img firmware/konnected-filesystem-0x100000-2-3-0.img
4 changes: 2 additions & 2 deletions scripts/flash
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

FIRMWARE_NAME=konnected-firmware-2-2-7.bin
FILESYSTEM_NAME=konnected-filesystem-0x100000-2-2-7.img
FIRMWARE_NAME=konnected-firmware-2-3-0.bin
FILESYSTEM_NAME=konnected-filesystem-0x100000-2-3-0.img
PORT=/dev/cu.wchusbserial1410

esptool.py --port=${PORT} write_flash --flash_mode dio 0x00000 firmware/${FIRMWARE_NAME}
Expand Down
Binary file modified src/http_index.html.gz
Binary file not shown.
101 changes: 15 additions & 86 deletions src/lfs/application.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ local dht_sensors = require("dht_sensors")
local ds18b20_sensors = require("ds18b20_sensors")
local actuators = require("actuators")
local settings = require("settings")
local sensorPut = {}
local actuatorGet = {}
local dni = wifi.sta.getmac():gsub("%:", "")
local timeout = tmr.create()
local sensorTimer = tmr.create()
local sendTimer = tmr.create()

timeout:register(10000, tmr.ALARM_SEMI, node.restart)
-- globals
sensorPut = {}
actuatorGet = {}

-- initialize binary sensors
for i, sensor in pairs(sensors) do
Expand Down Expand Up @@ -88,87 +85,19 @@ sensorTimer:alarm(200, tmr.ALARM_AUTO, function(t)
end
end)

-- print HTTP status line
local printHttpResponse = function(code, data)
local a = { "Heap:", node.heap(), "HTTP Call:", code }
for k, v in pairs(data) do
table.insert(a, k)
table.insert(a, v)
end
print(unpack(a))

-- Support different communication methods for reporting to upstream platforms
local endpoint_type = settings.endpoint_type or 'rest'

-- REST is the default communication method and is used by the original SmartThings, Hubitat, Home Assistant,
-- and OpenHab integrations.
if endpoint_type == 'rest' then
require("rest_endpoint")(settings)

-- AWS IoT is used for the Konnected Cloud Connector or custom integrations build on AWS
elseif endpoint_type == 'aws_iot' then
require("aws_iot")(settings)
end

-- This loop makes the HTTP requests to the home automation service to get or update device state
sendTimer:alarm(200, tmr.ALARM_AUTO, function(t)

-- gets state of actuators
if actuatorGet[1] then
t:stop()
local actuator = actuatorGet[1]
timeout:start()

http.get(table.concat({ settings.apiUrl, "/device/", dni, '?pin=', actuator.pin }),
table.concat({ "Authorization: Bearer ", settings.token, "\r\nAccept: application/json\r\n" }),
function(code, response)
timeout:stop()
local pin, state, json_response, status
if response and code >= 200 and code < 300 then
status, json_response = pcall(function() return sjson.decode(response) end)
if status then
pin = tonumber(json_response.pin)
state = tonumber(json_response.state)
end
end
printHttpResponse(code, {pin = pin, state = state})

gpio.mode(actuator.pin, gpio.OUTPUT)
if pin == actuator.pin and code >= 200 and code < 300 and state then
gpio.write(actuator.pin, state)
else
state = actuator.trigger == gpio.LOW and gpio.HIGH or gpio.LOW
gpio.write(actuator.pin, state)
end
print("Heap:", node.heap(), "Initialized actuator Pin:", actuator.pin, "Trigger:", actuator.trigger, "Initial state:", state)

table.remove(actuatorGet, 1)
blinktimer:start()
t:start()
end)

-- update state of sensors when needed
elseif sensorPut[1] then
t:stop()
local sensor = sensorPut[1]
timeout:start()
http.put(table.concat({ settings.apiUrl, "/device/", dni }),
table.concat({ "Authorization: Bearer ", settings.token, "\r\nAccept: application/json\r\nContent-Type: application/json\r\n" }),
sjson.encode(sensor),
function(code)
timeout:stop()
printHttpResponse(code, sensor)

-- check for success and retry if necessary
if code >= 200 and code < 300 then
table.remove(sensorPut, 1)
else
-- retry up to 10 times then reboot as a failsafe
local retry = sensor.retry or 0
if retry == 10 then
print("Heap:", node.heap(), "Retried 10 times and failed. Rebooting in 30 seconds.")
for k,v in pairs(sensorPut) do sensorPut[k]=nil end -- remove all pending sensor updates
tmr.create():alarm(30000, tmr.ALARM_SINGLE, function() node.restart() end) -- reboot in 30 sec
else
sensor.retry = retry + 1
sensorPut[1] = sensor
end
end

blinktimer:start()
t:start()
end)
end

collectgarbage()
end)

print("Heap:", node.heap(), "Endpoint:", settings.apiUrl)
94 changes: 94 additions & 0 deletions src/lfs/aws_iot.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
local module = ...

local mqtt = require('mqtt_ws')
local device_id = wifi.sta.getmac():lower():gsub(':','')
local c = mqtt.Client()

local function aws_sign_url(settings)
local aws_sig = require('aws_sig')
local url = aws_sig.createSignature(
settings.aws.access_key, settings.aws.secret_key,
settings.aws.region, 'iotdevicegateway', 'GET', settings.endpoint, '')

aws_sig = nil
package.loaded.aws_sig = nil
return url
end

local sendTimer = tmr.create()
local timeout = tmr.create()

timeout:register(3000, tmr.ALARM_SEMI, function()
sensorPut[1].retry = (sensorPut[1].retry or 0) + 1
sensorPut[1].message_id = nil
sendTimer:start()
end)

sendTimer:register(200, tmr.ALARM_AUTO, function(t)
local sensor = sensorPut[1]
if sensor then
t:stop()

if sensor.retry and sensor.retry > 0 then
print("Heap:", node.heap(), "Retry:", sensor.retry)
end

if sensor.retry and sensor.retry > 10 then
print("Heap:", node.heap(), "Retried 10 times and failed. Rebooting in 30 seconds.")
for k, v in pairs(sensorPut) do sensorPut[k] = nil end -- remove all pending sensor updates
tmr.create():alarm(30000, tmr.ALARM_SINGLE, function() node.restart() end) -- reboot in 30 sec
else
local message_id = c.msg_id
local topic = "konnected/" .. device_id .. "/sensor/" .. sensor.pin
print("Heap:", node.heap(), "PUBLISH", "Message ID:", message_id, "Topic:", topic, "Payload:", sjson.encode(sensor))
timeout:start()
c:publish(topic, sensor)
sensor.message_id = message_id
end
end
end)

local function startLoop(settings)
print("Heap:", node.heap(), 'Connecting to AWS IoT Endpoint:', settings.endpoint)

c:on('offline', function()
print("Heap:", node.heap(), "mqtt: offline")
sendTimer:stop()
c:connect(aws_sign_url(settings))
end)

c:connect(aws_sign_url(settings))
end

c:on('puback', function(_, message_id)
local sensor = sensorPut[1]
if sensor.message_id == message_id then
print("Heap:", node.heap(), 'PUBACK', 'Message ID:', message_id)
table.remove(sensorPut, 1)
blinktimer:start()
timeout:stop()
sendTimer:start()
end
end)

c:on('message', function(_, topic, message)
print("Heap:", node.heap(), 'topic:', topic, 'msg:', message)
local payload = sjson.decode(message)
require("switch")(payload)
end)

c:on('connect', function()
print("Heap:", node.heap(), "mqtt: connected")
for i, actuator in pairs(actuatorGet) do
local topic = "konnected/" .. device_id .. "/switch/" .. actuator.pin
print("Heap:", node.heap(), "Subscribing to topic:", topic)
c:subscribe(topic)
end
sendTimer:start()
end)

return function(settings)
package.loaded[module] = nil
module = nil
return startLoop(settings)
end
56 changes: 56 additions & 0 deletions src/lfs/aws_sig.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
local module = {}
local function sign(key, msg)
return crypto.hmac('SHA256', msg, key)
end

local function getSignatureKey(key, dateStamp, regionName, serviceName)
local kDate = sign('AWS4' .. key, dateStamp)
local kRegion = sign(kDate, regionName)
local kService = sign(kRegion, serviceName)
return sign(kService, 'aws4_request')
end

local function url_quote(text)
return string.gsub(text, "([^%w_%-~%.])", function(c) return string.format("%%%02X", string.byte(c)) end)
end

local function createSignature(aws_access_key, aws_secret_key, region, service, method, url, payload)
local t = rtctime.epoch2cal(rtctime.get())
local amz_date = string.format("%04d%02d%02dT%02d%02d%02dZ", t['year'], t['mon'], t['day'], t['hour'], t['min'], t['sec'])
local datestamp = amz_date:sub(1,8)

local protocol, host, path
protocol, host, path = string.match(url, "(%w+)://([^/]+)(.*)")
path = path or '/'

local canonical_headers = 'host:' .. host .. ':443\n'
local signed_headers = 'host'

local credential_scope = datestamp .. '/' .. region .. '/' .. service .. '/' .. 'aws4_request'

local canonical_querystring = (
'X-Amz-Algorithm=AWS4-HMAC-SHA256' ..
'&X-Amz-Credential=' .. url_quote(aws_access_key .. '/' .. credential_scope) ..
'&X-Amz-Date=' .. amz_date ..
'&X-Amz-Expires=86400' ..
'&X-Amz-SignedHeaders=' .. signed_headers
)
local payload_hash = 'UNSIGNED-PAYLOAD'
if payload ~= nil then
payload_hash = crypto.toHex(crypto.hash('SHA256', payload))
end
local canonical_request = (
method .. '\n' .. path .. '\n' ..
canonical_querystring .. '\n' .. canonical_headers .. '\n' ..
signed_headers .. '\n' .. payload_hash
)
-- print('canonical_request', canonical_request)
local string_to_sign = 'AWS4-HMAC-SHA256\n' .. amz_date .. '\n' .. credential_scope .. '\n' .. crypto.toHex(crypto.hash('SHA256', canonical_request))

local signing_key = getSignatureKey(aws_secret_key, datestamp, region, service)
local signature = crypto.toHex(crypto.hmac('SHA256', string_to_sign, signing_key))

return protocol .. '://' .. host .. path .. '?' .. canonical_querystring .. '&X-Amz-Signature=' .. signature
end
module.createSignature = createSignature
return module
4 changes: 2 additions & 2 deletions src/lfs/device.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
local me = {
id = "uuid:8f655392-a778-4fee-97b9-4825918" .. string.format("%x", node.chipid()),
name = "Konnected",
hwVersion = "2.2.7",
swVersion = "2.2.7",
hwVersion = "2.3.0",
swVersion = "2.3.0",
http_port = math.floor(node.chipid()/1000) + 8000,
urn = "urn:schemas-konnected-io:device:Security:1"
}
Expand Down
Loading

0 comments on commit 19848d7

Please sign in to comment.