Skip to content
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

Matter support for Zigbee Occupancy and Light 0/1/2 (OnOff / Dimmer / White Color Temperature) #22110

Merged
merged 1 commit into from
Sep 8, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
## [14.2.0.4]
### Added
- HX711 optional calibration precision option on command ``Sensor34 2 <weight in gram> <precision>`` where `<precision>` is 1 to 10 (#13983)
- Matter support for Zigbee Occupancy and Light 0/1/2 (OnOff / Dimmer / White Color Temperature)

### Breaking Changed

Expand Down
4 changes: 4 additions & 0 deletions lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,13 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Air_Quality.h"
#include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Rain.h"
#include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Waterleak.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Light0.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Light1.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Light2.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Temperature.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Pressure.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Humidity.h"
#include "solidify/solidified_Matter_Plugin_9_Zigbee_Occupancy.h"
#include "solidify/solidified_Matter_Plugin_z_All.h"
#include "solidify/solidified_Matter_zz_Device.h"

Expand Down
2 changes: 1 addition & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_IM.be
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ class Matter_IM
var ep_str = (q.endpoint != nil) ? f"{q.endpoint:02X}" : "**"
var cl_str = (q.cluster != nil) ? f"{q.cluster:04X}" : "****"
var ev_str = (q.event != nil) ? f"{q.event:02X}" : "**"
var event_no_min_str = (event_no_min != nil) ? f" (>{event_no_min})" : ""
var event_no_min_str = (event_no_min != nil) ? f" (event>{event_no_min})" : ""
log(f"MTR: >Read_Event({msg.session.local_session_id:6i}) [{ep_str}]{cl_str}/{ev_str} {event_name}{event_no_min_str}", 3)
end
end
Expand Down
11 changes: 9 additions & 2 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ matter_device.events.dump()
#
# we limit to 3 commands (to we need more?)
def publish_command(key1, value1, key2, value2, key3, value3)
# if zigbee, decompose simple commands
if self.ZIGBEE && self.zigbee_mapper && self.zigbee_mapper.resolve_zb_device()
self.zigbee_mapper.zb_single_command(key1, value1)
self.zigbee_mapper.zb_single_command(key2, value2)
self.zigbee_mapper.zb_single_command(key3, value3)
end

import json
var payload = f"{json.dump(key1)}:{json.dump(value1)}"
if key2 != nil
Expand Down Expand Up @@ -537,7 +544,7 @@ matter_device.events.dump()
# key: key name in the JSON payload to read from, do nothing if key does not exist or content is `null`
# old_val: previous value, used to detect a change or return the value unchanged
# type_func: type enforcer for value, typically `int`, `bool`, `str`, `number`, `real`
# cluster/attribute: in case the value has change, publish a change to cluster/attribute
# cluster/attribute: in case the value has change, publish a change to cluster/attribute. Don't publish if they are `nil`
#
# Returns:
# `old_val` if key does not exist, JSON value is `null`, or value is unchanged
Expand All @@ -547,7 +554,7 @@ matter_device.events.dump()
var val = payload.find(key)
if (val != nil)
val = type_func(val)
if (val != old_val)
if (val != old_val) && (cluster != nil) && (attribute != nil)
self.attribute_updated(cluster, attribute)
end
return val
Expand Down
44 changes: 44 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ class Matter_Plugin_Device : Matter_Plugin
end
end

#############################################################
# Called when the value changed compared to shadow value
#
# This must be overriden.
# This is where you call `self.attribute_updated(<cluster>, <attribute>)`
def value_changed()
# self.attribute_updated(0x0402, 0x0000)
end

#############################################################
# Pre-process value
#
# This must be overriden.
# This allows to convert the raw sensor value to the target one, typically int
def pre_value(val)
return val
end

#############################################################
# read an attribute
#
Expand Down Expand Up @@ -455,5 +473,31 @@ class Matter_Plugin_Device : Matter_Plugin
return old_val
end

#############################################################
# For Zigbee devices
#############################################################
#############################################################
# attributes_refined
#
# Filtered to only events for this endpoint
#
# Can be called only if `self.ZIGBEE` is true
def zigbee_received(frame, attr_list)
import math
log(f"MTR: zigbee_received Ox{self.zigbee_mapper.shortaddr:04X} {attr_list=} {type(attr_list)=}", 3)
var idx = 0
while (idx < size(attr_list))
var entry = attr_list[idx]
if (entry.key == self.ZIGBEE_NAME)
var val = self.pre_value(entry.val)
var update_list = { self.JSON_NAME : val } # Matter temperature is 1/100th of degrees
self.update_virtual(update_list)
log(f"MTR: [{self.endpoint:02X}] {self.JSON_NAME} updated {update_list}", 3)
return nil
end
idx += 1
end
end

end
matter.Plugin_Device = Matter_Plugin_Device
37 changes: 36 additions & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_Light0.be
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import matter

class Matter_Plugin_Light0 : Matter_Plugin_Device
static var TYPE = "light0" # name of the plug-in in json
static var DISPLAY_NAME = "Light 0 On" # display name of the plug-in
static var DISPLAY_NAME = "Light 0 OnOff" # display name of the plug-in
static var ARG = "relay" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
static var ARG_HINT = "Relay<x> number"
Expand Down Expand Up @@ -63,6 +63,7 @@ class Matter_Plugin_Light0 : Matter_Plugin_Device
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
# with Light0 we always need relay number but we don't for Light1/2/3 so self.tasmota_relay_index may be `nil`
self.tasmota_relay_index = int(config.find(self.ARG #-'relay'-#, nil))
if (self.tasmota_relay_index != nil && self.tasmota_relay_index <= 0) self.tasmota_relay_index = 1 end
Expand Down Expand Up @@ -174,6 +175,40 @@ class Matter_Plugin_Light0 : Matter_Plugin_Device
super(self).update_virtual(payload)
end

#############################################################
# For Zigbee devices
#############################################################
#############################################################
# attributes_refined
#
# Filtered to only events for this endpoint
# Contains common code for Light 0/1/2/3 to avoid code duplication
#
# Can be called only if `self.ZIGBEE` is true
def zigbee_received(frame, attr_list)
import math
log(f"MTR: zigbee_received Ox{self.zigbee_mapper.shortaddr:04X} {attr_list=} {type(attr_list)=}", 3)
var idx = 0
var update_list = {}
while (idx < size(attr_list))
var entry = attr_list[idx]
if (entry.key == "Power")
update_list['Power'] = int(entry.val)
end
if (entry.key == "Dimmer")
update_list['Dimmer'] = int(entry.val)
end
if (entry.key == "CT")
update_list['CT'] = int(entry.val)
end
idx += 1
end
if (size(update_list) > 0)
self.update_virtual(update_list)
log(f"MTR: [{self.endpoint:02X}] Light2 updated {update_list}", 3)
end
end

#############################################################
# For Bridge devices
#############################################################
Expand Down
44 changes: 0 additions & 44 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_Sensor.be
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,6 @@ class Matter_Plugin_Sensor : Matter_Plugin_Device
end
end

#############################################################
# Called when the value changed compared to shadow value
#
# This must be overriden.
# This is where you call `self.attribute_updated(<cluster>, <attribute>)`
def value_changed()
# self.attribute_updated(0x0402, 0x0000)
end

#############################################################
# Pre-process value
#
# This must be overriden.
# This allows to convert the raw sensor value to the target one, typically int
def pre_value(val)
return val
end

#############################################################
# update_virtual
#
Expand Down Expand Up @@ -168,31 +150,5 @@ class Matter_Plugin_Sensor : Matter_Plugin_Device
#############################################################
#############################################################

#############################################################
# For Zigbee devices
#############################################################
#############################################################
# attributes_refined
#
# Filtered to only events for this endpoint
#
# Can be called only if `self.ZIGBEE` is true
def zigbee_received(frame, attr_list)
import math
log(f"MTR: zigbee_received Ox{self.zigbee_mapper.shortaddr:04X} {attr_list=} {type(attr_list)=}", 3)
var idx = 0
while (idx < size(attr_list))
var entry = attr_list[idx]
if (entry.key == self.ZIGBEE_NAME)
var val = self.pre_value(entry.val)
var update_list = { self.JSON_NAME : val } # Matter temperature is 1/100th of degrees
self.update_virtual(update_list)
log(f"MTR: [{self.endpoint:02X}] {self.JSON_NAME} updated {update_list}", 3)
return nil
end
idx += 1
end
end

end
matter.Plugin_Sensor = Matter_Plugin_Sensor
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Matter_Plugin_Sensor_Air_Quality : Matter_Plugin_Device
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
self.prefix = str(config.find(self.ARG))
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Matter_Plugin_Sensor_Boolean : Matter_Plugin_Device
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
self.tasmota_switch_index = int(config.find(self.ARG #-'switch'-#, 1))
if self.tasmota_switch_index <= 0 self.tasmota_switch_index = 1 end
end
Expand Down Expand Up @@ -79,6 +80,16 @@ class Matter_Plugin_Sensor_Boolean : Matter_Plugin_Device
def value_updated()
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload)
self.shadow_bool_value = self._parse_update_virtual(payload, self.JSON_NAME, self.shadow_bool_value, bool, nil, nil) # publishing cluster/attr is delegated to `value_updated()`
self.value_updated()
super(self).update_virtual(payload)
end

#############################################################
# For Bridge devices
#############################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Matter_Plugin_Sensor_GenericSwitch_Btn : Matter_Plugin_Device
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
self.tasmota_switch_index = int(config.find(self.ARG #-'relay'-#, 1))
if self.tasmota_switch_index <= 0 self.tasmota_switch_index = 1 end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Matter_Plugin_Shutter : Matter_Plugin_Device
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
self.tasmota_shutter_index = config.find(self.ARG #-'relay'-#)
if self.tasmota_shutter_index == nil self.tasmota_shutter_index = 0 end
self.shadow_shutter_inverted = -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Matter_Plugin_Light1 : Matter_Plugin_Light0
#
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
# with Light0 we always need relay number but we don't for Light1/2/3 so self.tasmota_relay_index may be `nil`
if self.BRIDGE
self.tasmota_relay_index = int(config.find(self.ARG #-'relay'-#, nil))
Expand Down Expand Up @@ -207,7 +208,7 @@ class Matter_Plugin_Light1 : Matter_Plugin_Light0
var onoff = bri_254 > 0
self.set_bri(bri_254, onoff)
ctx.log = "bri:"+str(bri_254)
self.publish_command('Bri', bri_254, 'Dimmer', tasmota.scale_uint(bri_254, 0, 254, 0, 100), 'Power', onoff ? 1 : 0)
self.publish_command('Power', onoff ? 1 : 0, 'Bri', bri_254, 'Dimmer', tasmota.scale_uint(bri_254, 0, 254, 0, 100))
return true
elif command == 0x0005 # ---------- MoveWithOnOff ----------
# TODO, we don't really support it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Matter_Plugin_Sensor_Contact : Matter_Plugin_Sensor_Boolean
# static var ARG_HINT = "Switch<x> number"
# static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
# static var UPDATE_TIME = 750 # update every 750ms
static var JSON_NAME = "Contact" # Name of the sensor attribute in JSON payloads
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Contact")
static var CLUSTERS = matter.consolidate_clusters(_class, {
0x0045: [0], # Boolean State p.70 - no writable
Expand Down Expand Up @@ -66,15 +67,6 @@ class Matter_Plugin_Sensor_Contact : Matter_Plugin_Sensor_Boolean
return super(self).read_attribute(session, ctx, tlv_solo)
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload)
self.shadow_bool_value = self._parse_update_virtual(payload, "Contact", self.shadow_bool_value, bool, 0x0045, 0x0000)
super(self).update_virtual(payload)
end

#############################################################
# For Bridge devices
#############################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Matter_Plugin_Sensor_Occupancy : Matter_Plugin_Sensor_Boolean
# static var ARG_HINT = "Switch<x> number"
# static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
# static var UPDATE_TIME = 750 # update every 750ms
static var JSON_NAME = "Occupancy" # Name of the sensor attribute in JSON payloads
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Occupancy")
static var CLUSTERS = matter.consolidate_clusters(_class, {
0x0406: [0,1,2], # Occupancy Sensing p.105 - no writable
Expand Down Expand Up @@ -70,15 +71,6 @@ class Matter_Plugin_Sensor_Occupancy : Matter_Plugin_Sensor_Boolean
return super(self).read_attribute(session, ctx, tlv_solo)
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload)
self.shadow_bool_value = self._parse_update_virtual(payload, "Occupancy", self.shadow_bool_value, bool, 0x0406, 0x0000)
super(self).update_virtual(payload)
end

#############################################################
# For Bridge devices
#############################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class Matter_Plugin_Sensor_OnOff : Matter_Plugin_Sensor_Boolean
# static var ARG_HINT = "Switch<x> number"
# static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
# static var UPDATE_TIME = 750 # update every 750ms
static var JSON_NAME = "OnOff" # Name of the sensor attribute in JSON payloads
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "OnOff")
static var CLUSTERS = matter.consolidate_clusters(_class, {
0x0006: [0], # On/Off 1.5 p.48
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Matter_Plugin_Sensor_Rain : Matter_Plugin_Sensor_Boolean
# static var ARG_HINT = "Switch<x> number"
# static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
# static var UPDATE_TIME = 750 # update every 750ms
static var JSON_NAME = "Rain" # Name of the sensor attribute in JSON payloads
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Rain")
static var CLUSTERS = matter.consolidate_clusters(_class, {
0x0045: [0], # Boolean State p.70 - no writable
Expand Down Expand Up @@ -67,15 +68,6 @@ class Matter_Plugin_Sensor_Rain : Matter_Plugin_Sensor_Boolean
return super(self).read_attribute(session, ctx, tlv_solo)
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload)
self.shadow_bool_value = self._parse_update_virtual(payload, "Rain", self.shadow_bool_value, bool, 0x0045, 0x0000)
super(self).update_virtual(payload)
end

#############################################################
# For Bridge devices
#############################################################
Expand Down
Loading