Wednesday, September 6, 2017

Auto Starting Cloud9 on a Raspberry Pi (Linux)

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

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:

  • 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 file
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

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")