Skip to content

Instantly share code, notes, and snippets.

@edleno2

edleno2/init.lua Secret

Last active March 2, 2022 02:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edleno2/95948892898a6c90f3dc9b1949025ef2 to your computer and use it in GitHub Desktop.
Save edleno2/95948892898a6c90f3dc9b1949025ef2 to your computer and use it in GitHub Desktop.
Code and configuration for Smartthings Edge LAN driver for motorized oxygen concentrator control valve
-- init.lua
local log = require "log"
local capabilities = require "st.capabilities"
local utils = require "st.utils"
local oxygenMotorValveCap = capabilities["fluteconnect35136.oxygenMotorValve"]
local Driver = require "st.driver"
local socket = require "socket"
--local udpListen = socket.udp()
local udpListen2 = socket.udp()
local tcpListen = socket.tcp()
local utils = require "st.utils"
local lastAssignedRoom = 0
local lastHeardFrom = 0;
local multicastIP = "239.192.1.1"
local multicastPort = 2290
local offlineTimeInterval = 120 -- how long before setting device offline
local function handle_discovery(driver, _should_continue)
log.info("Starting Oxygen Motor Control Discovery")
local metadata = {
type = "LAN",
-- the DNI must be unique across your hub, using static ID here so that we
-- only ever have a single instance of this "device"
device_network_id = "OxygenMotorControl",
label = "Oxygen Motor Control Device",
profile = "oxygen-motor-control.v1",
manufacturer = "edleno",
model = "v1",
vendor_provided_label = nil
}
-- tell the cloud to create a new device record, will get synced back down
-- and `device_added` and `device_init` callbacks will be called
driver:try_create_device(metadata)
end
-- this is called once a device is added by the cloud and synchronized down to the hub
local function device_added(driver, device)
log.info("[" .. device.id .. "] Adding new Oxygen Motor Control device")
-- set a default or queried state for each capability attribute
device:emit_event(oxygenMotorValveCap.oxygenMotorValve.roomOneOn())
end
-- this is called both when a device is added (but after `added`) and after a hub reboots.
local function device_init(driver, device)
log.info("[" .. device.id .. "] Initializing Oxygen Motor Control device")
-- mark device as online so it can be controlled from the app
device:online()
lastHeardFrom = os.time() -- keep from setting offline immediately - wait for health timeout period
-- timer for refresh - isn't really needed since the udp check happens 1x/second
-- set refresh schedule
-- device.thread:call_on_schedule(
-- 30,
-- function ()
-- return Refresh(nil, device)
-- end,
-- 'Refresh schedule')
-- register a callback on the udp multicast status messages (it doesn't appear to work though)
local internals = require "cosock.socket.internals"
log.info("Setting up udpListen2 during device init")
assert(udpListen2:setoption('reuseaddr', true))
assert(udpListen2:setoption("ip-add-membership", {multiaddr = multicastIP, interface = "0.0.0.0"}), "join multicast group")
assert(udpListen2:settimeout(0))
assert(udpListen2:setsockname(multicastIP, multicastPort))
device.thread:call_on_schedule(
2, -- won't need this frequency if we can get register_socket to work
function ()
return CheckForUdpMessage(nil, device)
end,
'Refresh schedule')
-- try setting up a tcp socket to see if tcp works since udp doesn't appear to work (it doesn't work either)
log.info("setting up tcplisten socket")
tcpListen:settimeout(5)
tcpListen:connect("10.0.0.210", 2001)
internals.setuprealsocketwaker(tcpListen)
device.thread:register_socket(
tcpListen,
function ()
return OnSocketReceived
end,
"tcpListen event"
)
-- force initial refresh
Refresh(driver, device, nil)
log.info("end of init");
end
function CheckForUdpMessage(nullVal, device)
--log.info("Checking for UDP data in timer")
-- timeout is set for zero, so no delays, just see if a message is "stacked up"
local parseResult, returnIp, returnPort = udpListen2:receivefrom()
--log.info("back from receivefrom()")
if parseResult then
log.info("Data from IP: " .. tostring(returnIp) .. " port: " .. returnPort .. " data: " .. parseResult)
if parseResult:find("^CurrentRoom:") then
lastHeardFrom = os.time()
local currentRoom = parseResult:sub(17,17)
log.info("Current room is: " ..currentRoom)
device:online()
if currentRoom == lastAssignedRoom then
-- do nothing
else
if currentRoom == "1" then
log.info("turning room one on")
--device:emit_event(oxygenMotorValveCap.oxygenMotorValve.roomOneOn())
--device:emit_event(capabilities.toggleSwitch.toggleSwitch.On())
device:emit_event(oxygenMotorValveCap.oxygenMotorValve("Room1"))
else
log.info("turning room two on")
--device:emit_event(oxygenMotorValveCap.oxygenMotorValve.roomTwoOn())
--device:emit_event(capabilities.toggleSwitch.toggleSwitch.Off())
device:emit_event(oxygenMotorValveCap.oxygenMotorValve("Room2"))
end
end
lastAssignedRoom = currentRoom
-- see if rssi info is in the room info
local rssiIndex = parseResult:find("|rssi:")
if rssiIndex > 0 then
local rssiVal = parseResult:sub(rssiIndex+6)
log.info("rssiVal: " .. rssiVal)
if tonumber(rssiVal) then
local min_ed = -90
local max_ed = -5
local rssi = tonumber(rssiVal)
local lqi = math.floor((255*(rssiVal-min_ed))/(max_ed - min_ed))
log.info("lqi: " .. lqi)
device:emit_event(capabilities.signalStrength.rssi(rssi))
device:emit_event(capabilities.signalStrength.lqi(lqi))
end
end
end
end
-- while we are here check for staleness
if os.time() - lastHeardFrom > offlineTimeInterval then
log.info("setting device offline due to staleness")
device:offline() -- normally status is sent out to keep from going stale
lastHeardFrom = os.time(); -- will repeat the message above each interval
lastAssignedRoom = 0
end
end
function OnSocketReceived(args)
log.info("got a socket callback args[" .. args .. "]")
end
-- this is called when a device is removed by the cloud and synchronized down to the hub
local function device_removed(driver, device)
log.info("[" .. device.id .. "] Removing Oxygen Motor Control device")
end
-- local function handle_on(driver, device, command)
-- log.info("On: Sending ChangeTo:Room1 to motor control")
-- assert(udpListen2:sendto('RequestChange:Room1', multicastIP, multicastPort))
-- device:emit_event(oxygenMotorValveCap.oxygenMotorValve("Room1"))
-- end
-- local function handle_off(driver, device, command)
-- log.info("Off: Sending ChangeTo:Room2 to motor control")
-- assert(udpListen2:sendto("RequestChange:Room2", multicastIP, multicastPort))
-- device:emit_event(oxygenMotorValveCap.oxygenMotorValve("Room2"))
-- end
local function oxygen_motor_valve_set_handler (driver, device, command)
log.info("called oxygen motor valve set handler")
log.info(utils.stringify_table(command))
log.info("state: " .. command.args.state)
assert(udpListen2:sendto("RequestChange:" .. command.args.state, multicastIP, multicastPort))
device:emit_event(oxygenMotorValveCap.oxygenMotorValve(command.args.state))
end
function Refresh (driver, device, command)
log.info("Doing refresh logic")
udpListen2:sendto("RefreshStatus", multicastIP, multicastPort) -- response will come back thru the register_socket call back (hmmm...doesn't work)
-- the check for udp messages will do the actual refresh within a second
end
-- Driver library initialization
local oxygen_motor_control_driver =
Driver(
"oxygen_motor_control_driver",
{
supported_capabilities =
{
oxygenMotorValveCap,
capabilities.refresh
},
discovery = handle_discovery,
lifecycle_handlers =
{
added = device_added,
init = device_init,
removed = device_removed
},
capability_handlers =
{
[oxygenMotorValveCap.ID] =
{
--[oxygenMotorValveCap.commands.roomOneOn.NAME] = handle_on,
--[oxygenMotorValveCap.commands.roomTwoOn.NAME] = handle_off,
[oxygenMotorValveCap.commands.roomSet.NAME] = oxygen_motor_valve_set_handler,
},
-- Refresh command handler
[capabilities.refresh.ID] =
{
[capabilities.refresh.commands.refresh.NAME] = Refresh
}
}
}
)
-- Put other setup code here
log.info("Setting up the oxygen_motor_control_driver object")
oxygen_motor_control_driver:run()
name: Oxygen Motor Valve
attributes:
oxygenMotorValve:
schema:
type: object
properties:
value:
type: string
enum:
- 'Room1'
- 'Room2'
additionalProperties: false
required:
- value
setter: roomSet
enumCommands: []
commands:
roomSet:
name: roomSet
arguments:
- name: state
optional: false
schema:
type: string
enum:
- 'Room1'
- 'Room2'
id: fluteconnect35136.oxygenMotorvalve
version: 1
mnmn: SmartThingsCommunity
vid: e9d180f8-40a6-3367-b189-6caf2fa637ff
version: 0.0.1
type: profile
dashboard:
states:
- component: main
capability: fluteconnect35136.oxygenMotorValve
version: 1
idx: 0
group: main
values: []
actions:
- component: main
capability: fluteconnect35136.oxygenMotorValve
version: 1
idx: 0
group: main
detailView:
- component: main
capability: fluteconnect35136.oxygenMotorValve
version: 1
values: []
patch: []
- component: main
capability: refresh
version: 1
values: []
patch: []
- component: main
capability: signalStrength
version: 1
values: []
patch: []
automation:
conditions:
- component: main
capability: fluteconnect35136.oxygenMotorValve
version: 1
values: []
patch: []
exclusion: []
actions:
- component: main
capability: fluteconnect35136.oxygenMotorValve
version: 1
values: []
patch: []
exclusion: []
- component: main
capability: refresh
version: 1
values: []
patch: []
exclusion: []
- component: main
capability: signalStrength
version: 1
values: []
patch: []
presentationId: e9d180f8-40a6-3367-b189-6caf2fa637ff
manufacturerName: SmartThingsCommunity
dashboard:
states:
- label: '{{oxygenMotorValve.value}}'
alternatives:
- key: 'Room1'
value: 'Room 1'
type: 'active'
- key: 'Room2'
value: 'Room 2'
type: 'active'
actions:
- displayType: toggleSwitch
toggleSwitch:
command:
name: roomSet
'on': 'Room1'
'off': 'Room2'
state:
value: oxygenMotorValve.value
'on': 'Room1'
'off': 'Room2'
basicPlus: []
detailView:
- label: Oxygen Valve
displayType: toggleSwitch
toggleSwitch:
command:
name: roomSet
'on': 'Room1'
'off': 'Room2'
state:
value: oxygenMotorValve.value
'on': 'Room1'
'off': 'Room2'
label: '{{oxygenMotorValve.value}}'
alternatives:
- key: 'Room1'
value: 'Room 1'
type: 'active'
- key: 'Room2'
value: 'Room 2'
type: 'active'
id: fluteconnect35136.oxygenMotorValve
version: 1
# oxygen-motor-control-profile
name: oxygen-motor-control.v1
components:
- id: main
capabilities:
- id: "fluteconnect35136.oxygenMotorValve"
version: 1
- id: refresh
version: 1
- id: signalStrength
version: 1
categories:
- name: Switch
metadata:
mnmn: SmartThingsCommunity
vid: e9d180f8-40a6-3367-b189-6caf2fa637ff
@edleno2
Copy link
Author

edleno2 commented Mar 1, 2022

Actual device is ESP32 using Wifi and UDP to control a motorized valve that can switch concentrated oxygen between two rooms (labelled Room1 and Room2). Basic concept is to replace a simple on/off switch with a Room1/Room2 switch.

@edleno2
Copy link
Author

edleno2 commented Mar 2, 2022

The current revision of the code is the code that works after this thread with @nayeylz: https://community.smartthings.com/t/cant-get-my-edge-custom-capability-switch-to-work-missing-something/240185

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment