This is an /etc/init.d/ script you can use on your Raspberry Pi, or other Linux machine, to auto start cloud9 at boot time.
#!/bin/bash
### BEGIN INIT INFO
# Provides: cloud9
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Simple script to start cloud9 at boot
# Description: A simple script which will start / stop cloud9 at boot / shutdown.
### END INIT INFO
# If you want a command to always run, put it here
# Carry out specific functions when asked to by the system
case "$1" in
start)
# echo "Starting noip"
# run application you want to start
cd /home/pi/c9sdk
sudo -u pi ./server.js -s standalone-homedir -l 0.0.0.0 -a : &
echo "Launching cloud9 with workspace root set to /home/pi"
;;
stop)
echo "Stopping cloud9"
# kill application you want to stop
pkill -f "node ./server.js"
;;
*)
echo "Usage: /etc/init.d/cloud9 {start|stop}"
exit 1
;;
esac
exit 0
ChiliPeppr
Wednesday, September 6, 2017
Tuesday, January 3, 2017
Latest Lua Code for Laser Cayenn Device
Please see new Github repo for latest code, as the code below is now outdated, but the description of how this project works is still valid.
https://github.com/chilipeppr/cayenn-laseruv
Here is the latest working code for a ChiliPeppr Cayenn device. The code consists of a main entry point called main_laseruv.lua. That file then loads all the supporting Lua files to create a complete Cayenn device.
The code lets you setup a NodeMCU to talk back to ChiliPeppr via the Cayenn protocol. The code does a few things:
https://github.com/chilipeppr/cayenn-laseruv
-- Main entry point for Laser UV Cayenn device
-- set freq to highest possible
node.setcpufreq(node.CPU160MHZ)
cayenn = require('cayenn_v3')
laser = require('laser_3amp_driver_v1')
cnc = require('tinyg_read_v3')
queue = require("queue")
led = require("led")
opts = {}
opts.Name = "LaserUV"
opts.Desc = "Control the BDR209 UV laser"
opts.Icon = "https://raw.githubusercontent.com/chilipeppr/widget-cayenn/master/laser.png"
opts.Widget = "com-chilipeppr-widget-laser"
-- opts.WidgetUrl = "https://github.com/chilipeppr/widget-laser/auto-generated.html"
-- define commands supported
cmds = {
"ResetCtr", "GetCtr", "GetCmds", "GetQ", "WipeQ", "CmdQ", "Mem",
"LaserBoot", "LaserShutdown",
'PwmOn {Hz,Duty} (Max Hz:1000, Max Duty:1023)', "PwmOff",
'PulseFor {ms}',
'MaxDuty {Duty}'
}
-- this is called by the cnc library when the coolant pin changes
function onCncCounter(counter)
print("Got CNC pin change. counter:" .. counter)
local cmd = queue.getId(counter)
onCmd(cmd)
end
-- this is called by Cayenn when an incoming cmd comes in
-- from the network, i.e. from SPJS. These are TCP commands so
-- they are guaranteed to come in (vs UDP which could drop)
function onCmd(payload)
if (type(payload) == "table") then
-- print("is json")
-- print("Got incoming Cayenn cmd JSON: " .. cjson.encode(payload))
-- See what cmd
if payload.Cmd == "LaserBoot" then
laser.relayOn()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(4)
-- print("Turned on relay to laser driver. Takes 3 secs to boot.")
elseif payload.Cmd == "LaserShutdown" then
laser.relayOff()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(2)
-- print("Turned off relay to laser driver")
elseif payload.Cmd == "MaxDuty" then
-- force a max duty to control laser power
laser.pwmSetMaxDuty(payload.Duty)
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["MaxDuty"] = payload.Duty})
led.blink(2)
elseif payload.Cmd == "PulseFor" then
-- should have been given milliseconds to pulse for
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(1)
-- run pulse after sending resp so no cpu is being used
-- so we get precise timing
laser.pulseFor(payload.ms)
-- print("Turned off relay to laser driver")
elseif payload.Cmd == "PwmOn" then
-- should have been given milliseconds to pulse for
led.blink(1)
if payload.Hz == nil or payload.Duty == nil then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz or Duty not specified"})
elseif 'number' ~= type(payload.Hz) or 'number' ~= type(payload.Duty) then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz or Duty not a number"})
elseif payload.Hz > 1000 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz " .. payload.Hz .. " too high", ["Hz"] = payload.Hz})
elseif payload.Hz <= 0 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz " .. payload.Hz .. " too low", ["Hz"] = payload.Hz})
elseif payload.Duty > 1023 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Duty " .. payload.Duty .. " too high"})
else
local actualDuty = laser.pwmOn(payload.Hz, payload.Duty)
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = "PwmOn", ["Hz"] = payload.Hz, ["Duty"] = actualDuty})
end
elseif payload.Cmd == "PwmOff" then
-- should have been given milliseconds to pulse for
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = "PwmOff"})
led.blink(1)
laser.pwmOff()
elseif payload.Cmd == "ResetCtr" then
cnc.resetIdCounter()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Ctr"] = cnc.getIdCounter()})
led.blink(1)
elseif payload.Cmd == "GetCtr" then
-- cnc.resetIdCounter()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Ctr"] = cnc.getIdCounter()})
led.blink(1)
elseif payload.Cmd == "GetCmds" then
local resp = {}
resp.Resp = "GetCmds"
resp.Cmds = cmds
resp.TransId = payload.TransId
-- resp.CmdsMeta = cmdsMeta
cayenn.sendBroadcast(resp)
led.blink(2)
elseif payload.Cmd == "GetQ" then
-- this method will send slowly as not to overwhelm
queue.send(cayenn.sendBroadcast, payload.TransId)
-- print("Sending queue back to network")
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Start"] = 0})
-- local resp = {}
-- local count = 0
-- resp.Resp = "GetQ"
-- -- loop and send multiple packets so we don't run out of mem
-- cmd = queue.getId(count)
-- while cmd ~= nil do
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Cmd"] = cmd.Cmd, ["Id"] = cmd.Id})
-- print(cmd)
-- resp.Queue = cmd
-- cayenn.sendBroadcast(resp)
-- count = count + 1
-- cmd = queue.getId(count)
-- led.blink(1)
-- end
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Q"] = queue.getTxt()})
led.blink(2)
elseif payload.Cmd == "WipeQ" then
-- queue = {}
-- print("Wiped queue: " .. cjson.encode(queue))
queue.wipe()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(1)
elseif payload.Cmd == "CmdQ" then
-- queuing cmd. we must have ID.
if payload.Id == nil then
-- print("Error queuing command. It must have an ID")
return
end
if payload.RunCmd == nil then
-- print("Error queuing command. It must have a RunCmd like RunCmd:{Cmd:AugerOn,Speed:10}.")
return
end
-- wipe the peerIp cuz don't need it
payload.peerIp = nil
-- print("Queing command")
--queue[payload.Id] = payload.RunCmd
payload.RunCmd.Id = payload.Id
queue.add(payload.RunCmd)
-- print("New queue: " .. cjson.encode(queue))
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Id"] = payload.Id, ["MemRemain"] = node.heap()})
led.blink(1)
elseif payload.Cmd == "Mem" then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["MemRemain"] = node.heap()})
led.blink(2)
elseif payload["Announce"] ~= nil then
-- do nothing.
if payload.Announce == "i-am-your-server" then
-- perhaps store this ip in future
-- so we know what our server is
end
else
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Unsupported cmd"})
-- print("Got cmd we do not understand. Huh?")
led.blink(1)
end
else
-- print("is string")
-- print("Got incoming Cayenn cmd. str: ", payload)
end
end
-- this callback is called when an incoming UDP broadcast
-- comes in to this device. typically this is just for
-- Cayenn Discover requests to figure out what devices are on
-- the network
function onIncomingBroadcast(cmd)
-- print("Got incoming UDP cmd: ", cmd)
if (type(cmd) == "table") then
if cmd["Cayenn"] ~= nil then
if cmd.Cayenn == "Discover" then
-- somebody is asking me to announce myself
cayenn.sendAnnounceBroadcast()
else
-- print("Do not understand incoming Cayenn cmd")
end
elseif cmd["Announce"] ~= nil then
if cmd.Announce == "i-am-your-server" then
-- we should store the server address so we can send
-- back TCP
-- print("Got a server announcement. Cool. Store it. TODO")
else
-- print("Got announce but not from a server. Huh?")
end
else
-- print("Do not understand incoming UDP cmd")
end
else
-- print("Got incoming UDP as string")
end
end
-- add listener to incoming cayenn commands
cayenn.addListenerOnIncomingCmd(onCmd)
cayenn.addListenerOnIncomingUdpCmd(onIncomingBroadcast)
cayenn.init(opts)
laser.init()
-- laser.pwmSetMaxDuty(100)
-- listen to coolant pin changes
cnc.addListenerOnIdChange(onCncCounter)
cnc.init()
led.blink(6)
print("Mem left:" .. node.heap())
-- Cayenn Protocol for ChiliPeppr
-- This module does udp/tcp sending/receiving to talk with SPJS
-- or have the browser talk direct to this ESP8266 device.
-- This module has methods for connecting to wifi, then initting
-- UDP servers, TCP servers, and sending a broadcast announce out.
-- The broadcast announce let's any listening SPJS know we're alive.
-- SPJS then reflects that message to any listening browsers like
-- ChiliPeppr so they know a device is available on the network.
-- Then the browser can send commands back to this device.
local M = {}
-- M = {}
M.port = 8988
M.myip = nil
M.sock = nil
M.jsonTagTable = nil
--M.announce =
M.isInitted = false
-- When you are initting you can pass in tags to describe your device
-- You should use a format like this:
-- cayenn.init({
-- widget: "com-chilipeppr-widget-ina219",
-- icon: "http://chilipeppr.com/img/ina219.jpg",
-- widgetUrl: "https://github.com/chilipeppr/widget-ina219/auto-generated.html",
-- })
function M.init(jsonTagTable)
-- save the jsonTagTable
if jsonTagTable ~= nil then
M.jsonTagTable = jsonTagTable
end
if M.isInitted then
print("Already initted")
return
end
print("Init...")
-- figure out if i have an IP
M.myip = wifi.sta.getip()
if M.myip == nil then
print("Connecting to wifi.")
M.setupWifi()
else
print("My IP: " .. M.myip)
M.isInitted = true
-- create socket for outbound UDP sending
M.sock = net.createConnection(net.UDP, 0)
-- create server to listen to incoming udp
M.initUdpServer()
-- create server to listen to incoming tcp
M.initTcpServer()
-- send our announce
M.sendAnnounceBroadcast(M.jsonTagTable)
end
end
function M.createAnnounce(jsonTagTable)
local a = {}
a.Announce = "i-am-a-client"
-- a.MyDeviceId = "chip:" .. node.chipid() .. "-flash:" .. node.flashid() .. "-mac:" .. wifi.sta.getmac()
a.MyDeviceId = "chip:" .. node.chipid() .. "-mac:" .. wifi.sta.getmac()
-- if jsonTagTable.Name then
-- a.Name = jsonTagTable.Name
-- -- jsonTagTable.Name = nil -- erase from table so not in jsonTags
-- elseif M.jsonTagTable.Name then
-- a.Name = M.jsonTagTable.Name
-- else
-- a.Name = "Unnamed"
-- end
-- if jsonTagTable.Desc then
-- a.Desc = jsonTagTable.Desc
-- -- jsonTagTable.Desc = nil -- erase from table so not in jsonTags
-- else
-- a.Desc = "(no desc provided)"
-- end
-- if jsonTagTable.Icon then
-- a.Icon = jsonTagTable.Icon
-- -- jsonTagTable.Icon = nil -- erase from table so not in jsonTags
-- else
-- a.Icon = "(no icon provided)"
-- end
if jsonTagTable.Widget then
a.Widget = jsonTagTable.Widget
-- jsonTagTable.Widget = nil
elseif M.jsonTagTable.Widget then
a.Widget = M.jsonTagTable.Widget
else
a.Widget = "com-chilipeppr-widget-undefined"
end
-- if jsonTagTable.WidgetUrl then
-- a.WidgetUrl = jsonTagTable.WidgetUrl
-- -- jsonTagTable.WidgetUrl = nil
-- else
-- a.WidgetUrl = "(no url specified)"
-- end
-- see if there is a jsontagtable passed in as extra meta
local jsontag = ""
if jsonTagTable then
ok, jsontag = pcall(cjson.encode, jsonTagTable)
if ok then
-- print("Adding jsontagtable" .. jsontag)
else
print("failed to encode jsontag!")
end
end
a.JsonTag = jsontag
local ok, json = pcall(cjson.encode, a)
if ok then
--print("Encoded json for announce: " .. json)
else
print("failed to encode json!")
end
print("Announce msg: " .. json)
return json
end
-- send announce to broadcast addr so spjs
-- knows of our existence
function M.sendAnnounceBroadcast(jsonTagTable)
if M.isInitted == false then
print("You must init first.")
return
end
local bip = wifi.sta.getbroadcast()
--print("Broadcast addr:" .. bip)
-- if there was no jsonTagTable passed in, then used
-- stored one
if not jsonTagTable then
jsonTagTable = M.jsonTagTable
end
print("Sending announce to ip: " .. bip)
M.sock:connect(M.port, bip)
M.sock:send(M.createAnnounce(jsonTagTable))
M.sock:close()
end
function M.sendBroadcast(jsonTagTable)
if M.isInitted == false then
print("You must init first.")
return
end
-- we need to attach deviceid
local a = {}
a.MyDeviceId = "chip:" .. node.chipid() .. "-mac:" .. wifi.sta.getmac()
-- see if there is a jsontagtable passed in as extra meta
local jsontag = ""
if jsonTagTable then
ok, jsontag = pcall(cjson.encode, jsonTagTable)
if ok then
-- print("Adding jsontagtable" .. jsontag)
else
print("failed to encode jsontag!")
end
end
a.JsonTag = jsontag
local ok, json = pcall(cjson.encode, a)
if ok then
--print("Encoded json for announce: " .. json)
else
print("failed to encode json!")
end
local bip = wifi.sta.getbroadcast()
--print("Broadcast addr:" .. bip)
-- local msg = cjson.encode(jsonTagTable)
-- local msg = cjson.encode(a)
print("Sending UDP msg: " .. json .. " to ip: " .. bip)
M.sock:connect(M.port, bip)
M.sock:send(json)
M.sock:close()
end
function M.setupWifi()
-- setwifi
wifi.setmode(wifi.STATION)
-- longest range is b
wifi.setphymode(wifi.PHYMODE_N)
--Connect to access point automatically when in range
-- for some reason digits in password seem to get mangled
-- so splitting them seems to solve problem
wifi.sta.config("NETGEAR-main", "(your password")
wifi.sta.connect()
--register callback
wifi.sta.eventMonReg(wifi.STA_IDLE, function() print("STATION_IDLE") end)
--wifi.sta.eventMonReg(wifi.STA_CONNECTING, function() print("STATION_CONNECTING") end)
wifi.sta.eventMonReg(wifi.STA_WRONGPWD, function() print("STATION_WRONG_PASSWORD") end)
wifi.sta.eventMonReg(wifi.STA_APNOTFOUND, function() print("STATION_NO_AP_FOUND") end)
wifi.sta.eventMonReg(wifi.STA_FAIL, function() print("STATION_CONNECT_FAIL") end)
wifi.sta.eventMonReg(wifi.STA_GOTIP, M.gotip)
--register callback: use previous state
wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_State)
if(previous_State==wifi.STA_GOTIP) then
print ("Reconnecting")
-- print("Station lost connection with access point\n\tAttempting to reconnect...")
else
print("STATION_CONNECTING")
end
end)
--start WiFi event monitor with default interval
wifi.sta.eventMonStart(1000)
end
function M.gotip()
print("STATION_GOT_IP")
M.myip = wifi.sta.getip()
print("My IP: " .. M.myip)
-- stop monitoring now since we're connected
wifi.sta.eventMonStop()
print("Stopped monitoring") -- wifi events since connected.")
-- make sure we are initted
M.init()
end
function M.initUdpServer()
M.udpServer = net.createServer(net.UDP)
--M.udpServer:on("connection", M.onUdpConnection)
M.udpServer:on("receive", M.onUdpRecv)
M.udpServer:listen(8988)
print("UDP Server started on port 8988")
end
function M.onUdpConnection(sck)
print("UDP connection.")
--ip, port = sck:getpeer()
--print("UDP connection. from: " .. ip)
end
function M.onUdpRecv(sck, data)
print("UDP Recvd. data: " .. data)
if (M.listenerOnIncomingUdpCmd) then
-- see if json
if string.sub(data,1,1) == "{" then
-- catch json errors
local succ, results = pcall(function()
return cjson.decode(data)
end)
-- see if we could parse
if succ then
data = results --cjson.decode(data)
-- data.peerIp = peer
else
print("Error parsing JSON")
return
end
end
M.listenerOnIncomingUdpCmd(data)
end
end
-- this property and method let an external object attach a
-- listener to the incoming UDP cmd
M.listenerOnIncomingUdpCmd = nil
function M.addListenerOnIncomingUdpCmd(listenerCallback)
M.listenerOnIncomingUdpCmd = listenerCallback
-- print("Attached listener to incoming UDP cmd")
end
function M.removeListenerOnIncomingUdpCmd(listenerCallback)
M.listenerOnIncomingUdpCmd = nil
-- print("Removed listener on incoming UDP cmd")
end
function M.initTcpServer()
M.tcpServer = net.createServer(net.TCP)
M.tcpServer:listen(8988, M.onTcpListen)
print("TCP Server started on port 8988")
end
function M.onTcpListen(conn)
conn:on("receive", M.onTcpRecv)
end
function M.onTcpConnection(sck)
print("TCP connection.")
--ip, port = sck:getpeer()
--print("UDP connection. from: " .. ip)
end
function M.onTcpRecv(sck, data)
local peer = sck:getpeer()
print("TCP Recvd. data: " .. data .. ", Peer:" .. peer)
if (M.listenerOnIncomingCmd) then
-- see if json
if string.sub(data,1,1) == "{" then
-- catch json errors
local succ, results = pcall(function()
return cjson.decode(data)
end)
-- see if we could parse
if succ then
data = results --cjson.decode(data)
data.peerIp = peer
else
print("Error parsing JSON")
return
end
end
M.listenerOnIncomingCmd(data)
end
end
-- this property and method let an external object attach a
-- listener to the incoming TCP command
M.listenerOnIncomingCmd = nil
function M.addListenerOnIncomingCmd(listenerCallback)
M.listenerOnIncomingCmd = listenerCallback
-- print("Attached listener to incoming TCP cmd")
end
function M.removeListenerOnIncomingCmd(listenerCallback)
M.listenerOnIncomingCmd = nil
-- print("Removed listener on incoming TCP cmd")
end
return M
--M.init()
-- cayenn = M
-- cayenn.init()
-- GOOD INIT
-- opts = {}
-- opts.Name = "Dispenser DMP16"
-- opts.Desc = "Techcon DMP16 auger with stepper and linear slide"
-- opts.Icon = "http://gds-storage-prd.s3.amazonaws.com/fusion-360/161021/1972/70f0aa71/thumbnails/raasrendering-bbf3b3d5-01c5-48e4-bbdb-f81727a520ab-160-160.jpg"
-- opts.Widget = "com-chilipeppr-widget-dispenser"
-- opts.WidgetUrl = "https://github.com/chilipeppr/widget-dispenser/auto-generated.html"
-- cayenn.init(opts)
local m = {}
-- m = {}
m.filename = "queue.txt"
m.tmrSend = 1
function m.wipe()
file.remove(m.filename)
file.open(m.filename, "w+")
-- file.writeline("")
file.close()
end
function m.add(payload)
-- open 'queue.txt' in 'a+' mode to append
file.open(m.filename, "a+")
local ok, jsontag = pcall(cjson.encode, payload)
if ok then
-- write to the end of the file
file.writeline(jsontag)
-- print("Wrote line to file:" .. jsontag)
else
-- print("failed to encode jsontag for queue file!")
end
file.close()
end
function m.getId(id)
-- if file.exists(m.filename) == false then
-- return nil
-- end
file.open(m.filename, "r")
-- read the # of lines by the id
local line
for ctr = 0, id do
line = file.readline()
-- print("Line:", id, ctr, line)
end
file.close()
-- print("Line:", line)
if line == nil then
return nil
end
-- parse json to table
local succ, results = pcall(function()
return cjson.decode(line)
end)
-- see if we could parse
if succ then
--data = results
return results
else
-- print("Error parsing JSON")
return nil
end
end
function m.getTxt()
file.open(m.filename, "r")
local txt = file.read()
file.close()
return txt
end
-- this method sends back data slowly
m.lastSendId = 0
m.callback = nil
m.transId = nil
function m.send(callback, transId)
-- reset ctr
m.lastSendId = 0
m.callback = callback
m.transId = transId
-- say we are starting
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Start"] = 0})
-- callback slowly and send q each time
tmr.alarm(m.tmrSend, 2, tmr.ALARM_SEMI, m.onSend)
end
function m.onSend()
-- get next line
local cmd = queue.getId(m.lastSendId)
if cmd ~= nil then
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Q"] = cmd})
m.lastSendId = m.lastSendId + 1
tmr.start(m.tmrSend)
else
-- we are done, cuz hit null
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Finish"] = m.lastSendId})
end
end
return m
-- use this module like
-- led = require("led")
-- led.on() -- turn on led
-- led.off() -- turn off led
-- led.blink(2) -- blink twice w/ 200ms delay
-- led.blink(6,100) -- blink 6 times w/ 100ms delay
-- led.blink(10,20) -- blink 10 times w/ 20ms delay
local led = {}
-- led = {}
led.pin = 4 -- this is GPIO2
led.timerId = 6 -- 0 thru 6 allowed. change to not conflict.
led.isOn = false
led.isInitted = false
function led.init()
-- setup led as output and turn on
gpio.mode(led.pin, gpio.OUTPUT)
gpio.write(led.pin, gpio.HIGH)
led.isOn = false
led.isInitted = true
end
function led.on()
if led.isInitted ~= true then
led.init()
end
gpio.write(4, gpio.LOW) --on
-- print("Led on")
end
function led.off()
if led.isInitted ~= true then
led.init()
end
gpio.write(4, gpio.HIGH) --off
-- print("Led off")
end
led.blinkTimes = 0
led.delay = 200
function led.blink(val, delay)
led.blinkTimes = val * 2
if delay ~= nil and delay > 0 then
led.delay = delay
end
tmr.alarm(led.timerId, led.delay, tmr.ALARM_AUTO, led.onTimer)
end
led.ctr = 0
function led.onTimer()
led.ctr = led.ctr + 1
if led.ctr > led.blinkTimes then
tmr.stop(led.timerId)
led.ctr = 0
led.off()
-- print("Done")
led.delay = 200 --reset delay to default
return
end
if gpio.read(4) == gpio.HIGH then
led.on()
else
led.off()
end
end
return led
dofile("main_laseruv.lc")
https://github.com/chilipeppr/cayenn-laseruv
Here is the latest working code for a ChiliPeppr Cayenn device. The code consists of a main entry point called main_laseruv.lua. That file then loads all the supporting Lua files to create a complete Cayenn device.
The code lets you setup a NodeMCU to talk back to ChiliPeppr via the Cayenn protocol. The code does a few things:
- Announces the existence of the device to your network so ChiliPeppr can see it
- Lets ChiliPeppr send commands to the device to control it
- Lets ChiliPeppr upload a set of commands with ID's from your pre-processed Gcode
- Sync your NodeMCU to your main CNC controller via the coolant on/off pin so as your Gcode executes, the NodeMCU will stay in sync and execute any relevant commands that were pre-uploaded at the exact time
- Send data back to ChiliPeppr during execution
The image below has 3 Cayenn devices in it, but this code is for the Laser 3A device.
main_laseruv.lua
(Please see Github project for latest version of this code, as code below is no longer current.)https://github.com/chilipeppr/cayenn-laseruv
-- Main entry point for Laser UV Cayenn device
-- set freq to highest possible
node.setcpufreq(node.CPU160MHZ)
cayenn = require('cayenn_v3')
laser = require('laser_3amp_driver_v1')
cnc = require('tinyg_read_v3')
queue = require("queue")
led = require("led")
opts = {}
opts.Name = "LaserUV"
opts.Desc = "Control the BDR209 UV laser"
opts.Icon = "https://raw.githubusercontent.com/chilipeppr/widget-cayenn/master/laser.png"
opts.Widget = "com-chilipeppr-widget-laser"
-- opts.WidgetUrl = "https://github.com/chilipeppr/widget-laser/auto-generated.html"
-- define commands supported
cmds = {
"ResetCtr", "GetCtr", "GetCmds", "GetQ", "WipeQ", "CmdQ", "Mem",
"LaserBoot", "LaserShutdown",
'PwmOn {Hz,Duty} (Max Hz:1000, Max Duty:1023)', "PwmOff",
'PulseFor {ms}',
'MaxDuty {Duty}'
}
-- this is called by the cnc library when the coolant pin changes
function onCncCounter(counter)
print("Got CNC pin change. counter:" .. counter)
local cmd = queue.getId(counter)
onCmd(cmd)
end
-- this is called by Cayenn when an incoming cmd comes in
-- from the network, i.e. from SPJS. These are TCP commands so
-- they are guaranteed to come in (vs UDP which could drop)
function onCmd(payload)
if (type(payload) == "table") then
-- print("is json")
-- print("Got incoming Cayenn cmd JSON: " .. cjson.encode(payload))
-- See what cmd
if payload.Cmd == "LaserBoot" then
laser.relayOn()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(4)
-- print("Turned on relay to laser driver. Takes 3 secs to boot.")
elseif payload.Cmd == "LaserShutdown" then
laser.relayOff()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(2)
-- print("Turned off relay to laser driver")
elseif payload.Cmd == "MaxDuty" then
-- force a max duty to control laser power
laser.pwmSetMaxDuty(payload.Duty)
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["MaxDuty"] = payload.Duty})
led.blink(2)
elseif payload.Cmd == "PulseFor" then
-- should have been given milliseconds to pulse for
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(1)
-- run pulse after sending resp so no cpu is being used
-- so we get precise timing
laser.pulseFor(payload.ms)
-- print("Turned off relay to laser driver")
elseif payload.Cmd == "PwmOn" then
-- should have been given milliseconds to pulse for
led.blink(1)
if payload.Hz == nil or payload.Duty == nil then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz or Duty not specified"})
elseif 'number' ~= type(payload.Hz) or 'number' ~= type(payload.Duty) then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz or Duty not a number"})
elseif payload.Hz > 1000 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz " .. payload.Hz .. " too high", ["Hz"] = payload.Hz})
elseif payload.Hz <= 0 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Hz " .. payload.Hz .. " too low", ["Hz"] = payload.Hz})
elseif payload.Duty > 1023 then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Duty " .. payload.Duty .. " too high"})
else
local actualDuty = laser.pwmOn(payload.Hz, payload.Duty)
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = "PwmOn", ["Hz"] = payload.Hz, ["Duty"] = actualDuty})
end
elseif payload.Cmd == "PwmOff" then
-- should have been given milliseconds to pulse for
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = "PwmOff"})
led.blink(1)
laser.pwmOff()
elseif payload.Cmd == "ResetCtr" then
cnc.resetIdCounter()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Ctr"] = cnc.getIdCounter()})
led.blink(1)
elseif payload.Cmd == "GetCtr" then
-- cnc.resetIdCounter()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Ctr"] = cnc.getIdCounter()})
led.blink(1)
elseif payload.Cmd == "GetCmds" then
local resp = {}
resp.Resp = "GetCmds"
resp.Cmds = cmds
resp.TransId = payload.TransId
-- resp.CmdsMeta = cmdsMeta
cayenn.sendBroadcast(resp)
led.blink(2)
elseif payload.Cmd == "GetQ" then
-- this method will send slowly as not to overwhelm
queue.send(cayenn.sendBroadcast, payload.TransId)
-- print("Sending queue back to network")
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Start"] = 0})
-- local resp = {}
-- local count = 0
-- resp.Resp = "GetQ"
-- -- loop and send multiple packets so we don't run out of mem
-- cmd = queue.getId(count)
-- while cmd ~= nil do
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Cmd"] = cmd.Cmd, ["Id"] = cmd.Id})
-- print(cmd)
-- resp.Queue = cmd
-- cayenn.sendBroadcast(resp)
-- count = count + 1
-- cmd = queue.getId(count)
-- led.blink(1)
-- end
-- cayenn.sendBroadcast({["Resp"] = payload.Cmd, ["Q"] = queue.getTxt()})
led.blink(2)
elseif payload.Cmd == "WipeQ" then
-- queue = {}
-- print("Wiped queue: " .. cjson.encode(queue))
queue.wipe()
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd})
led.blink(1)
elseif payload.Cmd == "CmdQ" then
-- queuing cmd. we must have ID.
if payload.Id == nil then
-- print("Error queuing command. It must have an ID")
return
end
if payload.RunCmd == nil then
-- print("Error queuing command. It must have a RunCmd like RunCmd:{Cmd:AugerOn,Speed:10}.")
return
end
-- wipe the peerIp cuz don't need it
payload.peerIp = nil
-- print("Queing command")
--queue[payload.Id] = payload.RunCmd
payload.RunCmd.Id = payload.Id
queue.add(payload.RunCmd)
-- print("New queue: " .. cjson.encode(queue))
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Id"] = payload.Id, ["MemRemain"] = node.heap()})
led.blink(1)
elseif payload.Cmd == "Mem" then
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["MemRemain"] = node.heap()})
led.blink(2)
elseif payload["Announce"] ~= nil then
-- do nothing.
if payload.Announce == "i-am-your-server" then
-- perhaps store this ip in future
-- so we know what our server is
end
else
cayenn.sendBroadcast({["TransId"] = payload.TransId, ["Resp"] = payload.Cmd, ["Err"] = "Unsupported cmd"})
-- print("Got cmd we do not understand. Huh?")
led.blink(1)
end
else
-- print("is string")
-- print("Got incoming Cayenn cmd. str: ", payload)
end
end
-- this callback is called when an incoming UDP broadcast
-- comes in to this device. typically this is just for
-- Cayenn Discover requests to figure out what devices are on
-- the network
function onIncomingBroadcast(cmd)
-- print("Got incoming UDP cmd: ", cmd)
if (type(cmd) == "table") then
if cmd["Cayenn"] ~= nil then
if cmd.Cayenn == "Discover" then
-- somebody is asking me to announce myself
cayenn.sendAnnounceBroadcast()
else
-- print("Do not understand incoming Cayenn cmd")
end
elseif cmd["Announce"] ~= nil then
if cmd.Announce == "i-am-your-server" then
-- we should store the server address so we can send
-- back TCP
-- print("Got a server announcement. Cool. Store it. TODO")
else
-- print("Got announce but not from a server. Huh?")
end
else
-- print("Do not understand incoming UDP cmd")
end
else
-- print("Got incoming UDP as string")
end
end
-- add listener to incoming cayenn commands
cayenn.addListenerOnIncomingCmd(onCmd)
cayenn.addListenerOnIncomingUdpCmd(onIncomingBroadcast)
cayenn.init(opts)
laser.init()
-- laser.pwmSetMaxDuty(100)
-- listen to coolant pin changes
cnc.addListenerOnIdChange(onCncCounter)
cnc.init()
led.blink(6)
print("Mem left:" .. node.heap())
cayenn_v3.lua
-- Cayenn Protocol for ChiliPeppr
-- This module does udp/tcp sending/receiving to talk with SPJS
-- or have the browser talk direct to this ESP8266 device.
-- This module has methods for connecting to wifi, then initting
-- UDP servers, TCP servers, and sending a broadcast announce out.
-- The broadcast announce let's any listening SPJS know we're alive.
-- SPJS then reflects that message to any listening browsers like
-- ChiliPeppr so they know a device is available on the network.
-- Then the browser can send commands back to this device.
local M = {}
-- M = {}
M.port = 8988
M.myip = nil
M.sock = nil
M.jsonTagTable = nil
--M.announce =
M.isInitted = false
-- When you are initting you can pass in tags to describe your device
-- You should use a format like this:
-- cayenn.init({
-- widget: "com-chilipeppr-widget-ina219",
-- icon: "http://chilipeppr.com/img/ina219.jpg",
-- widgetUrl: "https://github.com/chilipeppr/widget-ina219/auto-generated.html",
-- })
function M.init(jsonTagTable)
-- save the jsonTagTable
if jsonTagTable ~= nil then
M.jsonTagTable = jsonTagTable
end
if M.isInitted then
print("Already initted")
return
end
print("Init...")
-- figure out if i have an IP
M.myip = wifi.sta.getip()
if M.myip == nil then
print("Connecting to wifi.")
M.setupWifi()
else
print("My IP: " .. M.myip)
M.isInitted = true
-- create socket for outbound UDP sending
M.sock = net.createConnection(net.UDP, 0)
-- create server to listen to incoming udp
M.initUdpServer()
-- create server to listen to incoming tcp
M.initTcpServer()
-- send our announce
M.sendAnnounceBroadcast(M.jsonTagTable)
end
end
function M.createAnnounce(jsonTagTable)
local a = {}
a.Announce = "i-am-a-client"
-- a.MyDeviceId = "chip:" .. node.chipid() .. "-flash:" .. node.flashid() .. "-mac:" .. wifi.sta.getmac()
a.MyDeviceId = "chip:" .. node.chipid() .. "-mac:" .. wifi.sta.getmac()
-- if jsonTagTable.Name then
-- a.Name = jsonTagTable.Name
-- -- jsonTagTable.Name = nil -- erase from table so not in jsonTags
-- elseif M.jsonTagTable.Name then
-- a.Name = M.jsonTagTable.Name
-- else
-- a.Name = "Unnamed"
-- end
-- if jsonTagTable.Desc then
-- a.Desc = jsonTagTable.Desc
-- -- jsonTagTable.Desc = nil -- erase from table so not in jsonTags
-- else
-- a.Desc = "(no desc provided)"
-- end
-- if jsonTagTable.Icon then
-- a.Icon = jsonTagTable.Icon
-- -- jsonTagTable.Icon = nil -- erase from table so not in jsonTags
-- else
-- a.Icon = "(no icon provided)"
-- end
if jsonTagTable.Widget then
a.Widget = jsonTagTable.Widget
-- jsonTagTable.Widget = nil
elseif M.jsonTagTable.Widget then
a.Widget = M.jsonTagTable.Widget
else
a.Widget = "com-chilipeppr-widget-undefined"
end
-- if jsonTagTable.WidgetUrl then
-- a.WidgetUrl = jsonTagTable.WidgetUrl
-- -- jsonTagTable.WidgetUrl = nil
-- else
-- a.WidgetUrl = "(no url specified)"
-- end
-- see if there is a jsontagtable passed in as extra meta
local jsontag = ""
if jsonTagTable then
ok, jsontag = pcall(cjson.encode, jsonTagTable)
if ok then
-- print("Adding jsontagtable" .. jsontag)
else
print("failed to encode jsontag!")
end
end
a.JsonTag = jsontag
local ok, json = pcall(cjson.encode, a)
if ok then
--print("Encoded json for announce: " .. json)
else
print("failed to encode json!")
end
print("Announce msg: " .. json)
return json
end
-- send announce to broadcast addr so spjs
-- knows of our existence
function M.sendAnnounceBroadcast(jsonTagTable)
if M.isInitted == false then
print("You must init first.")
return
end
local bip = wifi.sta.getbroadcast()
--print("Broadcast addr:" .. bip)
-- if there was no jsonTagTable passed in, then used
-- stored one
if not jsonTagTable then
jsonTagTable = M.jsonTagTable
end
print("Sending announce to ip: " .. bip)
M.sock:connect(M.port, bip)
M.sock:send(M.createAnnounce(jsonTagTable))
M.sock:close()
end
function M.sendBroadcast(jsonTagTable)
if M.isInitted == false then
print("You must init first.")
return
end
-- we need to attach deviceid
local a = {}
a.MyDeviceId = "chip:" .. node.chipid() .. "-mac:" .. wifi.sta.getmac()
-- see if there is a jsontagtable passed in as extra meta
local jsontag = ""
if jsonTagTable then
ok, jsontag = pcall(cjson.encode, jsonTagTable)
if ok then
-- print("Adding jsontagtable" .. jsontag)
else
print("failed to encode jsontag!")
end
end
a.JsonTag = jsontag
local ok, json = pcall(cjson.encode, a)
if ok then
--print("Encoded json for announce: " .. json)
else
print("failed to encode json!")
end
local bip = wifi.sta.getbroadcast()
--print("Broadcast addr:" .. bip)
-- local msg = cjson.encode(jsonTagTable)
-- local msg = cjson.encode(a)
print("Sending UDP msg: " .. json .. " to ip: " .. bip)
M.sock:connect(M.port, bip)
M.sock:send(json)
M.sock:close()
end
function M.setupWifi()
-- setwifi
wifi.setmode(wifi.STATION)
-- longest range is b
wifi.setphymode(wifi.PHYMODE_N)
--Connect to access point automatically when in range
-- for some reason digits in password seem to get mangled
-- so splitting them seems to solve problem
wifi.sta.config("NETGEAR-main", "(your password")
wifi.sta.connect()
--register callback
wifi.sta.eventMonReg(wifi.STA_IDLE, function() print("STATION_IDLE") end)
--wifi.sta.eventMonReg(wifi.STA_CONNECTING, function() print("STATION_CONNECTING") end)
wifi.sta.eventMonReg(wifi.STA_WRONGPWD, function() print("STATION_WRONG_PASSWORD") end)
wifi.sta.eventMonReg(wifi.STA_APNOTFOUND, function() print("STATION_NO_AP_FOUND") end)
wifi.sta.eventMonReg(wifi.STA_FAIL, function() print("STATION_CONNECT_FAIL") end)
wifi.sta.eventMonReg(wifi.STA_GOTIP, M.gotip)
--register callback: use previous state
wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_State)
if(previous_State==wifi.STA_GOTIP) then
print ("Reconnecting")
-- print("Station lost connection with access point\n\tAttempting to reconnect...")
else
print("STATION_CONNECTING")
end
end)
--start WiFi event monitor with default interval
wifi.sta.eventMonStart(1000)
end
function M.gotip()
print("STATION_GOT_IP")
M.myip = wifi.sta.getip()
print("My IP: " .. M.myip)
-- stop monitoring now since we're connected
wifi.sta.eventMonStop()
print("Stopped monitoring") -- wifi events since connected.")
-- make sure we are initted
M.init()
end
function M.initUdpServer()
M.udpServer = net.createServer(net.UDP)
--M.udpServer:on("connection", M.onUdpConnection)
M.udpServer:on("receive", M.onUdpRecv)
M.udpServer:listen(8988)
print("UDP Server started on port 8988")
end
function M.onUdpConnection(sck)
print("UDP connection.")
--ip, port = sck:getpeer()
--print("UDP connection. from: " .. ip)
end
function M.onUdpRecv(sck, data)
print("UDP Recvd. data: " .. data)
if (M.listenerOnIncomingUdpCmd) then
-- see if json
if string.sub(data,1,1) == "{" then
-- catch json errors
local succ, results = pcall(function()
return cjson.decode(data)
end)
-- see if we could parse
if succ then
data = results --cjson.decode(data)
-- data.peerIp = peer
else
print("Error parsing JSON")
return
end
end
M.listenerOnIncomingUdpCmd(data)
end
end
-- this property and method let an external object attach a
-- listener to the incoming UDP cmd
M.listenerOnIncomingUdpCmd = nil
function M.addListenerOnIncomingUdpCmd(listenerCallback)
M.listenerOnIncomingUdpCmd = listenerCallback
-- print("Attached listener to incoming UDP cmd")
end
function M.removeListenerOnIncomingUdpCmd(listenerCallback)
M.listenerOnIncomingUdpCmd = nil
-- print("Removed listener on incoming UDP cmd")
end
function M.initTcpServer()
M.tcpServer = net.createServer(net.TCP)
M.tcpServer:listen(8988, M.onTcpListen)
print("TCP Server started on port 8988")
end
function M.onTcpListen(conn)
conn:on("receive", M.onTcpRecv)
end
function M.onTcpConnection(sck)
print("TCP connection.")
--ip, port = sck:getpeer()
--print("UDP connection. from: " .. ip)
end
function M.onTcpRecv(sck, data)
local peer = sck:getpeer()
print("TCP Recvd. data: " .. data .. ", Peer:" .. peer)
if (M.listenerOnIncomingCmd) then
-- see if json
if string.sub(data,1,1) == "{" then
-- catch json errors
local succ, results = pcall(function()
return cjson.decode(data)
end)
-- see if we could parse
if succ then
data = results --cjson.decode(data)
data.peerIp = peer
else
print("Error parsing JSON")
return
end
end
M.listenerOnIncomingCmd(data)
end
end
-- this property and method let an external object attach a
-- listener to the incoming TCP command
M.listenerOnIncomingCmd = nil
function M.addListenerOnIncomingCmd(listenerCallback)
M.listenerOnIncomingCmd = listenerCallback
-- print("Attached listener to incoming TCP cmd")
end
function M.removeListenerOnIncomingCmd(listenerCallback)
M.listenerOnIncomingCmd = nil
-- print("Removed listener on incoming TCP cmd")
end
return M
--M.init()
-- cayenn = M
-- cayenn.init()
-- GOOD INIT
-- opts = {}
-- opts.Name = "Dispenser DMP16"
-- opts.Desc = "Techcon DMP16 auger with stepper and linear slide"
-- opts.Icon = "http://gds-storage-prd.s3.amazonaws.com/fusion-360/161021/1972/70f0aa71/thumbnails/raasrendering-bbf3b3d5-01c5-48e4-bbdb-f81727a520ab-160-160.jpg"
-- opts.Widget = "com-chilipeppr-widget-dispenser"
-- opts.WidgetUrl = "https://github.com/chilipeppr/widget-dispenser/auto-generated.html"
-- cayenn.init(opts)
laser_3amp_driver_v1.lua
-- Laser module. Uses ACS714 for current reading.
-- Allows toggling/pulsing of laser
-- Has global relay for main power shutdown
node.setcpufreq(node.CPU160MHZ)
local m = {}
-- m = {}
m.pin = 2 -- laser TTL
m.pinRelay = 3 -- relay to main power supply
m.tmrReading = 0
m.tmrOutput = 4
m.tmrPulse = 3
m.isOn = false
m.isInitted = false
-- user can set this to ensure power is not above max
m.maxDuty = 1023
function m.init()
if m.isInitted then
return
end
-- TODO setup the i2c current sensor
-- setup TTL
gpio.mode(m.pin, gpio.OUTPUT)
gpio.write(m.pin, gpio.LOW)
-- setup relay
-- relay requires high or low (not float) to turn on
-- so set to float to turn off main power relay
gpio.mode(m.pinRelay, gpio.INPUT)
m.isOn = false
m.isInitted = true
end
function m.relayOn()
m.init()
-- relay requires high or low (not float) to turn on
gpio.mode(m.pinRelay, gpio.OUTPUT)
gpio.write(m.pinRelay, gpio.LOW)
-- gpio.write(m.pinRelay, gpio.HIGH)
print("Relay On")
end
function m.relayOff()
m.init()
-- relay requires float to turn off
gpio.mode(m.pinRelay, gpio.INPUT)
-- gpio.write(m.pinRelay, gpio.LOW)
print("Relay Off")
end
function m.on()
m.init()
gpio.write(m.pin, gpio.HIGH)
print("Laser On")
end
function m.off()
m.init()
gpio.write(m.pin, gpio.LOW)
print("Laser Off")
end
function m.read()
-- read 30 samples and average
local val = 0
for i=0,9,1
do
val = val + adc.read(0)
-- print("val: " .. val)
end
-- val = val / 1
local pct = (val * 100) / 1024
local millivolts = ((3300 * pct)) -- / 1000)
-- subtract 2500 mV cuz that means 0 amps (2560 mV actually)
millivolts = millivolts - 2570000
if millivolts < 0 then millivolts = 0 end
-- divide by 185 to figure out amps
local ma = (millivolts) / 185
-- print("ADC: " .. val .. " Pct: " .. pct .. " mv: " .. millivolts .. " mA: " .. ma)
return ma
end
m.samples = {} --{0,0,0,0,0,0,0,0,0,0}
m.lastSampleIndex = 0
function m.readAvgStart()
m.init()
-- let's do a reading each 10ms
-- create 10 samples, but always let the samples fall
-- off the queue
tmr.alarm(m.tmrReading, 20, tmr.ALARM_AUTO, function()
local ma = m.read()
m.samples[m.lastSampleIndex] = ma
m.lastSampleIndex = m.lastSampleIndex + 1
if m.lastSampleIndex > 9 then m.lastSampleIndex = 0 end
end)
tmr.alarm(m.tmrOutput, 500, tmr.ALARM_AUTO, function()
-- figure out avg
local s = 0
for i2=0,9,1 do
s = s + tonumber(m.samples[i2])
-- print("s: " .. s .. " i2: " .. i2 .. " m.samples[]: " .. m.samples[i2])
end
s = s / 10
print("mA: " .. s)
end)
end
function m.readAvgStop()
tmr.stop(m.tmrReading)
tmr.stop(m.tmrOutput)
-- print("Stopped average reading.")
end
function m.readStart()
m.init()
tmr.alarm(m.tmrReading, 500, tmr.ALARM_AUTO, m.onRead)
end
function m.onRead()
m.read()
end
function m.readStop()
tmr.stop(m.tmrReading)
end
-- pulse the laser for a delay of ms
function m.pulseFor(delay)
m.init()
local d = 100
if delay ~= nil then
d = delay
end
tmr.alarm(m.tmrPulse, d, tmr.ALARM_AUTO, m.pulseStop)
m.on()
end
function m.pulseStop()
tmr.stop(m.tmrPulse)
m.off()
end
-- frequency in hertz. max 1000hz or 1khz
-- duty cycle. 0 is 0% duty. 1023 is 100% duty. 512 is 50%.
function m.pwmOn(freqHz, duty)
if (duty > m.maxDuty) then duty = m.maxDuty end
print("Laser pwmOn hz:", freqHz, "duty:", duty)
pwm.setup(m.pin, freqHz, duty)
pwm.start(m.pin)
return duty
end
function m.pwmOff()
print("Laser pwmOff")
pwm.stop(m.pin)
end
function m.pwmSetMaxDuty(duty)
m.maxDuty = duty
end
-- m.init()
-- m.readStart()
-- m.readAvgStart()
return m
tinyg_read_v3.lua
-- Read the inputs from TinyG
-- We need to watch Coolant Pin
-- We also need to watch the A axis step/dir pins.
local m = {}
-- m = {}
m.pinCoolant = 5
m.pinADir = 6
m.pinAStep = 7
-- global ID counter. we increment this each time we see
-- a signal on the coolant pin
m.idCounter = -1
function m.init()
-- gpio.mode(m.pinCoolant, gpio.INPUT)
gpio.mode(m.pinCoolant, gpio.INT) --, gpio.PULLUP)
gpio.mode(m.pinADir, gpio.INT)
gpio.mode(m.pinAStep, gpio.INT)
gpio.trig(m.pinCoolant, "both", m.pinCoolantCallback)
-- gpio.trig(m.pinCoolant, "up", debounce(m.pinCoolantCallback))
-- gpio.trig(m.pinADir, "both", m.pinADirCallback)
-- gpio.trig(m.pinAStep, "both", m.pinAStepCallback)
-- print("Setup pin watchers for Coolant, ADir, AStep")
print("Coolant: " .. tostring(gpio.read(m.pinCoolant)) ..
", ADir: " .. tostring(gpio.read(m.pinADir)) ..
", AStep: " .. tostring(gpio.read(m.pinAStep))
)
end
function m.status()
print("Coolant: " .. tostring(gpio.read(m.pinCoolant)) ..
", ADir: " .. tostring(gpio.read(m.pinADir)) ..
", AStep: " .. tostring(gpio.read(m.pinAStep))
)
end
-- function debounce (func)
-- local last = 0
-- local delay = 50000 -- 50ms * 1000 as tmr.now() has μs resolution
-- return function (...)
-- local now = tmr.now()
-- local delta = now - last
-- if delta < 0 then delta = delta + 214748364 end; -- proposed because of delta rolling over, https://github.com/hackhitchin/esp8266-co-uk/issues/2
-- if delta < delay then return end;
-- last = now
-- return func(...)
-- end
-- end
-- function onChange ()
-- print('The pin value has changed to '..gpio.read(pin))
-- end
-- gpio.mode(pin, gpio.INT, gpio.PULLUP) -- see https://github.com/hackhitchin/esp8266-co-uk/pull/1
-- gpio.trig(pin, 'both', debounce(onChange))
m.lastTime = 0
m.lookingFor = gpio.HIGH
m.lookingCtr = 0
function m.pinCoolantCallback(level)
-- we get called here on rising and falling edge
if m.lookingFor == gpio.HIGH then
-- read 10 more times, and if we get good reads, trust it
local readCtr = 0
for i = 0, 5 do
if gpio.read(m.pinCoolant) == gpio.HIGH then
readCtr = readCtr + 1
end
end
if readCtr > 3 then
-- treat that as good avg
m.idCounter = m.idCounter + 1
-- print("Got coolant pin. idCounter: " .. m.idCounter)
m.onIdChange()
m.lookingFor = gpio.LOW
else
-- print("Failed avg")
end
else
-- looking for gpio.LOW
-- read 10 more times, and if we get good reads, trust it
local readCtr = 0
for i = 0, 5 do
if gpio.read(m.pinCoolant) == gpio.LOW then
readCtr = readCtr + 1
end
end
if readCtr > 3 then
-- treat that as good avg
-- print("Trusting low")
m.lookingFor = gpio.HIGH
else
-- print("Failed avg")
end
end
gpio.trig(m.pinCoolant, "both")
end
-- function m.pinCoolantCallbackOld(level)
-- -- this method is called when the coolant pin has an interrupt
-- if m.lookingFor == gpio.HIGH then
-- -- we are waiting for coolant to go high
-- if level == gpio.HIGH then
-- -- increment our ctr. if we get 10 in a row trust it.
-- m.lookingCtr = m.lookingCtr + 1
-- if m.lookingCtr > 5 then
-- -- we have hit our target. trust it.
-- m.idCounter = m.idCounter + 1
-- print("Got coolant pin. Level: " .. level .. " idCounter: " .. m.idCounter .. " tmr:" .. tmr.now())
-- m.onIdChange()
-- m.lookingFor = gpio.LOW
-- m.lookingCtr = 0
-- -- now look for falling edge
-- gpio.trig(m.pinCoolant, "down")
-- else
-- -- keep counting highs
-- gpio.trig(m.pinCoolant, "high")
-- end
-- else
-- -- we just saw a low, so reset our ctr to start again
-- -- so we get 10 clean reads
-- m.lookingCtr = 0
-- -- print("reset lookingCtr to 0")
-- gpio.trig(m.pinCoolant, "high")
-- end
-- else
-- -- we are waiting for coolant to go low
-- if level == gpio.LOW then
-- -- we got the low we are looking for
-- -- increment our ctr. if we get 10 in a row trust it.
-- m.lookingCtr = m.lookingCtr + 1
-- if m.lookingCtr > 5 then
-- -- print("Trusting we are low now")
-- -- now look for high
-- m.lookingFor = gpio.HIGH
-- m.lookingCtr = 0
-- -- now look for rising edge
-- gpio.trig(m.pinCoolant, "up")
-- else
-- -- keep counting lows
-- gpio.trig(m.pinCoolant, "low")
-- end
-- else
-- -- we just saw a high, so reset our ctr to start again
-- -- so we get 10 clean reads
-- m.lookingCtr = 0
-- -- print("reset lookingCtr to 0")
-- gpio.trig(m.pinCoolant, "low")
-- end
-- end
-- -- if we got a low, toss it
-- if level == gpio.LOW then
-- -- just trigger next callback
-- gpio.trig(m.pinCoolant, "up")
-- print("crapped bad lvl")
-- return
-- end
-- we have a high. now make sure we see high for 200 more reads.
-- if we do, we're safe
-- for i = 0, 200 do
-- if gpio.read(m.pinCoolant) == gpio.LOW then
-- -- if we got a low, consider this bad data
-- -- just trigger next callback
-- gpio.trig(m.pinCoolant, "up")
-- print("crapped on read " .. i .. " of 1000 reads")
-- return
-- end
-- end
-- -- if we got here, then we can be sure it's a good HIGH read
-- m.idCounter = m.idCounter + 1
-- m.onIdChange()
-- print("Got coolant pin. Level: " .. level .. " idCounter: " .. m.idCounter .. " tmr:" .. tmr.now())
-- gpio.trig(m.pinCoolant, "up")
-- end
-- end
function m.resetIdCounter()
m.idCounter = -1
m.onIdChange()
print("Reset idCounter: " .. m.idCounter)
end
function m.getIdCounter()
return m.idCounter
end
-- this property and method let an external object attach a
-- listener to the counter change
m.listenerOnIdChange = null
function m.addListenerOnIdChange(listenerCallback)
m.listenerOnIdChange = listenerCallback
-- print("Attached listener to Id Change")
end
function m.removeListenerOnIdChange(listenerCallback)
m.listenerOnIdChange = null
-- print("Removed listener on Id Change")
end
function m.onIdChange()
if m.listenerOnIdChange then
m.listenerOnIdChange(m.idCounter)
end
end
-- this property and method let an external object attach a
-- listener to the ADir pin
m.listenerOnADir = null
function m.addListenerOnADir(listenerCallback)
m.listenerOnADir = listenerCallback
-- print("Attached listener to ADir pin")
end
function m.removeListenerOnADir(listenerCallback)
m.listenerOnADir = null
-- print("Removed listener on ADir pin")
end
-- this property and method let an external object attach a
-- listener to the AStep pin
m.listenerOnAStep = null
function m.addListenerOnAStep(listenerCallback)
m.listenerOnAStep = listenerCallback
-- print("Attached listener to AStep pin")
end
function m.removeListenerOnAStep(listenerCallback)
m.listenerOnAStep = null
-- print("Removed listener on AStep pin")
end
function m.pinADirCallback(level)
gpio.trig(m.pinADir, "both")
-- this method is called when the ADir pin has an interrupt
-- we need to simply regurgitate it to appropriate listener
print("ADir: " .. level)
-- call listener
if m.listenerOnADir then
m.listenerOnADir()
end
-- print("Got coolant pin. idCounter: " .. m.idCounter)
end
function m.pinAStepCallback(level)
gpio.trig(m.pinAStep, "both")
-- this method is called when the AStep pin has an interrupt
-- we need to simply regurgitate it to appropriate listener
print("AStep: " .. level)
-- call listener
if m.listenerOnAStep then
m.listenerOnAStep()
end
end
return m
-- m.init()
queue.lua
-- Cayenn Queue by using a filelocal m = {}
-- m = {}
m.filename = "queue.txt"
m.tmrSend = 1
function m.wipe()
file.remove(m.filename)
file.open(m.filename, "w+")
-- file.writeline("")
file.close()
end
function m.add(payload)
-- open 'queue.txt' in 'a+' mode to append
file.open(m.filename, "a+")
local ok, jsontag = pcall(cjson.encode, payload)
if ok then
-- write to the end of the file
file.writeline(jsontag)
-- print("Wrote line to file:" .. jsontag)
else
-- print("failed to encode jsontag for queue file!")
end
file.close()
end
function m.getId(id)
-- if file.exists(m.filename) == false then
-- return nil
-- end
file.open(m.filename, "r")
-- read the # of lines by the id
local line
for ctr = 0, id do
line = file.readline()
-- print("Line:", id, ctr, line)
end
file.close()
-- print("Line:", line)
if line == nil then
return nil
end
-- parse json to table
local succ, results = pcall(function()
return cjson.decode(line)
end)
-- see if we could parse
if succ then
--data = results
return results
else
-- print("Error parsing JSON")
return nil
end
end
function m.getTxt()
file.open(m.filename, "r")
local txt = file.read()
file.close()
return txt
end
-- this method sends back data slowly
m.lastSendId = 0
m.callback = nil
m.transId = nil
function m.send(callback, transId)
-- reset ctr
m.lastSendId = 0
m.callback = callback
m.transId = transId
-- say we are starting
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Start"] = 0})
-- callback slowly and send q each time
tmr.alarm(m.tmrSend, 2, tmr.ALARM_SEMI, m.onSend)
end
function m.onSend()
-- get next line
local cmd = queue.getId(m.lastSendId)
if cmd ~= nil then
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Q"] = cmd})
m.lastSendId = m.lastSendId + 1
tmr.start(m.tmrSend)
else
-- we are done, cuz hit null
m.callback({["TransId"] = m.transId, ["Resp"] = "GetQ", ["Finish"] = m.lastSendId})
end
end
return m
led.lua
-- control led on esp8266-- use this module like
-- led = require("led")
-- led.on() -- turn on led
-- led.off() -- turn off led
-- led.blink(2) -- blink twice w/ 200ms delay
-- led.blink(6,100) -- blink 6 times w/ 100ms delay
-- led.blink(10,20) -- blink 10 times w/ 20ms delay
local led = {}
-- led = {}
led.pin = 4 -- this is GPIO2
led.timerId = 6 -- 0 thru 6 allowed. change to not conflict.
led.isOn = false
led.isInitted = false
function led.init()
-- setup led as output and turn on
gpio.mode(led.pin, gpio.OUTPUT)
gpio.write(led.pin, gpio.HIGH)
led.isOn = false
led.isInitted = true
end
function led.on()
if led.isInitted ~= true then
led.init()
end
gpio.write(4, gpio.LOW) --on
-- print("Led on")
end
function led.off()
if led.isInitted ~= true then
led.init()
end
gpio.write(4, gpio.HIGH) --off
-- print("Led off")
end
led.blinkTimes = 0
led.delay = 200
function led.blink(val, delay)
led.blinkTimes = val * 2
if delay ~= nil and delay > 0 then
led.delay = delay
end
tmr.alarm(led.timerId, led.delay, tmr.ALARM_AUTO, led.onTimer)
end
led.ctr = 0
function led.onTimer()
led.ctr = led.ctr + 1
if led.ctr > led.blinkTimes then
tmr.stop(led.timerId)
led.ctr = 0
led.off()
-- print("Done")
led.delay = 200 --reset delay to default
return
end
if gpio.read(4) == gpio.HIGH then
led.on()
else
led.off()
end
end
return led
init.lua
dofile("main_laseruv.lc")
Saturday, November 5, 2016
Cayenn RPM Sensor for ChiliPeppr Using Nodemcu ESP8266 / Hall Effect Sensor
This blog post describe the creation of an RPM hall effect sensor for the Cayenn protocol for ChiliPeppr.
It runs from a NodeMCU ESP8266.
A Hall Effect Sensor is connected to the ESP8266 to port 2. The actual sensor will eventually be mounted correctly from a plastic part.
Back side of hall effect sensor. Only 3.3v power and the digital out port are used.
The blue pulses are debug output on port 3 of the ESP8266. Each high/low indicates 2 counts of the spindle rotating. The high/low output made it much easier to debug on the oscilloscope to ensure counts were accurate. As seen in the screen grab above it is 100% accurate.
The JSON announce packet is highlighted in yellow below to show you where it shows up in the console.
You can also send and receive Cayenn commands in the serial port console window to debug your code.
It runs from a NodeMCU ESP8266.
A Hall Effect Sensor is connected to the ESP8266 to port 2. The actual sensor will eventually be mounted correctly from a plastic part.
Back side of hall effect sensor. Only 3.3v power and the digital out port are used.
The spindle head has a magnet attached on the inside of it.
Video of the sensor in action.
Oscilloscope Output
When the spindle head is running, pulses are generated by the hall effect sensor. The pulses are sent to pin 2 of the ESP8266 so it can count the rotations of the spindle.
1K RPM
When running spindle at 1K RPM. The yellow lines are the direct pulses from the hall effect sensor. The voltage is 3.3v and is low for only about 2% of the time because the magnet is very small compared to the overall circumference of the spindle plastic head. This makes detection somewhat difficult because the pulse is only low when the magnet is directly in front of the hall effect sensor.The blue pulses are debug output on port 3 of the ESP8266. Each high/low indicates 2 counts of the spindle rotating. The high/low output made it much easier to debug on the oscilloscope to ensure counts were accurate. As seen in the screen grab above it is 100% accurate.
5K RPM
When running spindle at 5K RPM. The blips on the blue line are when the ESP8266 transmits the UDP packets. The RPM is not counted during that period. Per the screenshot below, this is a 100% exact capture of RPM count.10K RPM
When running spindle at 10K RPM. Keep in mind the yellow pulses are just not all showing due to anti-aliasing in the oscilloscope software.Asking RPM Sensor to Start Sending
Once the ESP8266 is powered on, it connects to Wifi and announces its existence on the network. Per the Cayenn protocol, it sends the following announcement to the broadcast network address, thus reaching SPJS, thus subsequently reaching ChiliPeppr (if you're connected to SPJS).{You can see this in the SPJS widget by choosing Show / Hide Console in the upper right corner triangle menu.
"Addr":{
"IP":"10.0.0.169",
"Port":3739,
"Network":"udp"
},
"Announce":"i-am-a-client",
"Widget":"com-chilipeppr-widget-rpmsensor",
"JsonTag":"{\"Icon\":\"http:\\/\\/gds-storage-prd.s3.amazonaws.com\\/fusion-360\\/161021\\/1972\\/70f0aa71\\/thumbnails\\/raasrendering-bbf3b3d5-01c5-48e4-bbdb-f81727a520ab-160-160.jpg\",\"Name\":\"Spindle RPM\",\"WidgetUrl\":\"https:\\/\\/github.com\\/chilipeppr\\/widget-dispenser\\/auto-generated.html\",\"Widget\":\"com-chilipeppr-widget-rpmsensor\",\"Desc\":\"Counts spindle RPM using LED tachometer\"}",
"DeviceId":"chip:8395871-flash:1458400-mac:5c:cf:7f:80:1c:5f"
}
The JSON announce packet is highlighted in yellow below to show you where it shows up in the console.
Request Announcement
You can also request to discover all Cayenn devices if you want them to announce themselves again. Send the following command to SPJS at any time. Take note this is a UDP command.cayenn-sendudp 10.0.0.255 {"Cayenn":"Discover"}
Start RPM Sensing
You will get back all discovered devices. When you find your RPM sensor, and thus its IP address, you can ask it to start sending RPM readings. You will need to send the following command to SPJS. Take note this is a TCP command. Ensure the IP address is the address of your device.cayenn-sendtcp 10.0.0.169 {"Cmd":"RpmSenseStart"}
UDP Packets Sent from ESP8266 to SPJS to ChiliPeppr
When the RPM sensor starts to provide data, it will come in packets like those below. Notice that the ESP8266 sends the RPM update packets via UDP out to the network broadcast address. This makes the sending efficient because TCP may have to retransmit packets. Since RPM data is very fleeting, it doesn't matter if a packet gets dropped occasionally by the Wifi network.
The actual RPM value comes in as a JsonTag which is the part of the Cayenn protocol that allows random tags of any type, and it is simply up to the final widget to interpret the data. Notice that a unique DeviceID is also transmitted each time to assist ChiliPeppr with uniquely recognizing each Cayenn device.
{
"Addr":{
"IP":"10.0.0.169",
"Port":7366,
"Network":"udp"
},
"Announce":"",
"Widget":"",
"JsonTag":"{\"Rpm\":1020}",
"DeviceId":"chip:8395871-flash:1458400-mac:5c:cf:7f:80:1c:5f"
}
RPM Widget in ChiliPeppr
Once ChiliPeppr recognizes the existence of the hall effect sensor, it will dynamically load the Javascript for the widget into memory. The widget will be initted and auto appear inside ChiliPeppr. This makes configuring your workspace easy.
Here is what the widget looks like in the TinyG workspace in ChiliPeppr.
Lua Code
The Lua code consists of several modules.
- init.lua
- main_rpmsensor.lua
- rpmsensorv2.lua
- cayennv2.lua
- led.lua
init.lua - auto loading file when ESP8266 boots
-- Init file -- Simply run the main compiled file for this Cayenn device dofile("main_rpmsensor.lc")
main_rpmsensor.lua - main entry point
-- RPM Sensor -- Supports Cayenn announcement and UDP data sending of RPM data -- run fast to gather rpm counters -- node.setcpufreq(node.CPU160MHZ) cayenn = require('cayennv2') rpmsensor = require('rpmsensorv2') led = require("led") opts = {} opts.Name = "Spindle RPM" opts.Desc = "Counts spindle RPM using LED tachometer" opts.Icon = "http://gds-storage-prd.s3.amazonaws.com/fusion-360/161021/1972/70f0aa71/thumbnails/raasrendering-bbf3b3d5-01c5-48e4-bbdb-f81727a520ab-160-160.jpg" opts.Widget = "com-chilipeppr-widget-rpmsensor" opts.WidgetUrl = "https://github.com/chilipeppr/widget-dispenser/auto-generated.html" -- opts = nil -- dealloc opts -- define commands supported cmds = { "ResetCtr", "GetCmds", "GetQ", "WipeQ", "CmdQ", "RpmSenseStart", "RpmSenseStop" } queue = {} -- this is called by RpmSensor each second when the new RPM count -- is known. function onRpmRead(ctr) -- print("Got RPM value from RPM sensor: " .. ctr) -- led inidcator we sent led.on() -- stop rpm watching to try to get more accurate count -- because sending UDP chews up cpu, so interrupts get weird -- during that time rpmsensor.stopRpmWatch() -- send UDP msg to SPJS local rpm = {} rpm.Rpm = ctr cayenn.sendBroadcast(rpm) led.off() -- start watching interrupts again rpmsensor.startRpmWatch() end -- this is called by Cayenn when an incoming cmd comes in -- from the network, i.e. from SPJS. These are TCP commands so -- they are guaranteed to come in (vs UDP which could drop) function onCmd(payload) if (type(payload) == "table") then -- print("is json") -- print("Got incoming Cayenn cmd JSON: " .. cjson.encode(payload)) -- See what cmd if payload.Cmd == "ResetCtr" then cnc.resetIdCounter() elseif payload.Cmd == "GetCmds" then cayenn.sendAnnounceBroadcast(cmds) elseif payload.Cmd == "GetQ" then -- print("Sending queue back to network") cayenn.sendAnnounceBroadcast(queue) elseif payload.Cmd == "WipeQ" then queue = nil -- print("Wiped queue: " .. cjson.encode(queue)) elseif payload.Cmd == "CmdQ" then -- queuing cmd. we must have ID. if payload.Id == nil then print("Error queuing command. It must have an ID") return end if payload.RunCmd == nil then print("Error queuing command. It must have a RunCmd like RunCmd:{Cmd:AugerOn,Speed:10}.") return end -- print("Queing command") queue[payload.Id] = payload.RunCmd -- print("New queue: " .. cjson.encode(queue)) elseif payload.Cmd == "RpmSenseStart" then rpmsensor.startRpmWatch() print("Start sensing RPM and sending updates each second") elseif payload.Cmd == "RpmSenseStop" then rpmsensor.stopRpmWatch() print("Stop sensing RPM and sending updates each second") else print("Got cmd we do not understand. Huh?") end else -- print("is string") -- print("Got incoming Cayenn cmd. str: ", payload) end end -- this callback is called when an incoming UDP broadcast -- comes in to this device. typically this is just for -- Cayenn Discover requests to figure out what devices are on -- the network function onIncomingBroadcast(cmd) -- print("Got incoming UDP cmd: ", cmd) if (type(cmd) == "table") then if cmd["Cayenn"] ~= nil then if cmd.Cayenn == "Discover" then -- somebody is asking me to announce myself cayenn.sendAnnounceBroadcast() else print("Do not understand incoming Cayenn cmd") end elseif cmd["Announce"] ~= nil then if cmd.Announce == "i-am-your-server" then print("Got a server announcement. Cool. Store it. TODO") else print("Got announce but not from a server. Huh?") end else print("Do not understand incoming UDP cmd") end else print("Got incoming UDP as string") end end -- add listener to incoming cayenn commands cayenn.addListenerOnIncomingCmd(onCmd) cayenn.addListenerOnIncomingUdpCmd(onIncomingBroadcast) -- add listener to RPM Known rpmsensor.addListenerOnRpmKnown(onRpmRead) cayenn.init(opts) rpmsensor.init() led.blink(6)
rpmsensorv2.lua - module that reads the interrupts from the hall sensor
-- Read the inputs from RPM Sensor -- We need to watch D2 Pin local m = {} -- m = {} node.setcpufreq(node.CPU160MHZ) -- node.setcpufreq(node.CPU80MHZ) m.pinRpm = 2 -- global counter. we increment this each time we see -- a signal on the rpm sensor pin m.counter = 0 function m.init() -- gpio.mode(m.pinCoolant, gpio.INPUT) gpio.mode(m.pinRpm, gpio.INT) --, gpio.PULLUP) gpio.mode(3, gpio.OUTPUT) -- gpio.trig(m.pinRpm, "up", m.pinRpmCallback) gpio.trig(m.pinRpm, "up", m.pinRpmCallback) print("Setup pin watcher for RPM sensor. pin: " .. m.pinRpm) -- m.startRpmWatch() m.status() end function m.startRpmWatch() m.counter = 0 tmr.alarm(0, 1000, tmr.ALARM_AUTO, m.onTimer) end function m.stopRpmWatch() tmr.stop(0) end function m.onTimer() tmr.stop(0) -- print("RPM: " .. m.counter * 60) m.onRpmKnown(m.counter * 60) m.counter = 0 tmr.start(0) end function m.status() print("RPM Pin: " .. tostring(gpio.read(m.pinRpm))) end m.lastTime = 0 function m.pinRpmCallback(level) -- make sure we got a callback later than our min time if tmr.now() - m.lastTime > 3000 then if m.isHigh then gpio.write(3, gpio.LOW) m.isHigh = false else gpio.write(3, gpio.HIGH) m.isHigh = true end m.counter = m.counter + 1 end -- if they don't start the counter then just reset when too high if m.counter > 1000 then m.counter = 0 end m.lastTime = tmr.now() gpio.trig(m.pinRpm, "up") end m.isHigh = false function m.pinRpmCallbackOrig(level) if level == 1 then m.counter = m.counter + 1 gpio.trig(m.pinRpm, "up") if m.isHigh then gpio.write(3, gpio.LOW) m.isHigh = false else gpio.write(3, gpio.HIGH) m.isHigh = true end end end function m.resetCounter() m.counter = 0 -- m.onIdChange() print("Reset counter: " .. m.counter) end function m.getCounter() return m.counter end -- this property and method let an external object attach a -- listener to the counter change m.listenerOnRpmKnown = null function m.addListenerOnRpmKnown(listenerCallback) m.listenerOnRpmKnown = listenerCallback print("Attached listener to RPM Known") end function m.removeListenerOnRpmKnown(listenerCallback) m.listenerOnRpmKnown = null print("Removed listener on RPM Known") end function m.onRpmKnown(ctr) if m.listenerOnRpmKnown then m.listenerOnRpmKnown(ctr) end end return m -- m.init()
cayennv2.lua - standard Cayenn module providing UDP/TCP communications
-- Cayenn Protocol for ChiliPeppr -- This module does udp/tcp sending/receiving to talk with SPJS -- or have the browser talk direct to this ESP8266 device. -- This module has methods for connecting to wifi, then initting -- UDP servers, TCP servers, and sending a broadcast announce out. -- The broadcast announce let's any listening SPJS know we're alive. -- SPJS then reflects that message to any listening browsers like -- ChiliPeppr so they know a device is available on the network. -- Then the browser can send commands back to this device. local M = {} -- M = {} M.port = 8988 M.myip = nil M.sock = nil M.jsonTagTable = nil --M.announce = M.isInitted = false -- When you are initting you can pass in tags to describe your device -- You should use a format like this: -- cayenn.init({ -- widget: "com-chilipeppr-widget-ina219", -- icon: "http://chilipeppr.com/img/ina219.jpg", -- widgetUrl: "https://github.com/chilipeppr/widget-ina219/auto-generated.html", -- }) function M.init(jsonTagTable) -- save the jsonTagTable if jsonTagTable ~= nil then M.jsonTagTable = jsonTagTable end if M.isInitted then print("Already initted, but got called again") return end print("Initting...") -- figure out if i have an IP M.myip = wifi.sta.getip() if M.myip == nil then print("You need to connect to wifi. Connecting for you.") M.setupWifi() else print("My IP: " .. M.myip) M.isInitted = true -- create socket for outbound UDP sending M.sock = net.createConnection(net.UDP, 0) -- create server to listen to incoming udp M.initUdpServer() -- create server to listen to incoming tcp M.initTcpServer() -- send our announce M.sendAnnounceBroadcast(M.jsonTagTable) end end function M.createAnnounce(jsonTagTable) local a = {} a.Announce = "i-am-a-client" a.MyDeviceId = "chip:" .. node.chipid() .. "-flash:" .. node.flashid() .. "-mac:" .. wifi.sta.getmac() if jsonTagTable.Name then a.Name = jsonTagTable.Name -- jsonTagTable.Name = nil -- erase from table so not in jsonTags elseif M.jsonTagTable.Name then a.Name = M.jsonTagTable.Name else a.Name = "Unnamed" end if jsonTagTable.Desc then a.Desc = jsonTagTable.Desc -- jsonTagTable.Desc = nil -- erase from table so not in jsonTags else a.Desc = "(no desc provided)" end if jsonTagTable.Icon then a.Icon = jsonTagTable.Icon -- jsonTagTable.Icon = nil -- erase from table so not in jsonTags else a.Icon = "(no icon provided)" end if jsonTagTable.Widget then a.Widget = jsonTagTable.Widget -- jsonTagTable.Widget = nil elseif M.jsonTagTable.Widget then a.Widget = M.jsonTagTable.Widget else a.Widget = "com-chilipeppr-widget-undefined" end if jsonTagTable.WidgetUrl then a.WidgetUrl = jsonTagTable.WidgetUrl -- jsonTagTable.WidgetUrl = nil else a.WidgetUrl = "(no url specified)" end -- see if there is a jsontagtable passed in as extra meta local jsontag = "" if jsonTagTable then ok, jsontag = pcall(cjson.encode, jsonTagTable) if ok then -- print("Adding jsontagtable" .. jsontag) else print("failed to encode jsontag!") end end a.JsonTag = jsontag local ok, json = pcall(cjson.encode, a) if ok then --print("Encoded json for announce: " .. json) else print("failed to encode json!") end print("Announce msg: " .. json) return json end -- send announce to broadcast addr so spjs -- knows of our existence function M.sendAnnounceBroadcast(jsonTagTable) if M.isInitted == false then print("You must init first.") return end local bip = wifi.sta.getbroadcast() --print("Broadcast addr:" .. bip) -- if there was no jsonTagTable passed in, then used -- stored one if not jsonTagTable then jsonTagTable = M.jsonTagTable end print("Sending announce to ip: " .. bip) M.sock:connect(M.port, bip) M.sock:send(M.createAnnounce(jsonTagTable)) M.sock:close() end function M.sendBroadcast(jsonTagTable) if M.isInitted == false then print("You must init first.") return end -- we need to attach deviceid local a = {} a.MyDeviceId = "chip:" .. node.chipid() .. "-flash:" .. node.flashid() .. "-mac:" .. wifi.sta.getmac() -- see if there is a jsontagtable passed in as extra meta local jsontag = "" if jsonTagTable then ok, jsontag = pcall(cjson.encode, jsonTagTable) if ok then -- print("Adding jsontagtable" .. jsontag) else print("failed to encode jsontag!") end end a.JsonTag = jsontag local ok, json = pcall(cjson.encode, a) if ok then --print("Encoded json for announce: " .. json) else print("failed to encode json!") end local bip = wifi.sta.getbroadcast() --print("Broadcast addr:" .. bip) -- local msg = cjson.encode(jsonTagTable) -- local msg = cjson.encode(a) -- print("Sending msg: " .. json .. " to ip: " .. bip) M.sock:connect(M.port, bip) M.sock:send(json) M.sock:close() end function M.setupWifi() -- setwifi wifi.setmode(wifi.STATION) -- longest range is b wifi.setphymode(wifi.PHYMODE_N) --Connect to access point automatically when in range -- for some reason digits in password seem to get mangled -- so splitting them seems to solve problem wifi.sta.config("NETGEAR-main", "yourpassword") wifi.sta.connect() --register callback wifi.sta.eventMonReg(wifi.STA_IDLE, function() print("STATION_IDLE") end) --wifi.sta.eventMonReg(wifi.STA_CONNECTING, function() print("STATION_CONNECTING") end) wifi.sta.eventMonReg(wifi.STA_WRONGPWD, function() print("STATION_WRONG_PASSWORD") end) wifi.sta.eventMonReg(wifi.STA_APNOTFOUND, function() print("STATION_NO_AP_FOUND") end) wifi.sta.eventMonReg(wifi.STA_FAIL, function() print("STATION_CONNECT_FAIL") end) wifi.sta.eventMonReg(wifi.STA_GOTIP, M.gotip) --register callback: use previous state wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_State) if(previous_State==wifi.STA_GOTIP) then print("Station lost connection with access point\n\tAttempting to reconnect...") else print("STATION_CONNECTING") end end) --start WiFi event monitor with default interval wifi.sta.eventMonStart(1000) end function M.gotip() print("STATION_GOT_IP") M.myip = wifi.sta.getip() print("My IP: " .. M.myip) -- stop monitoring now since we're connected wifi.sta.eventMonStop() print("Stopped monitoring wifi events since connected.") -- make sure we are initted M.init() end function M.initUdpServer() M.udpServer = net.createServer(net.UDP) --M.udpServer:on("connection", M.onUdpConnection) M.udpServer:on("receive", M.onUdpRecv) M.udpServer:listen(8988) print("UDP Server started on port 8988") end function M.onUdpConnection(sck) print("UDP connection.") --ip, port = sck:getpeer() --print("UDP connection. from: " .. ip) end function M.onUdpRecv(sck, data) print("UDP Recvd. data: " .. data) if (M.listenerOnIncomingUdpCmd) then -- see if json if string.sub(data,1,1) == "{" then -- catch json errors local succ, results = pcall(function() return cjson.decode(data) end) -- see if we could parse if succ then data = results --cjson.decode(data) -- data.peerIp = peer else print("Error parsing JSON") return end end M.listenerOnIncomingUdpCmd(data) end end -- this property and method let an external object attach a -- listener to the incoming UDP cmd M.listenerOnIncomingUdpCmd = nil function M.addListenerOnIncomingUdpCmd(listenerCallback) M.listenerOnIncomingUdpCmd = listenerCallback print("Attached listener to incoming UDP cmd") end function M.removeListenerOnIncomingUdpCmd(listenerCallback) M.listenerOnIncomingUdpCmd = nil print("Removed listener on incoming UDP cmd") end function M.initTcpServer() M.tcpServer = net.createServer(net.TCP) M.tcpServer:listen(8988, M.onTcpListen) print("TCP Server started on port 8988") end function M.onTcpListen(conn) conn:on("receive", M.onTcpRecv) end function M.onTcpConnection(sck) print("TCP connection.") --ip, port = sck:getpeer() --print("UDP connection. from: " .. ip) end function M.onTcpRecv(sck, data) local peer = sck:getpeer() print("TCP Recvd. data: " .. data .. ", Peer:" .. peer) if (M.listenerOnIncomingCmd) then -- see if json if string.sub(data,1,1) == "{" then -- catch json errors local succ, results = pcall(function() return cjson.decode(data) end) -- see if we could parse if succ then data = results --cjson.decode(data) data.peerIp = peer else print("Error parsing JSON") return end end M.listenerOnIncomingCmd(data) end end -- this property and method let an external object attach a -- listener to the incoming TCP command M.listenerOnIncomingCmd = nil function M.addListenerOnIncomingCmd(listenerCallback) M.listenerOnIncomingCmd = listenerCallback print("Attached listener to incoming TCP cmd") end function M.removeListenerOnIncomingCmd(listenerCallback) M.listenerOnIncomingCmd = nil print("Removed listener on incoming TCP cmd") end return M --M.init()
led.lua - utility module to blink ESP8266 led easily
-- control led on esp8266 -- use this module like -- led = require("led") -- led.on() -- turn on led -- led.off() -- turn off led -- led.blink(2) -- blink twice w/ 200ms delay -- led.blink(6,100) -- blink 6 times w/ 100ms delay -- led.blink(10,20) -- blink 10 times w/ 20ms delay local led = {} led = {} led.pin = 4 -- this is GPIO2 led.timerId = 6 -- 0 thru 6 allowed. change to not conflict. led.isOn = false led.isInitted = false function led.init() -- setup led as output and turn on gpio.mode(led.pin, gpio.OUTPUT) gpio.write(led.pin, gpio.HIGH) led.isOn = false led.isInitted = true end function led.on() if led.isInitted ~= true then led.init() end gpio.write(4, gpio.LOW) --on -- print("Led on") end function led.off() if led.isInitted ~= true then led.init() end gpio.write(4, gpio.HIGH) --off -- print("Led off") end led.blinkTimes = 0 led.delay = 200 function led.blink(val, delay) led.blinkTimes = val * 2 if delay ~= nil and delay > 0 then led.delay = delay end tmr.alarm(led.timerId, led.delay, tmr.ALARM_AUTO, led.onTimer) end led.ctr = 0 function led.onTimer() led.ctr = led.ctr + 1 if led.ctr > led.blinkTimes then tmr.stop(led.timerId) led.ctr = 0 led.off() -- print("Done") led.delay = 200 --reset delay to default return end if gpio.read(4) == gpio.HIGH then led.on() else led.off() end end return led
Using ChiliPeppr's Nodemcu Workspace to Edit Lua Code
I use the ChiliPeppr Nodemcu workspace at http://chilipeppr.com/nodemcu to edit all code and for uploading to the Nodemcu ESP8266 device. It's a really good workflow and I think much better than programming via the Arduino IDE because it fully embraces the flash memory on the ESP8266. This lets you develop different portions of code iteratively and just reference the module.You can also send and receive Cayenn commands in the serial port console window to debug your code.
Subscribe to:
Posts (Atom)