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 add support for Shutters (without Tilt) #18509

Merged
merged 1 commit into from
Apr 25, 2023
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 @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Command ``SetOption152 0/1`` to select two (0 = default) pin bistable or one (1) pin latching relay control (#18386)
- Matter allow `Matter#Initialized` rule once the device is configured (#18451)
- Matter add UI to change endpoints configuration (#18498)
- Matter add support for Shutters (without Tilt)

### Breaking Changed

Expand Down
2 changes: 2 additions & 0 deletions lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_Light1.h"
#include "solidify/solidified_Matter_Plugin_Light2.h"
#include "solidify/solidified_Matter_Plugin_Light3.h"
#include "solidify/solidified_Matter_Plugin_Shutter.h"
#include "solidify/solidified_Matter_Plugin_Sensor.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Pressure.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Temp.h"
Expand Down Expand Up @@ -337,6 +338,7 @@ module matter (scope: global, strings: weak) {
Plugin_Light1, class(be_class_Matter_Plugin_Light1) // Dimmable Light
Plugin_Light2, class(be_class_Matter_Plugin_Light2) // Color Temperature Light
Plugin_Light3, class(be_class_Matter_Plugin_Light3) // Extended Color Light
Plugin_Shutter, class(be_class_Matter_Plugin_Shutter) // Shutter
Plugin_Sensor, class(be_class_Matter_Plugin_Sensor) // Generic Sensor
Plugin_Sensor_Pressure, class(be_class_Matter_Plugin_Sensor_Pressure) // Pressure Sensor
Plugin_Sensor_Temp, class(be_class_Matter_Plugin_Sensor_Temp) // Temperature Sensor
Expand Down
71 changes: 57 additions & 14 deletions lib/libesp32/berry_matter/src/embedded/Matter_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Matter_Device
var message_handler # `matter.MessageHandler()` object
var sessions # `matter.Session_Store()` objet
var ui
var tick # increment at each tick, avoids to repeat too frequently some actions
# Commissioning open
var commissioning_open # timestamp for timeout of commissioning (millis()) or `nil` if closed
var commissioning_iterations # current PBKDF number of iterations
Expand Down Expand Up @@ -74,6 +75,7 @@ class Matter_Device
end # abort if SetOption 151 is not set

self.started = false
self.tick = 0
self.plugins = []
self.plugins_persist = false # plugins need to saved only when the first fabric is associated
self.plugins_classes = {}
Expand Down Expand Up @@ -326,6 +328,12 @@ class Matter_Device

end

#############################################################
# ticks
def every_50ms()
self.tick += 1
end

#############################################################
# dispatch every 250ms click to sub-objects that need it
def every_250ms()
Expand Down Expand Up @@ -991,6 +999,7 @@ class Matter_Device
#
# Applies only if there are no plugins already configured
def autoconf_device_map()
import string
import json
var m = {}

Expand Down Expand Up @@ -1018,15 +1027,46 @@ class Matter_Device
end
end

# handle shutters before relays (as we steal relays for shutters)
var r_st13 = tasmota.cmd("Status 13", true) # issue `Status 13`
var relays_reserved = [] # list of relays that are used for non-relay (shutters)
tasmota.log("MTR: Status 13 = "+str(r_st13), 3)

if r_st13.contains('StatusSHT')
r_st13 = r_st13['StatusSHT'] # skip root
# Shutter is enabled, iterate
var idx = 0
while true
var k = 'SHT' + str(idx) # SHT is zero based
if !r_st13.contains(k) break end # no more SHTxxx
var d = r_st13[k]
tasmota.log(string.format("MTR: '%s' = %s", k, str(d)), 3)
var relay1 = d.find('Relay1', 0) - 1 # relay base 0 or -1 if none
var relay2 = d.find('Relay2', 0) - 1 # relay base 0 or -1 if none

if relay1 >= 0 relays_reserved.push(relay1) end # mark relay1/2 as non-relays
if relay2 >= 0 relays_reserved.push(relay2) end

tasmota.log(string.format("MTR: relay1 = %s, relay2 = %s", relay1, relay2), 3)
# add shutter to definition
m[str(endpoint)] = {'type':'shutter','shutter':idx}
endpoint += 1
idx += 1
end

end

# how many relays are present
var relay_count = size(tasmota.get_power())
var relay_index = 0 # start at index 0
if light_present relay_count -= 1 end # last power is taken for lights

while relay_index < relay_count
m[str(endpoint)] = {'type':'relay','relay':relay_index}
if relays_reserved.find(relay_index) == nil # if relay is actual relay
m[str(endpoint)] = {'type':'relay','relay':relay_index}
endpoint += 1
end
relay_index += 1
endpoint += 1
end

# auto-detect sensors
Expand Down Expand Up @@ -1101,8 +1141,12 @@ class Matter_Device
# register_plugin_class
#
# Adds a class by name
def register_plugin_class(name, cl)
self.plugins_classes[name] = cl
def register_plugin_class(cl)
import introspect
var typ = introspect.get(cl, 'TYPE') # make sure we don't crash if TYPE does not exist
if typ
self.plugins_classes[typ] = cl
end
end

#############################################################
Expand All @@ -1128,16 +1172,15 @@ class Matter_Device
#
# Adds a class by name
def register_native_classes(name, cl)
self.register_plugin_class('root', matter.Plugin_Root)
self.register_plugin_class('light0', matter.Plugin_Light0)
self.register_plugin_class('light1', matter.Plugin_Light1)
self.register_plugin_class('light2', matter.Plugin_Light2)
self.register_plugin_class('light3', matter.Plugin_Light3)
self.register_plugin_class('relay', matter.Plugin_OnOff)
self.register_plugin_class('temperature', matter.Plugin_Sensor_Temp)
self.register_plugin_class('humidity', matter.Plugin_Sensor_Humidity)
self.register_plugin_class('illuminance', matter.Plugin_Sensor_Illuminance)
self.register_plugin_class('pressure', matter.Plugin_Sensor_Pressure)
# try to register any class that starts with 'Plugin_'
import introspect
import string
for k: introspect.members(matter)
var v = introspect.get(matter, k)
if type(v) == 'class' && string.find(k, "Plugin_") == 0
self.register_plugin_class(v)
end
end
tasmota.log("MTR: registered classes "+str(self.k2l(self.plugins_classes)), 3)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class Matter_MessageHandler
return ret
except .. as e, m
tasmota.log("MTR: MessageHandler::msg_received exception: "+str(e)+";"+str(m))
if self._debug_present
if tasmota._debug_present
import debug
debug.traceback()
end
Expand Down
17 changes: 13 additions & 4 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin.be
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#@ solidify:Matter_Plugin,weak

class Matter_Plugin
static var TYPE = "generic" # name of the plug-in in json
static var TYPE = "" # name of the plug-in in json
static var NAME = "" # display name of the plug-in
static var ARG = "" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> str(x) # function to convert argument to the right type
Expand All @@ -33,6 +33,7 @@ class Matter_Plugin
var device # reference to the `device` global object
var endpoint # current endpoint
var clusters # map from cluster to list of attributes, typically constructed from CLUSTERS hierachy
var tick # tick value when it was last updated

#############################################################
# MVC Model
Expand All @@ -55,15 +56,23 @@ class Matter_Plugin
#############################################################
# Stub for updating shadow values (local copies of what we published to the Matter gateway)
def update_shadow()
self.tick = self.device.tick
end

#############################################################
# Stub for updating shadow values (local copies of what we published to the Matter gateway)
def update_shadow_lazy()
if self.tick != self.device.tick
self.update_shadow()
end
end

#############################################################
# signal that an attribute has been changed
#
# If `endpoint` is `nil`, send to all endpoints
def attribute_updated(endpoint, cluster, attribute, fabric_specific)
if endpoint == nil endpoint = self.endpoint end
self.device.attribute_updated(endpoint, cluster, attribute, fabric_specific)
def attribute_updated(cluster, attribute, fabric_specific)
self.device.attribute_updated(self.endpoint, cluster, attribute, fabric_specific)
end

#############################################################
Expand Down
14 changes: 14 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Matter_Plugin_Device : Matter_Plugin
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
0x0003: [0,1,0xFFFC,0xFFFD], # Identify 1.2 p.16
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
0x0005: [0,1,2,3,4,5,0xFFFC,0xFFFD], # Scenes 1.4 p.30 - no writable
}
static var TYPES = { 0x0000: 0 } # fake type

Expand Down Expand Up @@ -67,6 +68,14 @@ class Matter_Plugin_Device : Matter_Plugin
return TLV.create_TLV(TLV.U4, 4)# "new data model format and notation"
end

# ====================================================================================================
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 - no writable ==========
if attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
end

else
return super(self).read_attribute(session, ctx)
end
Expand Down Expand Up @@ -105,6 +114,11 @@ class Matter_Plugin_Device : Matter_Plugin
# TODO
return true

# ====================================================================================================
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 ==========
# TODO
return true

else
return super(self).invoke_request(session, val, ctx)
end
Expand Down
75 changes: 11 additions & 64 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light0.be
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@
# Matter plug-in for core behavior

# dummy declaration for solidification
class Matter_Plugin end
class Matter_Plugin_Device end

#@ solidify:Matter_Plugin_Light0,weak

class Matter_Plugin_Light0 : Matter_Plugin
class Matter_Plugin_Light0 : Matter_Plugin_Device
static var TYPE = "light0" # name of the plug-in in json
static var NAME = "Light 0 On" # display name of the plug-in
static var CLUSTERS = {
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
0x0003: [0,1,0xFFFC,0xFFFD], # Identify 1.2 p.16
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
0x0005: [0,1,2,3,4,5,0xFFFC,0xFFFD], # Scenes 1.4 p.30 - no writable
# 0x0003: inherited # Identify 1.2 p.16
# 0x0004: inherited # Groups 1.3 p.21
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
}
static var TYPES = { 0x0100: 2 } # OnOff Light, but not actually used because Relay is managed by OnOff
Expand All @@ -52,7 +52,8 @@ class Matter_Plugin_Light0 : Matter_Plugin
import light
var light_status = light.get()
var pow = light_status.find('power', nil)
if pow != self.shadow_onoff self.attribute_updated(nil, 0x0006, 0x0000) self.shadow_onoff = pow end
if pow != self.shadow_onoff self.attribute_updated(0x0006, 0x0000) self.shadow_onoff = pow end
super(self).update_shadow()
end

#############################################################
Expand All @@ -65,37 +66,8 @@ class Matter_Plugin_Light0 : Matter_Plugin
var attribute = ctx.attribute

# ====================================================================================================
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
if attribute == 0x0000 # ---------- IdentifyTime / u2 ----------
return TLV.create_TLV(TLV.U2, 0) # no identification in progress
elif attribute == 0x0001 # ---------- IdentifyType / enum8 ----------
return TLV.create_TLV(TLV.U1, 0) # IdentifyType = 0x00 None
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) # no features
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 4) # "new data model format and notation"
end

# ====================================================================================================
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
if attribute == 0x0000 # ---------- ----------
return nil # TODO
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0)#
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 4)# "new data model format and notation"
end

# ====================================================================================================
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 - no writable ==========
if attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
end

# ====================================================================================================
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
if cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- OnOff / bool ----------
return TLV.create_TLV(TLV.BOOL, self.shadow_onoff)
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
Expand All @@ -121,33 +93,8 @@ class Matter_Plugin_Light0 : Matter_Plugin
var command = ctx.command

# ====================================================================================================
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========

if command == 0x0000 # ---------- Identify ----------
# ignore
return true
elif command == 0x0001 # ---------- IdentifyQuery ----------
# create IdentifyQueryResponse
# ID=1
# 0=Certificate (octstr)
var iqr = TLV.Matter_TLV_struct()
iqr.add_TLV(0, TLV.U2, 0) # Timeout
ctx.command = 0x00 # IdentifyQueryResponse
return iqr
elif command == 0x0040 # ---------- TriggerEffect ----------
# ignore
return true
end
# ====================================================================================================
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
# TODO
return true
# ====================================================================================================
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 ==========
# TODO
return true
# ====================================================================================================
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
if cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
self.update_shadow_lazy()
if command == 0x0000 # ---------- Off ----------
light.set({'power':false})
self.update_shadow()
Expand Down
11 changes: 9 additions & 2 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light1.be
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ class Matter_Plugin_Light1 : Matter_Plugin_Light0
import light
var light_status = light.get()
var bri = light_status.find('bri', nil)
if bri != nil bri = tasmota.scale_uint(bri, 0, 255, 0, 254) else bri = self.shadow_bri end
if bri != self.shadow_bri self.attribute_updated(nil, 0x0008, 0x0000) self.shadow_bri = bri end
if bri != nil
bri = tasmota.scale_uint(bri, 0, 255, 0, 254)
if bri != self.shadow_bri
self.attribute_updated(0x0008, 0x0000)
self.shadow_bri = bri
end
end
super(self).update_shadow() # superclass manages 'power'
end

Expand All @@ -70,6 +75,7 @@ class Matter_Plugin_Light1 : Matter_Plugin_Light0

# ====================================================================================================
if cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- CurrentLevel / u1 ----------
return TLV.create_TLV(TLV.U1, self.shadow_bri)
elif attribute == 0x0002 # ---------- MinLevel / u1 ----------
Expand Down Expand Up @@ -104,6 +110,7 @@ class Matter_Plugin_Light1 : Matter_Plugin_Light0

# ====================================================================================================
if cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
self.update_shadow_lazy()
if command == 0x0000 # ---------- MoveToLevel ----------
var bri_in = val.findsubval(0) # Hue 0..254
var bri = tasmota.scale_uint(bri_in, 0, 254, 0, 255)
Expand Down
Loading