The Cayenn protocol is a new method of attaching add-on devices to ChiliPeppr to extend it so that you can add new toolheads such as lasers, dispensers, tool changers, pick-and-place heads, etc. It is also about getting data into ChiliPeppr from sensors such as current/voltage sensors, heat sensors, distance sensors, touch probes, etc.
The Cayenn protocol consists of several components including:
- Devices talk to SPJS over UDP and TCP and auto-announce their existence on the network so that SPJS/ChiliPeppr can see them
- SPJS regurgitates UDP/TCP packets it sees from Cayenn devices back to ChiliPeppr
- SPJS lets ChiliPeppr send it UDP/TCP commands that it sends out to Cayenn devices
- Cayenn devices need to allow a list of commands to be uploaded to them via TCP with a unique counter ID on each command so that the main execution of Gcode can sync with each device
- The coolant on/off pin on your CNC device becomes a sync pin that Cayenn devices should listen to so they know when to execute their list of pre-uploaded commands. This pin needs to be hard-wired to your Cayenn devices for reliability/speed during the execution of the main Gcode.
- The Gcode widget in ChiliPeppr needs to have a pre-processor in it to see all Cayenn commands and generate the M7/M9 coolant pin toggle and the unique ID for that command so syncing works
- The Gcode widget needs to allow for the pre-upload of all commands to each Cayenn device and then receipt that the upload is complete and correct
- On each play of Gcode, the Gcode widget must send out a ResetCtr command to all Cayenn devices. If user starts Gcode at alternate position this should be taken into account.
The protocol expects that your devices can talk to Serial Port JSON Server (SPJS) over TCP and UDP. Thus you need your device on the network. The cheapest way to do this is an ESP8266, but other methods like the ESP32 or an ethernet connected device will work.
The typical flow is ChiliPeppr -> SPJS -> Cayenn device or the reverse Cayenn device -> SPJS -> ChiliPeppr.
There are new features in SPJS v1.93 to support Cayenn. Those features are:
- From ChiliPeppr you can do the command cayenn-sendudp 10.0.0.255 {"key":"value"} so that you can send out broadcast commands to all devices in one shot.
- From ChiliPeppr you can do the command cayenn-sendtcp 10.0.0.100 {"key":"value"} so that you can send out guaranteed packets to a specific device.
- From your device you can send UDP to a broadcast address like 10.0.0.255 and SPJS will see it and regurgitate it to ChiliPeppr
- From your device you can send TCP packet to a specific SPJS server so you have guaranteed communications. SPJS will see it and regurgitate it to ChiliPeppr.
Cayenn is still a work in progress, but the Cayenn widget for ChiliPeppr can be found at:
Github - https://github.com/chilipeppr/widget-cayenn
Cloud9 - http://ide.c9.io/chilipeppr/widget-cayenn
Sample Cayenn LaserUV Device
There is a Github page that describes in detail a sample latest working Cayenn device for a Laser 3A driver for running a UV BDR209 laser. It includes all of the Lua code tested and known to work.https://github.com/chilipeppr/cayenn-laseruv
It has code on it and the README describes how to build it and some sample Gcode to drive it.
Cayenn Widget
The Cayenn widget is like a control panel for all of your devices. When a device announces itself it will show an icon in the Cayenn widget. This widget also understands the set structure of a specific Cayenn widget and loads it to see the meta-data like what commands it supports, if it has a toolhead, the 3D Three.js geometry for the toolhead, the active queue list, etc.
The Cayenn widget will show the command queue for each Cayenn device. You can reload the list from the device, add to it, wipe it, etc.
Here's an example of an Air Coolant device.
Here is a sample laser device.
If the Cayenn device is a toolhead, you can provide a Three.js JSON object by creating your toolhead in the Three.js Editor at https://threejs.org/editor/ and then embed it into your Cayenn widget so that the control panel can see the object and show a preview.
Sample Dispenser Widget in ChiliPeppr
Work in progress, but here is the start of a sample widget for a specific Cayenn device.
Sample Gcode that Runs Cayenn Devices
In your Gcode, you need to add active comments. These are similar to the active comments that TinyG defined recently to control heated beds. Active comments are JSON inside a () comment.
The LinearSlideOn and LinearSlideOff commands basically tell the Cayenn device to toggle on the enable of its DRV8825 stepper motor driver. That driver is directly hard-wired into the step/dir pins from the TinyG.
G21
G0 Z10 (move spindle to clearance height)
G0 X0 Y0 (move to home)
({CayennDevice:"DispenserPaste",Cmd:"LinearSlideOn"})
(Configure A axis to linear slide settings. )
{"1":{"sa":18,"tr":0.5," mi":1,"po":1,"p m":3,"pl":0}}
(Axis mode is std like a linear axis.)
{"a":{"am":1,"vm":800,"fr":800,"tn":0,"tm":72}}
({Cmd:"AirOn"})
G0 A-70 (Move linear slide down to -70mm)
({"Cmd":"AugerFwd","Speed":5})
(Move 10mm along x axis)
G1 X5 F100 (100mm/min)
({Cmd:"AugerOff"})
({Cmd:"AirOff"})
G0 A0 X0 (Move linear slide back to 0 while moving X)
({Cmd:"LinearSlideOff"})
G0 Z10 (move spindle to clearance height)
G0 X0 Y0 (move to home)
({CayennDevice:"DispenserPaste",Cmd:"LinearSlideOn"})
(Configure A axis to linear slide settings. )
{"1":{"sa":18,"tr":0.5," mi":1,"po":1,"p m":3,"pl":0}}
(Axis mode is std like a linear axis.)
{"a":{"am":1,"vm":800,"fr":800,"tn":0,"tm":72}}
({Cmd:"AirOn"})
G0 A-70 (Move linear slide down to -70mm)
({"Cmd":"AugerFwd","Speed":5})
(Move 10mm along x axis)
G1 X5 F100 (100mm/min)
({Cmd:"AugerOff"})
({Cmd:"AirOff"})
G0 A0 X0 (Move linear slide back to 0 while moving X)
({Cmd:"LinearSlideOff"})
Here is the converted Gcode after you drag/drop it into ChiliPeppr. You'll notice that Id's have been added to each Cayenn command. M8/M9 coolant on/off commands have also been added.
G21
G0 Z10 (move spindle to clearance height)
G0 X0 Y0 (move to home)
M8 G4 P0.1 ({"CayennDevice":"DispenserPaste","Cmd":"LinearSlideOn","Id":0})
M9 G4 P0.1 (cayenn)
(Configure A axis to linear slide settings. )
{"1":{"sa":18,"tr":0.5," mi":1,"po":1,"p m":3,"pl":0}}
(Axis mode is std like a linear axis.)
{"a":{"am":1,"vm":800,"fr":800,"tn":0,"tm":72}}
M8 G4 P0.1 ({"Cmd":"AirOn","Id":1})
M9 G4 P0.1 (cayenn)
G0 A-70 (Move linear slide down to -70mm)
M8 G4 P0.1 ({"Cmd":"AugerFwd","Speed":5,"Id":2})
M9 G4 P0.1 (cayenn)
(Move 10mm along x axis)
G1 X5 F100 (100mm/min)
M8 G4 P0.1 ({"Cmd":"AugerOff","Id":3})
M9 G4 P0.1 (cayenn)
M8 G4 P0.1 ({"Cmd":"AirOff","Id":4})
M9 G4 P0.1 (cayenn)
G0 A0 X0 (Move linear slide back to 0 while moving X)
M8 G4 P0.1 ({"Cmd":"LinearSlideOff","Id":5})
M9 G4 P0.1 (cayenn)
Notice that when the linear slide is turned on that A axis configuration information is inserted into the Gcode. This is because the A axis is being shared by multiple axes now and thus settings would have to be applied on each change to the A axis to ensure stepper motor settings are correct. This is not necessarily part of Cayenn, but it is related because Cayenn devices can and most likely will attempt to share the A axis (or other axes) and thus inline Gcode configurations could become commonplace.
G21
G0 Z10 (move spindle to clearance height)
G0 X0 Y0 (move to home)
M8 G4 P0.1 ({"CayennDevice":"DispenserPaste","Cmd":"LinearSlideOn","Id":0})
M9 G4 P0.1 (cayenn)
(Configure A axis to linear slide settings. )
{"1":{"sa":18,"tr":0.5," mi":1,"po":1,"p m":3,"pl":0}}
(Axis mode is std like a linear axis.)
{"a":{"am":1,"vm":800,"fr":800,"tn":0,"tm":72}}
M8 G4 P0.1 ({"Cmd":"AirOn","Id":1})
M9 G4 P0.1 (cayenn)
G0 A-70 (Move linear slide down to -70mm)
M8 G4 P0.1 ({"Cmd":"AugerFwd","Speed":5,"Id":2})
M9 G4 P0.1 (cayenn)
(Move 10mm along x axis)
G1 X5 F100 (100mm/min)
M8 G4 P0.1 ({"Cmd":"AugerOff","Id":3})
M9 G4 P0.1 (cayenn)
M8 G4 P0.1 ({"Cmd":"AirOff","Id":4})
M9 G4 P0.1 (cayenn)
G0 A0 X0 (Move linear slide back to 0 while moving X)
M8 G4 P0.1 ({"Cmd":"LinearSlideOff","Id":5})
M9 G4 P0.1 (cayenn)
Notice that when the linear slide is turned on that A axis configuration information is inserted into the Gcode. This is because the A axis is being shared by multiple axes now and thus settings would have to be applied on each change to the A axis to ensure stepper motor settings are correct. This is not necessarily part of Cayenn, but it is related because Cayenn devices can and most likely will attempt to share the A axis (or other axes) and thus inline Gcode configurations could become commonplace.
Sample Pre-Upload of Commands to Cayenn Device
Based on the sample Gcode above, here are the commands that would be pre-uploaded to the Cayenn device from ChiliPeppr into SPJS. SPJS then regurgitates these commands over TCP to the Cayenn device. Notice the counter Id in each command. These correspond to the order of the commands from the sample Gcode above. This list will get auto-created by the Gcode widget so the Id's are accurate.
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":0,"RunCmd":{"Cmd":"LinearSlideOn"}}
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":1,"RunCmd":{"Cmd":"AirOn"}}
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":2,"RunCmd":{"Cmd":"AugerOn","Speed":10}}
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":3,"RunCmd":{"Cmd":"AugerOff"}}
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":4,"RunCmd":{"Cmd":"AirOff"}}
cayenn-sendtcp 10.0.0.154 {"Cmd":"CmdQ","Id":5,"RunCmd":{"Cmd":"LinearSlideOff"}}
This image below is an example of how ChiliPeppr shows you what it's doing when sending the above commands to SPJS, which then forwards to your Cayenn device over TCP so it's a guaranteed transmission.
Sample Lua Code for ESP8266 (Deprecated)
As of Jan 15, 2017 this sample code below for the dispenser it outdated. Please see a newer Cayenn device at https://github.com/chilipeppr/cayenn-laseruv for better, more recent code, known to work.Here is some sample code for a Dispenser running from an ESP8266. The dispenser is shown below. It has a linear slide controlled by a DRV8825 stepper driver, an auger stepper motor, and a TTL toggle for air pressure. It runs on an ESP8266.
dispenser.lua - main entry file that loads all required libraries
-- Dispenser DMP16
-- Linear Slide with stepper driver
-- Auger for DMP16
-- Air Pump toggle
cayenn = require('cayennv2')
cnc = require('tinyg_read')
linearslide = require('drv8825v2')
auger = require('28BYJv3')
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"
-- opts = nil -- dealloc opts
-- define commands supported
cmds = {
"ResetCtr", "GetCmds", "GetQ", "WipeQ", "CmdQ",
"AugerFwd", "AugerRev", "AugerOff",
"AirOn", "AirOff",
"LinearSlideOn", "LinearSlideOff"
}
queue = {}
-- this is called by Cnc when the ID counter changes/increments
-- which occurs when the Coolant pin toggles up/down. each up
-- on the pin increments the counter
function onIdChange(id)
-- we need to execute the cmd associated with this id
-- print("Got Id change")
onCmd(queue[id])
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 == "AugerFwd" then
-- send this cmd to 28BYJ module
-- if payload.Speed == nil then
-- print("Error turning on auger fwd without speed")
-- end
auger.fwd(payload.Speed)
print("Turning auger fwd on. Speed: " .. payload.Speed)
elseif payload.Cmd == "AugerRev" then
-- send this cmd to 28BYJ module
-- if payload.Speed == nil then
-- print("Error turning on auger rev without speed")
-- end
auger.rev(payload.Speed)
print("Turning auger rev on. Speed: " .. payload.Speed)
elseif payload.Cmd == "AugerOff" then
-- send this cmd to 28BYJ module
auger.off()
print("Turning auger off")
elseif payload.Cmd == "AirOn" then
print("Turning air on")
elseif payload.Cmd == "AirOff" then
print("Turning air off")
elseif payload.Cmd == "LinearSlideOn" then
-- tell drv8825 to enable stepper driver
linearslide.on()
print("Enabling linear slide stepper driver")
elseif payload.Cmd == "LinearSlideOff" then
-- tell drv8825 to disable stepper driver
linearslide.off()
print("Disabling linear slide stepper driver")
-- elseif payload.Cmd == "LinearGoTop" then
-- print("Moving linear slide to top")
-- elseif payload.Cmd == "LinearGoBot" then
-- print("Moving linear slide to bottom")
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 Id change from CNC
cnc.addListenerOnIdChange(onIdChange)
cayenn.init(opts)
cnc.init()
linearslide.init()
auger.init()
cayennv2.lua - the Cayenn protocol library. It sets up the Wifi on ESP8266 and the UDP and TCP server. It also lets you send out Announce messages.
-- 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
local bip = wifi.sta.getbroadcast()
--print("Broadcast addr:" .. bip)
local msg = cjson.encode(jsonTagTable)
print("Sending msg: " .. msg .. " to ip: " .. bip)
M.sock:connect(M.port, bip)
M.sock:send(msg)
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()
-- 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)
tinyg_read.lua - This library watches the Coolant on/off pin to generate the counter and provide a callback to the main entry Lua code so it can act on the coolant counter changes.
-- 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, "up", 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 m.pinCoolantCallback(level)
gpio.trig(m.pinCoolant, "up")
-- this method is called when the coolant pin has an interrupt
m.idCounter = m.idCounter + 1
m.onIdChange()
print("Got coolant pin. Level: " .. level .. " idCounter: " .. m.idCounter)
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()
drv8825v2.lua - This library talks to a stepper motor driver like DRV8825 which is a very common/cheap stepper motor driver. For the dispenser this stepper driver controls the linear slide that raises the dispenser up/down.
-- Control DRV8825 Stepper Motor
local m = {}
-- m = {}
-- which timer are we using so we get no conflicts
-- not using
-- m.tmrNum = 5
-- m.tmrWaitOnStepNum = 4
-- m.pinDir = 1 -- not using
-- m.pinStep = 2 -- not using
-- m.pinSleep = 2
-- m.pinReset = 3
m.pinEnable = 0
-- m.curDir = "f"
m.curEnable = false
function m.init()
-- gpio.mode(m.pinDir, gpio.OUTPUT)
-- gpio.mode(m.pinStep, gpio.OUTPUT)
gpio.mode(m.pinEnable, gpio.OUTPUT)
-- gpio.mode(motor.pinSleep, gpio.OUTPUT)
-- gpio.mode(motor.pinReset, gpio.OUTPUT)
-- gpio.write(m.pinDir, gpio.LOW)
-- gpio.write(m.pinStep, gpio.LOW)
-- LOW is enable. HIGH is off.
m.off()
gpio.write(m.pinEnable, gpio.HIGH)
-- gpio.write(motor.pinSleep, gpio.LOW)
-- gpio.write(motor.pinReset, gpio.LOW)
print("Setup stepper driver.")
end
function m.off()
if m.curEnable then
-- gpio.write(motor.pin1, gpio.LOW)
-- gpio.write(motor.pin2, gpio.LOW)
-- gpio.write(motor.pin3, gpio.LOW)
-- gpio.write(motor.pin4, gpio.LOW)
-- print("Turned off all outputs")
print("Turning driver off.")
-- LOW is enable
gpio.write(m.pinEnable, gpio.HIGH)
m.curEnable = false
else
print("Driver off already")
end
end
function m.on()
if m.curEnable == false then
-- enable it
-- LOW is enable
gpio.write(m.pinEnable, gpio.LOW)
m.curEnable = true
print("Turning driver on")
else
print("Driver on already")
end
end
return m
-- m.init()
28BYJv3.lua - This library rotates the small/cheap 5 phase stepper motor that rotates the auger on the dispenser.
-- 28BYJ-48 Stepper Control
-- This Motor has a Gear ratio of 64 , and Stride Angle 5.625° so this motor has a 4096 Steps.
-- steps = Number of steps in One Revolution * Gear ratio .
-- steps= (360°/5.625°)*64"Gear ratio" = 64 * 64 =4096 . this value will
local motor = {}
-- motor = {}
motor.pin1 = 1 -- this is GPIO14
motor.pin2 = 2 -- this is GPIO12
motor.pin3 = 6 -- this is GPIO13
motor.pin4 = 7 -- this is GPIO15
motor.speed = 20 -- ms between each step call
motor.abspos = 0 -- keep track of step pos with abs integer
function motor.init()
-- Setup motor pins as out
gpio.mode(motor.pin1, gpio.OUTPUT)
gpio.mode(motor.pin2, gpio.OUTPUT)
gpio.mode(motor.pin3, gpio.OUTPUT)
gpio.mode(motor.pin4, gpio.OUTPUT)
print("Setup all motors as output")
motor.off()
end
function motor.off()
tmr.stop(6) -- stop the step timer
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.LOW)
print("Turned all motor pins off")
end
function motor.fwd(speed)
if speed then
motor.speed = speed
end
tmr.stop(6)
tmr.alarm(6, motor.speed, tmr.ALARM_AUTO, motor.fwdStep)
end
function motor.rev(speed)
if speed then
motor.speed = speed
end
tmr.stop(6)
tmr.alarm(6, motor.speed, tmr.ALARM_AUTO, motor.revStep)
end
function motor.fwdStep()
motor.step(1)
end
function motor.revStep()
motor.step(-1)
end
-- Step
motor.loc = 1
function motor.step(val)
motor.loc = motor.loc + val
-- this could overflow
motor.abspos = motor.abspos + val
if motor.loc == 9 then
motor.loc = 1
elseif motor.loc == 0 then
motor.loc = 9
end
--print("step " .. motor.loc)
if motor.loc == 1 then
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.HIGH)
elseif motor.loc == 2 then
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.HIGH)
gpio.write(motor.pin4, gpio.HIGH)
elseif motor.loc == 3 then
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.HIGH)
gpio.write(motor.pin4, gpio.LOW)
elseif motor.loc == 4 then
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.HIGH)
gpio.write(motor.pin3, gpio.HIGH)
gpio.write(motor.pin4, gpio.LOW)
elseif motor.loc == 5 then
gpio.write(motor.pin1, gpio.LOW)
gpio.write(motor.pin2, gpio.HIGH)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.LOW)
elseif motor.loc == 6 then
gpio.write(motor.pin1, gpio.HIGH)
gpio.write(motor.pin2, gpio.HIGH)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.LOW)
elseif motor.loc == 7 then
gpio.write(motor.pin1, gpio.HIGH)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.LOW)
elseif motor.loc == 8 then
gpio.write(motor.pin1, gpio.HIGH)
gpio.write(motor.pin2, gpio.LOW)
gpio.write(motor.pin3, gpio.LOW)
gpio.write(motor.pin4, gpio.HIGH)
end
end
return motor
-- motor.init()