Skip to content

Added support for frient switch devices #2208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
55 changes: 45 additions & 10 deletions drivers/SmartThings/zigbee-switch/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -441,30 +441,65 @@ zigbeeManufacturer:
deviceProfileName: switch-power-energy
#frient A/S
- id: "frient A/S/SPLZB-131"
deviceLabel: "frient Outlet"
deviceLabel: "Smart Plug Mini"
manufacturer: frient A/S
model: SPLZB-131
deviceProfileName: switch-power-energy
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-132"
deviceLabel: "frient Outlet"
deviceLabel: "Smart Plug Mini"
manufacturer: frient A/S
model: SPLZB-132
deviceProfileName: switch-power-energy
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-134"
deviceLabel: "frient Outlet"
deviceLabel: "Smart Plug Mini UK"
manufacturer: frient A/S
model: SPLZB-134
deviceProfileName: switch-power-energy
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-137"
deviceLabel: "frient Outlet"
deviceLabel: "Smart Plug Mini"
manufacturer: frient A/S
model: SPLZB-137
deviceProfileName: switch-power-energy
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-141"
deviceLabel: "Smart Plug Mini 2"
manufacturer: frient A/S
model: SPLZB-141
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-142"
deviceLabel: Smart Plug Mini 2
manufacturer: frient A/S
model: SPLZB-142
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-144"
deviceLabel: Smart Plug Mini UK 2
manufacturer: frient A/S
model: SPLZB-144
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SPLZB-147"
deviceLabel: "Smart Plug Mini 2"
manufacturer: frient A/S
model: SPLZB-147
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SMRZB-143"
deviceLabel: "frient Outlet"
deviceLabel: "Smart Cable"
manufacturer: frient A/S
model: SMRZB-143
deviceProfileName: switch-power-energy
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SMRZB-153"
deviceLabel: "Smart Cable 2"
manufacturer: frient A/S
model: SMRZB-153
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SMRZB-332"
deviceLabel: "Smart Din Relay"
manufacturer: frient A/S
model: SMRZB-332
deviceProfileName: frient-switch-power-energy-voltage
- id: "frient A/S/SMRZB-342"
deviceLabel: Smart DIN Relay 2
manufacturer: frient A/S
model: SMRZB-342
deviceProfileName: frient-switch-power-energy-voltage
- id: "AduroSmart Eria/AD-DimmableLight3001"
deviceLabel: Eria Light
manufacturer: AduroSmart Eria
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: frient-switch-power-energy-voltage
components:
- id: main
capabilities:
- id: switch
version: 1
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: voltageMeasurement
version: 1
- id: currentMeasurement
version: 1
- id: powerSource
version: 1
config:
values:
- key: "powerSource.value"
enabledValues:
- unknown
- mains
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: SmartPlug

66 changes: 65 additions & 1 deletion drivers/SmartThings/zigbee-switch/src/configurations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ local device_def = require "st.device"

local ColorControl = clusters.ColorControl
local IASZone = clusters.IASZone
local ElectricalMeasurement = clusters.ElectricalMeasurement
local SimpleMetering = clusters.SimpleMetering
local Alarms = clusters.Alarms
local Status = require "st.zigbee.generated.types.ZclStatus"

local CONFIGURATION_VERSION_KEY = "_configuration_version"
Expand Down Expand Up @@ -63,7 +66,68 @@ local devices = {
}
},
IAS_ZONE_CONFIG_METHOD = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE
}
},
FRIENT_SWITCHES = {
FINGERPRINTS = {
{ mfr = "frient A/S", model = "SPLZB-131" },
{ mfr = "frient A/S", model = "SPLZB-132" },
{ mfr = "frient A/S", model = "SPLZB-134" },
{ mfr = "frient A/S", model = "SPLZB-137" },
{ mfr = "frient A/S", model = "SPLZB-141" },
{ mfr = "frient A/S", model = "SPLZB-142" },
{ mfr = "frient A/S", model = "SPLZB-144" },
{ mfr = "frient A/S", model = "SPLZB-147" },
{ mfr = "frient A/S", model = "SMRZB-143" },
{ mfr = "frient A/S", model = "SMRZB-153" },
{ mfr = "frient A/S", model = "SMRZB-332" },
{ mfr = "frient A/S", model = "SMRZB-342" }
},
CONFIGURATION = {
{
cluster = ElectricalMeasurement.ID,
attribute = ElectricalMeasurement.attributes.RMSVoltage.ID,
minimum_interval = 5,
maximum_interval = 3600,
data_type = ElectricalMeasurement.attributes.RMSVoltage.base_type,
reportable_change = 1
},{
cluster = ElectricalMeasurement.ID,
attribute = ElectricalMeasurement.attributes.RMSCurrent.ID,
minimum_interval = 5,
maximum_interval = 3600,
data_type = ElectricalMeasurement.attributes.RMSCurrent.base_type,
reportable_change = 1
},{
cluster = ElectricalMeasurement.ID,
attribute = ElectricalMeasurement.attributes.ActivePower.ID,
minimum_interval = 5,
maximum_interval = 3600,
data_type = ElectricalMeasurement.attributes.ActivePower.base_type,
reportable_change = 1
},{
cluster = SimpleMetering.ID,
attribute = SimpleMetering.attributes.InstantaneousDemand.ID,
minimum_interval = 5,
maximum_interval = 3600,
data_type = SimpleMetering.attributes.InstantaneousDemand.base_type,
reportable_change = 1
},{
cluster = SimpleMetering.ID,
attribute = SimpleMetering.attributes.CurrentSummationDelivered.ID,
minimum_interval = 5,
maximum_interval = 3600,
data_type = SimpleMetering.attributes.CurrentSummationDelivered.base_type,
reportable_change = 1
},{
cluster = Alarms.ID,
attribute = Alarms.attributes.AlarmCount.ID,
minimum_interval = 1,
maximum_interval = 3600,
data_type = Alarms.attributes.AlarmCount.base_type,
reportable_change = 1,
},
}
},
}


Expand Down
199 changes: 199 additions & 0 deletions drivers/SmartThings/zigbee-switch/src/frient/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
-- Copyright 2025 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

local capabilities = require "st.capabilities"
local clusters = require "st.zigbee.zcl.clusters"
local constants = require "st.zigbee.constants"
local log = require "log"
local configurationMap = require("configurations")

local SimpleMetering = clusters.SimpleMetering
local ElectricalMeasurement = clusters.ElectricalMeasurement
local Alarms = clusters.Alarms

local VOLTAGE_MEASUREMENT_MULTIPLIER_KEY = "_voltage_measurement_multiplier"
local VOLTAGE_MEASUREMENT_DIVISOR_KEY = "_voltage_measurement_divisor"
local CURRENT_MEASUREMENT_MULTIPLIER_KEY = "_current_measurement_multiplier"
local CURRENT_MEASUREMENT_DIVISOR_KEY = "_current_measurement_divisor"

local FRIENT_SMART_PLUG_FINGERPRINTS = {
{ mfr = "frient A/S", model = "SPLZB-131" },
{ mfr = "frient A/S", model = "SPLZB-132" },
{ mfr = "frient A/S", model = "SPLZB-134" },
{ mfr = "frient A/S", model = "SPLZB-137" },
{ mfr = "frient A/S", model = "SPLZB-141" },
{ mfr = "frient A/S", model = "SPLZB-142" },
{ mfr = "frient A/S", model = "SPLZB-144" },
{ mfr = "frient A/S", model = "SPLZB-147" },
{ mfr = "frient A/S", model = "SMRZB-143" },
{ mfr = "frient A/S", model = "SMRZB-153" },
{ mfr = "frient A/S", model = "SMRZB-332" },
{ mfr = "frient A/S", model = "SMRZB-342" },
}

local POWER_FAILURE_ALARM_CODE = 0x03


local function alarm_report_handler(driver, device, zb_rx)
local alarm_status = zb_rx.body.zcl_body
if ((alarm_status.cluster_identifier.value == SimpleMetering.ID) and (alarm_status.alarm_code.value == POWER_FAILURE_ALARM_CODE)) then
device.thread:call_with_delay(2, function(d)
device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.powerSource.powerSource.unknown())
end
)
end
end

local function device_added(driver, device)
end

-- Handler for voltage measurement
local function voltage_measurement_handler(driver, device, value, zb_rx)
local raw_value = value.value
-- By default emit raw value
local multiplier = device:get_field(VOLTAGE_MEASUREMENT_MULTIPLIER_KEY) or 1
local divisor = device:get_field(VOLTAGE_MEASUREMENT_DIVISOR_KEY) or 1

raw_value = raw_value * multiplier / divisor

local mult = 10 ^ 1 -- Round off to 1 decimal place
raw_value = math.floor(raw_value * mult + 0.5) / mult

if device:supports_capability(capabilities.voltageMeasurement) then
device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.voltageMeasurement.voltage({ value = raw_value, unit = "V" }))
end
if device:supports_capability(capabilities.powerSource) then
device:emit_event(capabilities.powerSource.powerSource.mains())
end
end

local function current_measurement_divisor_handler(driver, device, divisor, zb_rx)
local raw_value = divisor.value
if raw_value == 0 then
log.warn("Current scale divisor is 0; using 1 to avoid division by zero")
raw_value = 1
end
device:set_field(CURRENT_MEASUREMENT_DIVISOR_KEY, raw_value, { persist = true })
end

local function current_measurement_multiplier_handler(driver, device, multiplier, zb_rx)
local raw_value = multiplier.value
device:set_field(CURRENT_MEASUREMENT_MULTIPLIER_KEY, raw_value, { persist = true })
end

local function voltage_measurement_divisor_handler(driver, device, divisor, zb_rx)
local raw_value = divisor.value
if raw_value == 0 then
log.warn("Voltage scale divisor is 0; using 1 to avoid division by zero")
raw_value = 1
end
device:set_field(VOLTAGE_MEASUREMENT_DIVISOR_KEY, raw_value, { persist = true })
end

local function voltage_measurement_multiplier_handler(driver, device, multiplier, zb_rx)
local raw_value = multiplier.value

device:set_field(VOLTAGE_MEASUREMENT_MULTIPLIER_KEY, raw_value, { persist = true })
end

-- Handler for current measurement
local function current_measurement_handler(driver, device, value, zb_rx)
local raw_value = value.value
-- By default emit raw value
local multiplier = device:get_field(CURRENT_MEASUREMENT_MULTIPLIER_KEY) or 1
local divisor = device:get_field(CURRENT_MEASUREMENT_DIVISOR_KEY) or 1

raw_value = raw_value * multiplier / divisor

local mult = 10 ^ 2 -- Round off to 2 decimal places
raw_value = math.floor(raw_value * mult + 0.5) / mult

if device:supports_capability(capabilities.currentMeasurement) then
device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.currentMeasurement.current({ value = raw_value, unit = "A" }))
end
end

-- Device init function
local function device_init(driver, device)
device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000, {persist = true})
device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 1000, {persist = true})
-- Indicate device is powered by mains
if device:supports_capability(capabilities.powerSource) then
device:emit_event(capabilities.powerSource.powerSource.mains())
end
end

local function do_configure(driver, device)
local configuration = configurationMap.get_device_configuration(device)
if configuration ~= nil then
for _, attribute in ipairs(configuration) do
device:add_configured_attribute(attribute)
device:add_monitored_attribute(attribute)
end
end
device:configure()

local alarms_endpoint = device:get_endpoint(Alarms.ID) or device.fingerprinted_endpoint_id
-- Make sure we have a valid endpoint number before sending the bind request
if alarms_endpoint ~= nil then
device:send(ElectricalMeasurement.attributes.ACVoltageMultiplier:read(device))
device:send(ElectricalMeasurement.attributes.ACVoltageDivisor:read(device))
device:send(ElectricalMeasurement.attributes.ACCurrentMultiplier:read(device))
device:send(ElectricalMeasurement.attributes.ACCurrentDivisor:read(device))
else
log.warn("No valid endpoint found for Alarms cluster binding")
end
device:refresh()
end

-- Function to determine if the driver can handle this device
local function can_handle_frient_smart_plug(opts, driver, device, ...)
for _, fingerprint in ipairs(FRIENT_SMART_PLUG_FINGERPRINTS) do
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
local subdriver = require("frient")
return true, subdriver
end
end
return false
end

-- Main driver definition
local frient_smart_plug = {
NAME = "frient Smart Plug",
zigbee_handlers = {
cluster = {
[Alarms.ID] = {
[Alarms.client.commands.Alarm.ID] = alarm_report_handler
}
},
attr = {
[ElectricalMeasurement.ID] = {
[ElectricalMeasurement.attributes.ACVoltageMultiplier.ID] = voltage_measurement_multiplier_handler,
[ElectricalMeasurement.attributes.ACVoltageDivisor.ID] = voltage_measurement_divisor_handler,
[ElectricalMeasurement.attributes.RMSVoltage.ID] = voltage_measurement_handler,
[ElectricalMeasurement.attributes.ACCurrentMultiplier.ID] = current_measurement_multiplier_handler,
[ElectricalMeasurement.attributes.ACCurrentDivisor.ID] = current_measurement_divisor_handler,
[ElectricalMeasurement.attributes.RMSCurrent.ID] = current_measurement_handler
}
},
},
lifecycle_handlers = {
init = device_init,
doConfigure = do_configure,
added = device_added,
},
can_handle = can_handle_frient_smart_plug
}

return frient_smart_plug
3 changes: 2 additions & 1 deletion drivers/SmartThings/zigbee-switch/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ local zigbee_switch_driver_template = {
lazy_load_if_possible("wallhero"),
lazy_load_if_possible("inovelli-vzm31-sn"),
lazy_load_if_possible("laisiao"),
lazy_load_if_possible("tuya-multi")
lazy_load_if_possible("tuya-multi"),
lazy_load_if_possible("frient")
},
zigbee_handlers = {
global = {
Expand Down
Loading
Loading