-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
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
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.