Skip to content

Instantly share code, notes, and snippets.

Created March 13, 2017 22:17
Show Gist options
  • Select an option

  • Save anonymous/3f9294813fe3a0feff49e16d1377de21 to your computer and use it in GitHub Desktop.

Select an option

Save anonymous/3f9294813fe3a0feff49e16d1377de21 to your computer and use it in GitHub Desktop.
-- ###################################
-- Configuration and UI
-- ###################################
local Config, WifiConfig = dofile("Config.lua")
dofile("TelnetSrv.lua")
-- ###################################
-- System variables
-- ###################################
Tokens = nil
BillingToken = nil
BillingLatch = false
OperatorToken = nil
TokenAuthenticated = false
SpindleRunning = false
TemperatureOkay = false
FeedHold = true
EmergencyStop = true
Temperatures = {}
local MQTT, TokenTimer, TickTimer
local CNC_ESTOP, CNC_FEEDHOLD, CNC_SPINDLE, CNC_BILLTOKEN = "CNC/EmergencyStop", "CNC/FeedHold", "CNC/Spindle", "CNC/BillingToken"
local CNC_OPERTOKEN, CNC_TEMPOKAY, CNC_AUTHED, CNC_BILL = "CNC/OperatorToken", "CNC/TemperatureOkay", "CNC/Authenticated", "CNC/BillToken"
local CNC_TRUE, CNC_FALSE = "true", "false"
-- ###################################
-- Event Handlers
-- ###################################
function ReadSerial(a)
a = a:sub(2, 13)
ValidateTokenCode(a, HandleToken)
end
local function Tick()
-- Bill the user if the spindle is running, or -has- been running since they auth'd
if BillingToken and (SpindleRunning or BillingLatch) then
Tokens[BillingToken] = Tokens[BillingToken] - 1
if Tokens[BillingToken] % 60 == 0 then
Notify(CNC_BILL, BillingToken) --TODO this should probably be done over HTTP(s) or something instead
end
BillingLatch = true
end
if TokenAuthenticated and OperatorToken and TemperatureOkay then
-- If everything is okay to go, then allow the machine to run
gpio.write(4, gpio.LOW)
gpio.write(5, gpio.LOW)
FeedHold = false
EmergencyStop = false
Notify(CNC_FEEDHOLD, CNC_FALSE)
Notify(CNC_ESTOP, CNC_FALSE)
else
if SpindleRunning and TemperatureOkay then
-- If the token was removed, but the spindle is still going (and we aren't overheating)
if not FeedHold then
Notify(CNC_FEEDHOLD, CNC_TRUE) -- Notify MQTT
end
gpio.write(5, gpio.HIGH) -- Engage Feed-hold
FeedHold = true
else
-- If the spindle is stopped, OR if the temperature went too high,
-- immediately engage e-stop.
if not EmergencyStop then
Notify(CNC_ESTOP, CNC_TRUE) -- Notify MQTT
end
gpio.write(4, gpio.HIGH) -- Engage E-Stop
EmergencyStop = true
end
end
CheckTemperature()
end
-- When the spindle starts/stops (dry contact from VFD), this is called
local function SpindleStateChanged(State, when)
SpindleRunning = not State
Notify(CNC_SPINDLE, tostring(State))
end
-- If no token has been read for 30 seconds, this is called
local function TokenTimeout()
-- If the spindle is running, clear the operator token (this will engage feed-hold in Tick())
if SpindleRunning then
OperatorToken = nil
Notify(CNC_OPERTOKEN, "")
else
-- If the spindle is not running, lock out the machine
DeAuthenticate()
end
end
-- ###################################
-- Token Management
-- ###################################
local function AuthorizedOperator(Token)
Tokens[Token:lower()] > 0
end
local function HandleToken(Token)
-- If we're already "authenticated", then see if we have an operator change
if TokenAuthenticated and AuthorizedOperator(Token)then
-- First, reset the "token timeout" for feed-hold / e-stop
TokenTimer:stop()
TokenTimer:start()
-- Now see if the operator has changed
Token = Token:lower()
if Token ~= OperatorToken then
OperatorToken = Token
Notify(CNC_OPERTOKEN, OperatorToken)
end
-- If we're not authenticated (that is, we're locked out), then see if we can unlock the machine for the user
else
Authenticate(Token)
end
end
-- Authenticate a user and mark them as the "billed" user
local function Authenticate(TokenCode)
if AuthorizedOperator(TokenCode) then
BillingToken = TokenCode:lower()
OperatorToken = BillingToken
Notify(CNC_AUTHED, CNC_TRUE)
Notify(CNC_BILLTOKEN, TokenCode)
Notify(CNC_OPERTOKEN, OperatorToken)
TokenTimer:start()
TokenAuthenticated = true
end
end
-- Deauthenticate users and flag for machine shutdown
function DeAuthenticate()
BillingToken = nil
BillingLatch = false
TokenAuthenticated = false
Notify(CNC_AUTHED, CNC_FALSE)
Notify(CNC_BILLTOKEN, "")
Notify(CNC_OPERTOKEN, "")
end
-- Load token data from CSV file in SPIFFS
function LoadCSVData(Filename)
local CSVFile = file.open(Filename, "r")
local Line = CSVFile:readline()
Tokens = {}
repeat
local Split = Line:find(",")
if Split ~= nil then
Tokens[Line:sub(1, Split - 1):lower()] = tonumber(Line:sub(Split + 1))
Line = CSVFile:readline()
end
until Line == nil
CSVFile:close()
CSVFile = nil
end
function ValidateTokenCode(Input, ValidCallback)
local Data = {}
local i = 0
if #Input ~= 12 then
return false
end
for i = 1, 9, 2 do
table.insert(Data, tonumber(Input:sub(i, i + 1), 16))
end
local function Reverse(In)
local Out = 0
local I
for I = 0, 7 do
if bit.isset(In, I) then
Out = bit.set(Out, 7 - I)
end
end
return Out
end
-- Now checksum
local Checksum = tonumber(Input:sub(11), 16)
Checksum = bit.bxor(Checksum, Data[1], Data[2], Data[3], Data[4], Data[5])
if Checksum ~= 0 then
return false
end
-- Now convert
for i = 1, 5 do
Data[i] = string.format("%02x", Reverse(Data[i]))
end
ValidCallback(table.concat(Data, ""))
return true
end
-- ###################################
-- File Management
-- ###################################
local function UpdateTokens()
--Download(Config.File, Config.File, Config.Server, LoadCSVData)
end
function SaveData(Filename)
local CSVFile = file.open(Filename, "w")
for Token,Time in pairs(Tokens) do
CSVFile:write(Token .. "," .. tostring(Time) .. "\r\n")
end
CSVFile:close()
end
function Download(Filename, ToFile, Host, OnComplete, OnFail)
local Buffer, DLFile, conn, ParsingHeaders = nil, nil, net.createConnection(net.TCP, 0), true
local function StripHeaders(Data)
Buffer = Buffer .. Data
local T = Buffer:find("\r\n")
while T ~= nil do
Buffer = Buffer:sub(T + 2)
T = Buffer:find("\r\n")
if T == 1 then
ParsingHeaders = false
DLFile:write(Buffer:sub(3))
Buffer = nil
return
end
end
return nil
end
conn:on("receive", function(socket, Data)
if ParsingHeaders then
StripHeaders(Data)
else
DLFile:write(Data)
end
end)
conn:on("connection", function(socket, data)
ParsingHeaders = true
Buffer = ""
DLFile = file.open(ToFile, "w")
conn:send("GET /".. Filename .. " HTTP/1.1\r\nhost: " .. Host .. "\r\nconnection: close\r\nUser-Agent: NodeMCU Lua (CNC Router)\r\n\r\n")
end)
conn:on("disconnection", function(socket, data)
if DLFile ~= nil then
DLFile:close()
DLFile = nil
if OnComplete then
OnComplete()
end
elseif OnFail then
OnFail()
end
conn = nil
Buffer = nil
end)
conn:connect(80, Host)
end
-- ###################################
-- Temperature Checking
-- ###################################
local function CheckTemperature()
table.remove(Temperatures, Config.TempHist)
table.insert(Temperatures, 1, adc.read(0))
local Total = 0
local Count = 0
for i = 1, #Temperatures do
if Temperatures[i] > Config.ReadLowFilter and Temperatures[i] < Config.ReadHighFilter then -- Filter out invalid readings
Total = Total + Temperatures[i]
Count = Count + 1
end
end
if Count > 0 then
Total = Total / Count
local T = Total < Config.TempThreadhold
if TemperatureOkay ~= T then
Notify(CNC_TEMPOKAY, tostring(T))
end
TemperatureOkay = T
else
if TemperatureOkay then
Notify(CNC_TEMPOKAY, CNC_FALSE)
end
TemperatureOkay = false
end
end
-- ###################################
-- MQTT Functions
-- ###################################
local function Notify(Topic, Message)
MQTT:publish(Topic, Message, 1, 1)
end
-- ###################################
-- Startup and Initialization
-- ###################################
print("")
print("Initializing CNC router access control")
print("")
print("ADC Init...")
if adc.force_init_mode(adc.INIT_ADC) then
print("Restarting to complete ADC Init")
node.restart()
end
print("WiFi Init...")
wifi.setmode(wifi.STATION)
wifi.sta.config(WifiConfig)
print("GPIO Init...")
gpio.mode(4, gpio.OUTPUT)
gpio.write(4, gpio.HIGH)
gpio.mode(5, gpio.OUTPUT)
gpio.write(5, gpio.HIGH)
gpio.mode(1, gpio.INT, gpio.PULLUP)
gpio.trig(1, "both", SpindleStateChanged)
print("Timers Init...")
TokenTimer = tmr.create()
TokenTimer:register(Config.Timeout, tmr.ALARM_SEMI, TokenTimeout)
TickTimer = tmr.create()
TickTimer:register(1000, tmr.ALARM_AUTO, Tick)
TickTimer:start()
wifi.sta.eventMonReg(wifi.STA_GOTIP, function()
print("SNTP Sync...")
sntp.sync("pool.ntp.org")
print("Init telnet server...")
startTelnet()
print("Cache token data...")
UpdateTokens()
print("Load token data...")
LoadCSVData()
print("Init MQTT...")
MQTT = mqtt.Client(Config.MQTTId, 120)
MQTT:lwt("CNC/Status", "Offline")
MQTT:on("connect", function(c) Notify("CNC/Status", "Online") end)
MQTT:connect(Config.MQTTServer, Config.MQTTPort, 0)
print("Schedule automatic tasks...")
cron.schedule("0 0 * * *", UpdateTokens)
cron.schedule("0 */2 * * *", SaveData)
wifi.sta.eventMonStop()
print("UART Init - 9600 Baud...")
print("")
print("***APPLICATION STARTED***")
print("")
print("")
uart.on("data", '\003', ReadSerial, 0)
uart.setup(0, 9600, 8, uart.PARITY_NONE, uart.STOPBITS_1, 0)
end)
print("Waiting for WiFi start...")
wifi.sta.eventMonStart()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment