Tuesday, October 4, 2016

Toggling a Relay Between Two NodeMCU ESP8266 Devices

This code lets you use two NodeMCU ESP8266 devices communicate to each other to toggle a relay to turn lights on and off. I used this to create a light switch at the top of the stairs to control the main light switch at the bottom of the stairs.

This is the client NodeMCU code called client.lua and it runs on the client device.

-- Basement Client Switch
-- This code runs a simple toggle switch where it sends a udp "toggle"
-- command to the basement relay.
-- The hardest part is discovery of which IP address to send to.
-- To do this we simply send out a broadcast message for now.

M = {}

M.port = 8988
M.myip = nil
M.sock = nil
M.isInitted = false

local pin, pulse1
pin = 5
pulse1 = tmr.now()

gpio.mode(pin, gpio.INT, gpio.PULLUP)

function pin1cb(level)
  local pulse2 = tmr.now()
  M.sendCustomBroadcast("toggle")
  print("Switched", level, pulse2 - pulse1 )
  pulse1 = pulse2
  -- gpio.trig(pin, level == gpio.HIGH  and "down" or "up")
  gpio.trig(pin, "both")
end

gpio.trig(pin, "both", pin1cb)
print("setup trigger")

ledState = gpio.LOW
ledCtr = 0
ledCurrentCnt = 0

function blinkLed(ctr)
  local pin = 4
  ledCtr = ctr 
  ledCurrentCnt = 0
  gpio.mode(pin, gpio.OUTPUT)
  tmr.alarm(6, 100, tmr.ALARM_AUTO, blinkLedCallback)
end

function blinkLedCallback()
  print("blink", ledCurrentCnt, ledCtr)
  
  if ledCurrentCnt >= ledCtr then
    print("Stopping LED blink")
    tmr.stop(6)
    gpio.write(4, gpio.HIGH)
    return
  end
  
  if ledState == gpio.LOW then
    ledState = gpio.HIGH
  else
    ledState = gpio.LOW
  end
  gpio.write(4, ledState)
  ledCurrentCnt = ledCurrentCnt + 1 
end

blinkLed(6)

function M.init()
  print("Initting...")
  M.sock = net.createConnection(net.UDP, 0)
  M.myip = wifi.sta.getip()
  if M.myip == nil then
    print("You need to connect to wifi. Unable to init.")
  else 
    print("My IP: " .. M.myip)
    M.isInitted = true
  end 
end

function M.setupWifi()
  -- setwifi
  wifi.setmode(wifi.STATION)
  -- longest range is b
  wifi.setphymode(wifi.PHYMODE_B)
  --Connect to access point automatically when in range
  wifi.sta.config("NETGEAR-main", "blah")
  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)
  M.init()
  blinkLed(10)
  -- M.initUdpServer()
  -- M.initTcpServer()
  M.sendCustomBroadcast("toggle")
end

function M.sendCustomBroadcast(msg)
  if M.isInitted == false then
    print("You must init first.")
    return 
  end
  
  local bip = wifi.sta.getbroadcast()
  --print("Broadcast addr:" .. bip)
  
  print("Sending msp to ip: " .. bip .. " " .. msg)
  M.sock:connect(M.port, bip)
  M.sock:send(msg)
  M.sock:close()
  
end

M.setupWifi()
--M.init()

This is the server code, i.e. the device running the relay.

This is the basement_main.lua file for the server.

-- Main entry file for Basement Relay
-- We include the basement_relay file which is the object that controls the Relay
-- We include the basement_udp file which is the UDP server where we listen for on/off commands

server = require("basement_udp")
relay = require("basement_relay")

server.setupWifi()
server.init()

function onUdpCallback(msg)
  if msg then
    
    print("Got udp msg:" .. msg)
    if msg == "on" then
      relay.on()
    elseif msg == "off" then
      relay.off()
    elseif msg == "toggle" then
      relay.toggle()
    else 
      print("got cmd do not understand")
    end
    
  else
    print("Got nil udp msg")
  end

end

server.registerCallbackOnUdpRecv(onUdpCallback)

This is the basement_relay code for the server.

-- Toggle Relay
local M = {} 
M = {} 
M.pin = 5
M.isOn = false
M.isInitted = false

function M.init()
  if M.isInitted then return end
  -- setup led as output and turn on
  gpio.mode(M.pin, gpio.OUTPUT) --, gpio.PULLUP)
  M.isInitted = true
  print("Initted LED. pin: " .. M.pin)
end

function M.on()
  M.init()
  gpio.write(M.pin, gpio.LOW)
  M.isOn = true
  print("Turned LED on. pin: " .. M.pin)
end

function M.off()
  M.init()
  gpio.write(M.pin, gpio.HIGH)
  M.isOn = false
  print("Turned LED off. pin: " .. M.pin)
end

function M.toggle()
  if M.isOn then
    M.off()
  else
    M.on()
  end
end

return M

-- relay = M
-- led.on()
-- tmr.alarm(0, 4000, tmr.ALARM_AUTO, relay.toggle)

This is basement_udp.lua

-- UDP Hello Announce v3
--local M = {}
M = {}

M.port = 8988
M.myip = nil
M.sock = nil
M.callbackOnUdpRecv = nil -- a callback can be registered so that when we recv udp data we will call your method

--M.announce = 

M.isInitted = false

function M.init()
  print("Initting...")
  M.sock = net.createConnection(net.UDP, 0)
  M.myip = wifi.sta.getip()
  if M.myip == nil then
    print("You need to connect to wifi. Unable to init.")
  else 
    print("My IP: " .. M.myip)
    M.isInitted = true
  end 
end

function M.createAnnounce(jsonTagTable)
  
  -- see if there is a jsontagtable passed in as extra meta
  local jsontag = ""
  if jsonTagTable ~= nil then
    ok, jsontag = pcall(cjson.encode, jsonTagTable)
    if ok then
      --print("Adding jsontagtable" .. jsontag)
    else
      print("failed to encode jsontag!")
    end
  end

  local a = {}
  a.Announce = "i-am-a-client"
  a.Widget = "com-chilipeppr-widget-basementrelay"
  a.MyDeviceId = "chip:" .. node.chipid() .. "-flash:" .. node.flashid() .. "-mac:" .. wifi.sta.getmac()
  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
  return json
end

-- send announce to broadcast addr so spjs 
-- knows of our existence
function M.sendBroadcast()
  if M.isInitted == false then
    print("You must init first.")
    return 
  end
  
  local bip = wifi.sta.getbroadcast()
  --print("Broadcast addr:" .. bip)
  
  local annStr = M.createAnnounce()
  print("Sending announce to ip: " .. bip .. " " .. annStr)
  M.sock:connect(M.port, bip)
  M.sock:send(annStr)
  M.sock:close()
  
end

function M.setupWifi()
  -- setwifi
  wifi.setmode(wifi.STATION)
  -- longest range is b
  wifi.setphymode(wifi.PHYMODE_B)
  --Connect to access point automatically when in range
  wifi.sta.config("NETGEAR-main", "blah")
  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)
  M.init()
  M.initUdpServer()
  M.initTcpServer()
  M.sendBroadcast()
end

function M.sendinit()
  -- we need to send a UDP announce packet to every IP
  -- on the subnet to let any possible SPJS know we are
  -- available
 
  -- iterate thru entire subnet
  M.ipctr = 1
  tmr.alarm(6, 200, tmr.ALARM_SINGLE, M.sendNextHello)

end

function M.sendNextHello()
  
  local i, j = string.find(M.myip, "%.%d+$")
  local prefix = string.sub(M.myip, 0, i)
  local ip = prefix .. M.ipctr
  print("Sending announce to ip: " .. ip)
  M.sock:connect(M.port, ip)
  M.sock:send(M.announce)
  M.sock:close()
  M.ipctr = M.ipctr + 1

  if M.ipctr < 25 then
    tmr.alarm(6, 50, tmr.ALARM_SINGLE, M.sendNextHello)
  end

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(M.port)
  print("UDP Server started on port " .. M.port)
end

function M.onUdpConnection(sck)
  print("UDP connection.")
  --ip, port = sck:getpeer()
  --print("UDP connection. from: " .. ip)
end

function M.registerCallbackOnUdpRecv(callback)
  M.callbackOnUdpRecv = callback  
end

function M.onUdpRecv(sck, data)
  print("UDP Recvd. data: " .. data)
  if M.callbackOnUdpRecv then
    M.callbackOnUdpRecv(data)
  end
  
end

function M.initTcpServer()
  M.tcpServer = net.createServer(net.TCP)
  M.tcpServer:listen(M.port, M.onTcpListen)
  
  print("TCP Server started on port " .. M.port)
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)
end

return M

-- M.setupWifi()
-- M.init()

--M.initServer()
--M.sendBroadcast()

-- function sampleOnUdpCallback(msg)
--   print("Got udp msg:" .. msg)
-- end

-- M.registerCallbackOnUdpRecv(sampleOnUdpCallback)






No comments:

Post a Comment