Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions drivers/SmartThings/wemo/src/command_handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ function command_handlers.handle_refresh(driver, device)
end
end

function command_handlers.handle_reset_energy_meter(driver, device)
device.log.warn("Wemo has no documented api to reset energy meter")
end

return command_handlers
7 changes: 5 additions & 2 deletions drivers/SmartThings/wemo/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ local utils = require "st.utils"

-- maps model name to profile name
local profiles = {
["Insight"] = "wemo.mini-smart-plug.v1",
["Insight"] = "wemo.insight-smart-plug.v1",
["Socket"] = "wemo.mini-smart-plug.v1",
["Dimmer"] = "wemo.dimmer-switch.v1",
["Motion"] = "wemo.motion-sensor.v1",
Expand Down Expand Up @@ -249,7 +249,10 @@ local wemo = Driver("wemo", {
},
[capabilities.refresh.ID] = {
[capabilities.refresh.commands.refresh.NAME] = command_handlers.handle_refresh,
}
},
[capabilities.energyMeter.ID] = {
[capabilities.energyMeter.commands.resetEnergyMeter.NAME] = command_handlers.handle_reset_energy_meter,
},
}
})

Expand Down
43 changes: 37 additions & 6 deletions drivers/SmartThings/wemo/src/parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,28 @@ end

local function handle_binary_state(device, value)
-- parses wemo insight style ("8|1611850428|58|...") state, works just fine for a single value too
local vals = string.gmatch(value, "%d+")
-- map all values to numbers
-- note: this really does need `or nil`, `tonumber()` is an error, `tonumber(nil)` returns nil
local numvals = function() return tonumber(vals() or 0) end
local result = {}
local lut = {
"state",
"last_changed_timestamp",
"last_on_for_s",
"on_today_s",
"on_total_s",
"timespan_s",
"avg_power_W",
"current_power_mW",
"energy_today_Wh",
"energy_total_Wh",
"standby_limit_mW",
}
-- Parse the data and insert into the result_table
local iter = value:gmatch("([^|]+)")
for _, name in ipairs(lut) do
result[name] = tonumber(iter() or 0)
end

-- power state
local state = numvals()
local state = result.state
if state == 0 then
if device:supports_capability_by_id("switch") then
device:emit_event(capabilities.switch.switch("off"))
Expand All @@ -55,7 +70,17 @@ local function handle_binary_state(device, value)
log.warn("parse| BinaryState event on device that supports neither `switch` nor `motionSensor`")
end
end
-- TODO: there's a bunch more values from insight plugs, what do they mean?

if result.current_power_mW ~= nil and result.energy_today_Wh ~= nil and
device:supports_capability_by_id("powerMeter") and device:supports_capability_by_id("energyMeter") then
device:emit_event(capabilities.powerMeter.power(result.current_power_mW / 1000)) --ST uses watts by default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you felt safe always emitting the power events?

--Sometimes total energy reported is way off, in that case use the daily energy reported
if result.energy_today_Wh > result.energy_total_Wh then
device:emit_event(capabilities.energyMeter.energy({value = result.energy_today_Wh, unit = "Wh"}))
else
device:emit_event(capabilities.energyMeter.energy({value = result.energy_total_Wh, unit = "Wh"}))
end
end
end

local function handle_brightness(device, value)
Expand Down Expand Up @@ -111,6 +136,12 @@ function parser.parse_get_state_resp_xml(device, xml)
log.trace("parse| brightness", brightness)
handle_brightness(device, brightness)
end

local insight = tablefind(parsed_xml, "s:Envelope.s:Body.u:GetInsightParamsResponse.InsightParams")
if insight then
log.trace("parse| insight_params", insight)
handle_binary_state(device, insight)
end
end

return parser
42 changes: 40 additions & 2 deletions drivers/SmartThings/wemo/src/protocol.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ function protocol.poll(device)
}
}

-- TODO: some retries needed here to get device health if we timeout

if resp == nil then
log.warn_with({hub_logs=true},
string.format("proto| retry sending poll request for %s: %s", device.label, code_or_err))
Expand Down Expand Up @@ -99,6 +97,10 @@ function protocol.poll(device)
local resp_body = table.concat(response_chunks)
parser.parse_get_state_resp_xml(device, resp_body)
end

if device:supports_capability_by_id("powerMeter") then
protocol.get_insight_params(device)
end
end

function protocol.subscribe(device, listen_ip, listen_port)
Expand Down Expand Up @@ -237,4 +239,40 @@ function protocol.send_switch_level_cmd(device, level)
end
end

function protocol.get_insight_params(device)
local ip, port = get_ip_and_port(device)
if not (ip and port) then
return
end


local body = string.format(request_wrapper, [[<u:GetInsightParams xmlns:u="urn:Belkin:service:insight:1"></u:GetInsightParams>]])

local response_body = {}
log.trace(string.format("proto| %s get_insight_params", device.label))
local resp, code_or_err, _, status_line = http.request {
url = "http://" .. ip .. ":" .. port .. "/upnp/control/insight1",
method = "POST",
sink = ltn12.sink.table(response_body),
source = ltn12.source.string(body),
headers = {
["SOAPAction"] = [["urn:Belkin:service:insight:1#GetInsightParams"]],
["Content-Type"] = "text/xml",
["Host"] = ip .. ":" .. port,
["Content-Length"] = #body
}
}

if resp == nil or code_or_err ~= 200 then
log.warn_with({ hub_logs = true }, string.format(
"proto| %s get_insight_params failed with error code %s and status: %s",
device.label, code_or_err, status_line
))
else
local resp_body = table.concat(response_body)
parser.parse_get_state_resp_xml(device, resp_body)
end

end

return protocol
Loading