Unverified Commit 67027c0d authored by Marcel Stör's avatar Marcel Stör Committed by GitHub
Browse files

Merge pull request #2340 from nodemcu/dev

2.2 master snap
parents 5073c199 18f33f5f
--SAFETRIM
-- function _provision(self,socket,first_rec)
local self, socket, first_rec = ...
local crypto, file, json, node, table = crypto, file, sjson, node, table
local stripdebug, gc = node.stripdebug, collectgarbage
local buf = {}
gc(); gc()
local function getbuf() -- upval: buf, table
if #buf > 0 then return table.remove(buf, 1) end -- else return nil
end
-- Process a provisioning request record
local function receiveRec(socket, rec) -- upval: self, buf, crypto
-- Note that for 2nd and subsequent responses, we assme that the service has
-- "authenticated" itself, so any protocol errors are fatal and lkely to
-- cause a repeating boot, throw any protocol errors are thrown.
local buf, config, file, log = buf, self.config, file, self.log
local cmdlen = (rec:find('\n',1, true) or 0) - 1
local cmd,hash = rec:sub(1,cmdlen-6), rec:sub(cmdlen-5,cmdlen)
if cmdlen < 16 or
hash ~= crypto.toHex(crypto.hmac("MD5",cmd,self.secret):sub(-3)) then
return error("Invalid command signature")
end
local s; s, cmd = pcall(json.decode, cmd)
local action,resp = cmd.a, {s = "OK"}
local chunk
if action == "ls" then
for name,len in pairs(file.list()) do
resp[name] = len
end
elseif action == "mv" then
if file.exists(cmd.from) then
if file.exists(cmd.to) then file.remove(cmd.to) end
if not file.rename(cmd.from,cmd.to) then
resp.s = "Rename failed"
end
end
else
if action == "pu" or action == "cm" or action == "dl" then
-- These commands have a data buffer appended to the received record
if cmd.data == #rec - cmdlen - 1 then
buf[#buf+1] = rec:sub(cmdlen +2)
else
error(("Record size mismatch, %u expected, %u received"):format(
cmd.data or "nil", #buf - cmdlen - 1))
end
end
if action == "cm" then
stripdebug(2)
local lcf,msg = load(getbuf, cmd.name)
if not msg then
gc(); gc()
local code, name = string.dump(lcf), cmd.name:sub(1,-5) .. ".lc"
local s = file.open(name, "w+")
if s then
for i = 1, #code, 1024 do
s = s and file.write(code:sub(i, ((i+1023)>#code) and i+1023 or #code))
end
file.close()
if not s then file.remove(name) end
end
if s then
resp.lcsize=#code
print("Updated ".. name)
else
msg = "file write failed"
end
end
if msg then
resp.s, resp.err = "compile fail", msg
end
buf = {}
elseif action == "dl" then
local s = file.open(cmd.name, "w+")
if s then
for i = 1, #buf do
s = s and file.write(buf[i])
end
file.close()
end
if s then
print("Updated ".. name)
else
file.remove(name)
resp.s = "write failed"
end
buf = {}
elseif action == "ul" then
if file.open(cmd.name, "r") then
file.seek("set", cmd.offset)
chunk = file.read(cmd.len)
file.close()
end
elseif action == "restart" then
cmd.a = nil
cmd.secret = self.secret
file.open(self.prefix.."config.json", "w+")
file.writeline(json.encode(cmd))
file.close()
socket:close()
print("Restarting to load new application")
node.restart() -- reboot just schedules a restart
return
end
end
self.socket_send(socket, resp, chunk)
gc()
end
-- Replace the receive CB by the provisioning version and then tailcall this to
-- process this first record.
socket:on("receive", receiveRec)
return receiveRec(socket, first_rec)
--SAFETRIM
--------------------------------------------------------------------------------
-- LuaOTA provisioning system for ESPs using NodeMCU Lua
-- LICENCE: http://opensource.org/licenses/MIT
-- TerryE 15 Jul 2017
--
-- See luaOTA.md for description and implementation notes
--------------------------------------------------------------------------------
-- upvals
local crypto, file, json, net, node, table, tmr, wifi =
crypto, file, sjson, net, node, table, tmr, wifi
local error, pcall = error, pcall
local loadfile, gc = loadfile, collectgarbage
local concat, unpack = table.concat, unpack or table.unpack
local self = {post = node.task.post, prefix = "luaOTA/", conf = {}}
self.log = (DEBUG == true) and print or function() end
self.modname = ...
--------------------------------------------------------------------------------------
-- Utility Functions
setmetatable( self, {__index=function(self, func) --upval: loadfile
-- The only __index calls in in LuaOTA are dynamically loaded functions.
-- The convention is that functions starting with "_" are treated as
-- call-once / ephemeral; the rest are registered in self
func = self.prefix .. func
local f,msg = loadfile( func..".lc")
if msg then f, msg = loadfile(func..".lua") end
if msg then error (msg,2) end
if func:sub(8,8) ~= "_" then self[func] = f end
return f
end} )
function self.sign(arg) --upval: crypto, json, self
arg = json.encode(arg)
return arg .. crypto.toHex(crypto.hmac("MD5", arg, self.secret):sub(-3)) .. '\n'
end
function self.startApp(arg) --upval: gc, self, tmr, wifi
gc();gc()
tmr.unregister(0)
self.socket = nil
if not self.config.leave then wifi.setmode(wifi.NULLMODE,false) end
local appMod = self.config.app or "luaOTA.default"
local appMethod = self.config.entry or "entry"
if not arg then arg = "General timeout on provisioning" end
self.post(function() --upval: appMod, appMethod, arg
require(appMod)[appMethod](arg)
end)
end
function self.socket_send(socket, rec, opt_buffer)
return socket:send(self.sign(rec) .. (opt_buffer or ''))
end
self.post(function() -- upval: self
-- This config check is to prevent a double execution if the
-- user invokes with "require 'luaOTA/check':_init( etc>)" form
if not rawget(self, "config") then self:_init() end
end)
return self
{"leave":0,"port":8266,"ssid":"YourSID","spwd":"YourSSIDpwd","server":"your_server","secret":"yoursecret"}
--
local function enum(t,log) for k,v in pairs(t)do log(k,v) end end
return {entry = function(msg)
package.loaded["luaOTA.default"]=nil
local gc=collectgarbage; gc(); gc()
if DEBUG then
for k,v in pairs(_G) do print(k,v) end
for k,v in pairs(debug.getregistry()) do print(k,v) end
end
gc(); gc()
print(msg, node.heap())
end}
--------------------------------------------------------------------------------
-- LuaOTA provisioning system for ESPs using NodeMCU Lua
-- LICENCE: http://opensource.org/licenses/MIT
-- TerryE 15 Jul 2017
--
-- See luaOTA.md for description
--------------------------------------------------------------------------------
--[[ luaOTAserver.lua - an example provisioning server
This module implements an example server-side implementation of LuaOTA provisioning
system for ESPs used the SPI Flash FS (SPIFFS) on development and production modules.
This implementation is a simple TCP listener which can have one active provisioning
client executing the luaOTA module at a time. It will synchronise the client's FS
with the content of the given directory on the command line.
]]
local socket = require "socket"
local lfs = require "lfs"
local md5 = require "md5"
local json = require "cjson"
require "etc.strict" -- see http://www.lua.org/extras/5.1/strict.lua
-- Local functions (implementation see below) ------------------------------------------
local get_inventory -- function(root_directory, CPU_ID)
local send_command -- function(esp, resp, buffer)
local receive_and_parse -- function(esp)
local provision -- function(esp, config, files, inventory, fingerprint)
local read_file -- function(fname)
local save_file -- function(fname, data)
local compress_lua -- function(lua_file)
local hmac -- function(data)
-- Function-wide locals (can be upvalues)
local unpack = table.unpack or unpack
local concat = table.concat
local load = loadstring or load
local format = string.format
-- use string % operators as a synomyn for string.format
getmetatable("").__mod =
function(a, b)
return not b and a or
(type(b) == "table" and format(a, unpack(b)) or format(a, b))
end
local ESPport = 8266
local ESPtimeout = 15
local src_dir = arg[1] or "."
-- Main process ------------------------ do encapsulation to prevent name-clash upvalues
local function main ()
local server = assert(socket.bind("*", ESPport))
local ip, port = server:getsockname()
print("Lua OTA service listening on %s:%u\n After connecting, the ESP timeout is %u s"
% {ip, port, ESPtimeout})
-- Main loop forever waiting for ESP clients then processing each request ------------
while true do
local esp = server:accept() -- wait for ESP connection
esp:settimeout(ESPtimeout) -- set session timeout
-- receive the opening request
local config = receive_and_parse(esp)
if config and config.a == "HI" then
print ("Processing provision check from ESP-"..config.id)
local inventory, fingerprint = get_inventory(src_dir, config.id)
-- Process the ESP request
if config.chk and config.chk == fingerprint then
send_command(esp, {r = "OK!"}) -- no update so send_command with OK
esp:receive("*l") -- dummy receive to allow client to close
else
local status, msg = pcall(provision, esp, config, inventory, fingerprint)
if not status then print (msg) end
end
end
pcall(esp.close, esp)
print ("Provisioning complete")
end
end
-- Local Function Implementations ------------------------------------------------------
local function get_hmac_md5(key)
if key:len() > 64 then
key = md5.sum(key)
elseif key:len() < 64 then
key = key .. ('\0'):rep(64-key:len())
end
local ki = md5.exor(('\54'):rep(64),key)
local ko = md5.exor(('\92'):rep(64),key)
return function (data) return md5.sumhexa(ko..md5.sum(ki..data)) end
end
-- Enumerate the sources directory and load the relevent inventory
------------------------------------------------------------------
get_inventory = function(dir, cpuid)
if (not dir or lfs.attributes(dir).mode ~= "directory") then
error("Cannot open directory, aborting %s" % arg[0], 0)
end
-- Load the CPU's (or the default) inventory
local invtype, inventory = "custom", read_file("%s/ESP-%s.json" % {dir, cpuid})
if not inventory then
invtype, inventory = "default", read_file(dir .. "/default.json")
end
-- tolerate and remove whitespace formatting, then decode
inventory = (inventory or ""):gsub("[ \t]*\n[ \t]*","")
inventory = inventory:gsub("[ \t]*:[ \t]*",":")
local ok; ok,inventory = pcall(json.decode, inventory)
if ok and inventory.files then
print( "Loading %s inventory for ESP-%s" % {invtype, cpuid})
else
error( "Invalid inventory for %s :%s" % {cpuid,inventory}, 0)
end
-- Calculate the current fingerprint of the inventory
local fp,f = {},inventory.files
for i= 1,#f do
local name, fullname = f[i], "%s/%s" % {dir, f[i]}
local fa = lfs.attributes(fullname)
assert(fa, "File %s is required but not in sources directory" % name)
fp[#fp+1] = name .. ":" .. fa.modification
f[i] = {name = name, mtime = fa.modification,
size = fa.size, content = read_file(fullname) }
assert (f[i].size == #(f[i].content or ''), "File %s unreadable" % name )
end
assert(#f == #fp, "Aborting provisioning die to missing fies",0)
assert(type(inventory.secret) == "string",
"Aborting, config must contain a shared secret")
hmac = get_hmac_md5(inventory.secret)
return inventory, md5.sumhexa(concat(fp,":"))
end
-- Encode a response buff, add a signature and any optional buffer
------------------------------------------------------------------
send_command = function(esp, resp, buffer)
if type(buffer) == "string" then
resp.data = #buffer
else
buffer = ''
end
local rec = json.encode(resp)
rec = rec .. hmac(rec):sub(-6) .."\n"
-- print("requesting ", rec:sub(1,-2), #(buffer or ''))
esp:send(rec .. buffer)
end
-- Decode a response buff, check the signature and any optional buffer
----------------------------------------------------------------------
receive_and_parse = function(esp)
local line = esp:receive("*l")
local packed_cmd, sig = line:sub(1,#line-6),line:sub(-6)
-- print("reply:", packed_cmd, sig)
local status, cmd = pcall(json.decode, packed_cmd)
if not hmac or hmac(packed_cmd):sub(-6) == sig then
if cmd and cmd.data == "number" then
local data = esp:receive(cmd.data)
return cmd, data
end
return cmd
end
end
provision = function(esp, config, inventory, fingerprint)
if type(config.files) ~= "table" then config.files = {} end
local cf = config.files
for _, f in ipairs(inventory.files) do
local name, size, mtime, content = f.name, f.size, f.mtime, f.content
if not cf[name] or cf[name] ~= mtime then
-- Send the file
local func, action, cmd, buf
if f.name:sub(-4) == ".lua" then
assert(load(content, f.name)) -- check that the contents can compile
if content:find("--SAFETRIM\n",1,true) then
-- if the source is tagged with SAFETRIM then its safe to remove "--"
-- comments, leading and trailing whitespace. Not as good as LuaSrcDiet,
-- but this simple source compression algo preserves line numbering in
-- the generated lc files, which helps debugging.
content = content:gsub("\n[ \t]+","\n")
content = content:gsub("[ \t]+\n","\n")
content = content:gsub("%-%-[^\n]*","")
size = #content
end
action = "cm"
else
action = "dl"
end
print ("Sending file ".. name)
for i = 1, size, 1024 do
if i+1023 < size then
cmd = {a = "pu", data = 1024}
buf = content:sub(i, i+1023)
else
cmd = {a = action, data = size - i + 1, name = name}
buf = content:sub(i)
end
send_command(esp, cmd, buf)
local resp = receive_and_parse(esp)
assert(resp and resp.s == "OK", "Command to ESP failed")
if resp.lcsize then
print("Compiled file size %s bytes" % resp.lcsize)
end
end
end
cf[name] = mtime
end
config.chk = fingerprint
config.id = nil
config.a = "restart"
send_command(esp, config)
end
-- Load contents of the given file (or null if absent/unreadable)
-----------------------------------------------------------------
read_file = function(fname)
local file = io.open(fname, "rb")
if not file then return end
local data = file and file:read"*a"
file:close()
return data
end
-- Save contents to the given file
----------------------------------
save_file = function(fname, data)
local file = io.open(fname, "wb")
file:write(data)
file:close()
end
--------------------------------------------------------------------------------------
main() -- now that all functions have been bound to locals, we can start the show :-)
-- Test sjson and GitHub API
local s = tls.createConnection()
s:on("connection", function(sck, c)
sck:send("GET /repos/nodemcu/nodemcu-firmware/git/trees/master HTTP/1.0\r\nUser-agent: nodemcu/0.1\r\nHost: api.github.com\r\nConnection: close\r\nAccept: application/json\r\n\r\n")
end)
function startswith(String, Start)
return string.sub(String, 1, string.len(Start)) == Start
end
local seenBlank = false
local partial
local wantval = { tree = 1, path = 1, url = 1 }
-- Make an sjson decoder that only keeps certain fields
local decoder = sjson.decoder({
metatable =
{
__newindex = function(t, k, v)
if wantval[k] or type(k) == "number" then
rawset(t, k, v)
end
end
}
})
local function handledata(s)
decoder:write(s)
end
-- The receive callback is somewhat gnarly as it has to deal with find the end of the header
-- and having the newline sequence split across packets
s:on("receive", function(sck, c)
if partial then
c = partial .. c
partial = nil
end
if seenBlank then
handledata(c)
return
end
while c do
if startswith(c, "\r\n") then
seenBlank = true
c = c:sub(3)
handledata(c)
return
end
local s, e = c:find("\r\n")
if s then
-- Throw away line
c = c:sub(e + 1)
else
partial = c
c = nil
end
end
end)
local function getresult()
local result = decoder:result()
-- This gets the resulting decoded object with only the required fields
print(result['tree'][4]['path'], "is at",
result['tree'][4]['url'])
end
s:on("disconnection", getresult)
s:on("reconnection", getresult)
-- Make it all happen!
s:connect(443, "api.github.com")
......@@ -4,10 +4,11 @@ sda = 1
scl = 2
drdyn = false
HDC1000.init(sda, scl, drdyn)
i2c.setup(0, sda, scl, i2c.SLOW) -- call i2c.setup() only once
HDC1000.setup(drdyn)
HDC1000.config() -- default values are used if called with no arguments. prototype is config(address, resolution, heater)
print(string.format("Temperature: %.2f °C\nHumidity: %.2f %%", HDC1000.getTemp(), HDC1000.getHumi()))
HDC1000 = nil
package.loaded["HDC1000"]=nil
\ No newline at end of file
package.loaded["HDC1000"]=nil
......@@ -69,10 +69,9 @@ function M.batteryDead()
end
-- initalize i2c
function M.init(sda, scl, drdyn_pin)
-- setup i2c
function M.setup(drdyn_pin)
_drdyn_pin = drdyn_pin
i2c.setup(id, sda, scl, i2c.SLOW)
end
function M.config(addr, resolution, heater)
......
......@@ -10,7 +10,10 @@ First, require it:
Then, initialize it:
`HDC1000.init(sda, scl, drdyn)`
```lua
i2c.setup(0, sda, scl, i2c.SLOW) -- call i2c.setup() only once
HDC1000.setup(drdyn)
```
If you don't want to use the DRDYn pin, set it to false: a 20ms delay will be automatically set after each read request.
......
......@@ -16,16 +16,14 @@ LM92 = nil
package.loaded["lm92"]=nil
```
##init()
##setup()
####Description
Setting the i2c pins and address for lm92.
Setting the address for lm92.
####Syntax
init(sda, scl, address)
setup(sda, scl, address)
####Parameters
sda: 1~12, IO index.<br />
scl: 1~12, IO index.<br />
address: 0x48~0x4b, i2c address (depends on tha A0~A1 pins)
####Returns
nil
......@@ -38,7 +36,8 @@ gpio2 = 4
sda = gpio0
scl = gpio2
addr = 0x48
LM92.init(sda, scl,addr)
i2c.setup(0, sda, scl, i2c.SLOW) -- call i2c.setup() only once
LM92.setup(addr)
```
##getTemperature()
####Description
......@@ -251,7 +250,8 @@ gpio2 = 4
sda = gpio0
scl = gpio2
addr = 0x48
LM92.init(sda, scl,addr)
i2c.setup(0, sda, scl, i2c.SLOW) -- call i2c.setup() only once
LM92.setup(addr)
t = LM92.getTemperature()
print("Got temperature: "..t.." C")
......
......@@ -59,13 +59,9 @@ local function write_comp_reg(reg_addr, msb, lsb)
i2c.stop(id)
end
-- initialize i2c
-- d: sda
-- c: scl
-- a: i2c addr 0x48|A1<<1|A0 (A0-A1: chip pins)
function M.init(d,c,a)
if (d ~= nil) and (c ~= nil) and (d >= 0) and (d <= 11) and (c >= 0) and ( c <= 11) and (d ~= l) and (a ~= nil) and (a >= 0x48) and (a <= 0x4b ) then
sda = d
scl = c
function M.setup(a)
if (a ~= nil) and (a >= 0x48) and (a <= 0x4b ) then
address = a
i2c.start(id)
res = i2c.address(id, address, i2c.TRANSMITTER) --verify that the address is valid
......@@ -74,10 +70,9 @@ if (d ~= nil) and (c ~= nil) and (d >= 0) and (d <= 11) and (c >= 0) and ( c <=
print("device not found")
return nil
end
else
print("i2c configuration failed") return nil
end
i2c.setup(id,sda,scl,i2c.SLOW)
else
print("wrong i2c address") return nil
end
end
-- Return the temperature data
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment