Skip to content

Commit 2cf0eda

Browse files
authored
Matter add Fan support (virtual only) (#21637)
* Matter add Fan support (virtual only) * Add MtrReceived event
1 parent 767ac60 commit 2cf0eda

16 files changed

+890
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
1010
- Extend command ``SetOption147 1`` to disable publish of IRReceived MQTT messages (#21574)
1111
- Matter support for Rain sensor (#21633)
1212
- Matter add internal debug option
13+
- Matter add Fan support (virtual only)
1314

1415
### Breaking Changed
1516

lib/libesp32/berry_matter/src/be_matter_module.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
258258
#include "solidify/solidified_Matter_Plugin_3_Sensor_Contact.h"
259259
#include "solidify/solidified_Matter_Plugin_3_Sensor_Rain.h"
260260
#include "solidify/solidified_Matter_Plugin_3_Sensor_Waterleak.h"
261+
#include "solidify/solidified_Matter_Plugin_2_Fan.h"
262+
#include "solidify/solidified_Matter_Plugin_9_Virt_Fan.h"
261263
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Contact.h"
262264
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Occupancy.h"
263265
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Rain.h"

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin.be - generic superclass for all Matter plugins, used to define specific behaviors (light, switch, media...)
2+
# Matter_Plugin_0.be - generic superclass for all Matter plugins, used to define specific behaviors (light, switch, media...)
33
#
44
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
55
#
@@ -48,6 +48,7 @@ class Matter_Plugin
4848
static var FEATURE_MAPS = { # feature map per cluster
4949
0x0031: 0x04, # Put Eth for now which should work for any on-network
5050
0x0102: 1 + 4, # Lift + PA_LF
51+
0x0202: 2, # Fan: Auto
5152
}
5253
# `CLUSTER_REVISIONS` contains revision numbers for each cluster, or `1` if not present
5354
static var CLUSTER_REVISIONS = {

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Aggregator.be

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin_Aggregator.be - implements the Aggregator endpoint
2+
# Matter_Plugin_1_Aggregator.be - implements the Aggregator endpoint
33
#
44
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
55
#

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Device.be

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin_Device.be - implements the behavior for a standard Device
2+
# Matter_Plugin_1_Device.be - implements the behavior for a standard Device
33
#
44
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
55
#

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin_Root.be - implements the core features that a Matter device must implemment
2+
# Matter_Plugin_1_Root.be - implements the core features that a Matter device must implemment
33
#
44
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
55
#
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#
2+
# Matter_Plugin_2_Fan.be - implements the behavior for a generic Fan
3+
#
4+
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
import matter
21+
22+
# Matter plug-in for core behavior
23+
24+
#@ solidify:Matter_Plugin_Fan,weak
25+
26+
class Matter_Plugin_Fan : Matter_Plugin_Device
27+
static var TYPE = "fan" # name of the plug-in in json
28+
static var DISPLAY_NAME = "Fan" # display name of the plug-in
29+
# static var ARG = "" # additional argument name (or empty if none)
30+
static var CLUSTERS = matter.consolidate_clusters(_class, {
31+
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
32+
# 0x0003: inherited # Identify 1.2 p.16
33+
# 0x0004: inherited # Groups 1.3 p.21
34+
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
35+
0x0202: [0,1,2,3], # Fan
36+
})
37+
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "FanMode", "FanSpeed")
38+
static var TYPES = { 0x002B: 2 } # Fan
39+
40+
# Inherited
41+
# var device # reference to the `device` global object
42+
# var endpoint # current endpoint
43+
# var clusters # map from cluster to list of attributes, typically constructed from CLUSTERS hierachy
44+
# var tick # tick value when it was last updated
45+
# var node_label # name of the endpoint, used only in bridge mode, "" if none
46+
var shadow_fan_mode
47+
var shadow_fan_speed_pct
48+
#############################################################
49+
# FanMode:
50+
# 0: Off
51+
# 1: Low
52+
# 2: Medium
53+
# 3: High
54+
# 4: On -- deprecated
55+
# 5: Auto -- not declared as supported
56+
# 6: Smart -- deprecated
57+
58+
#############################################################
59+
# Constructor
60+
def init(device, endpoint, config)
61+
super(self).init(device, endpoint, config)
62+
self.shadow_fan_mode = 0 # Off by default
63+
self.shadow_fan_speed_pct = 0
64+
end
65+
66+
#############################################################
67+
# Model
68+
#
69+
def set_fan_mode(fan_mode)
70+
fan_mode = int(fan_mode)
71+
if (fan_mode < 0) fan_mode = 0 end # force positive
72+
if fan_mode != self.shadow_fan_mode
73+
self.attribute_updated(0x0202, 0x0000)
74+
self.shadow_fan_mode = int(fan_mode)
75+
# compute new speed
76+
var new_speed_pct = self.shadow_fan_speed_pct
77+
if self.shadow_fan_mode == 0 # set to Off, we need to adjust speed to 0 (4.4.6.1.1)
78+
new_speed_pct = 0
79+
elif self.shadow_fan_mode > 3 # Auto mode or unsupported modes, since we don't support AUTO, set speed to max
80+
self.shadow_fan_mode = 3 # HIGH
81+
new_speed_pct = 100
82+
else # set to value
83+
new_speed_pct = tasmota.scale_uint(fan_mode, 0, 3, 0, 100)
84+
end
85+
# adjust and advertize if speed changed
86+
if self.shadow_fan_speed_pct != new_speed_pct
87+
self.shadow_fan_speed_pct = new_speed_pct
88+
self.attribute_updated(0x0202, 0x0002)
89+
end
90+
end
91+
end
92+
93+
def set_fan_speed_pct(fan_speed_pct)
94+
# guard value
95+
fan_speed_pct = int(fan_speed_pct)
96+
if (fan_speed_pct < 0) fan_speed_pct = 0 end
97+
if (fan_speed_pct > 100) fan_speed_pct = 100 end
98+
if fan_speed_pct != self.shadow_fan_speed_pct
99+
self.attribute_updated(0x0202, 0x0002)
100+
self.shadow_fan_speed_pct = fan_speed_pct
101+
# adjust mode if needed
102+
var new_mode = self.shadow_fan_mode
103+
if (fan_speed_pct == 0)
104+
new_mode = 0
105+
else
106+
new_mode = tasmota.scale_uint(fan_speed_pct, 1, 100, 1, 3)
107+
end
108+
# adjust and advertize if mode changed
109+
if (new_mode != self.shadow_fan_mode)
110+
self.shadow_fan_mode = new_mode
111+
self.attribute_updated(0x0202, 0x0000)
112+
end
113+
end
114+
end
115+
116+
#############################################################
117+
# read an attribute
118+
#
119+
def read_attribute(session, ctx, tlv_solo)
120+
var TLV = matter.TLV
121+
var cluster = ctx.cluster
122+
var attribute = ctx.attribute
123+
124+
# ====================================================================================================
125+
if cluster == 0x0202 # ========== Fan ==========
126+
self.update_shadow_lazy()
127+
if attribute == 0x0000 # ---------- FanMode / enum8 ----------
128+
return tlv_solo.set(TLV.U1, self.shadow_fan_mode)
129+
elif attribute == 0x0001 # ---------- FanModeSequence / enum8 ----------
130+
return tlv_solo.set(TLV.U1, 2) # Off/Low/Med/High/Auto
131+
elif attribute == 0x0002 # ---------- PercentSetting / enum8 ----------
132+
return tlv_solo.set(TLV.U1, self.shadow_fan_speed_pct)
133+
elif attribute == 0x0003 # ---------- PercentSetting / enum8 ----------
134+
return tlv_solo.set(TLV.U1, self.shadow_fan_speed_pct)
135+
end
136+
137+
end
138+
return super(self).read_attribute(session, ctx, tlv_solo)
139+
end
140+
141+
#############################################################
142+
# MVC Model
143+
#
144+
# Controller write attributes
145+
#############################################################
146+
#############################################################
147+
# write attribute
148+
def write_attribute(session, ctx, write_data)
149+
var TLV = matter.TLV
150+
var cluster = ctx.cluster
151+
var attribute = ctx.attribute
152+
153+
# ====================================================================================================
154+
if cluster == 0x0202 # ========== Fan ==========
155+
self.update_shadow_lazy()
156+
if attribute == 0x0000 # ---------- FanMode / enum8 ----------
157+
if type(write_data) == 'int'
158+
self.set_fan_mode(write_data)
159+
self.publish_command('FanMode', self.shadow_fan_mode, 'FanSpeed', self.shadow_fan_speed_pct)
160+
return true
161+
else
162+
ctx.status = matter.CONSTRAINT_ERROR
163+
return false
164+
end
165+
elif attribute == 0x0002 # ---------- PercentSetting / enum8 ----------
166+
if type(write_data) == 'int'
167+
self.set_fan_speed_pct(write_data)
168+
self.publish_command('FanMode', self.shadow_fan_mode, 'FanSpeed', self.shadow_fan_speed_pct)
169+
return true
170+
else
171+
ctx.status = matter.CONSTRAINT_ERROR
172+
return false
173+
end
174+
end
175+
176+
end
177+
# return super(self).read_attribute(session, ctx, tlv_solo) # not useful as there is nothing in superclass
178+
return nil
179+
end
180+
181+
#############################################################
182+
# update_virtual
183+
#
184+
# Update internal state for virtual devices
185+
def update_virtual(payload)
186+
var val_fan_mode = payload.find("FanMode")
187+
if val_fan_mode != nil
188+
self.set_fan_mode(int(val_fan_mode))
189+
end
190+
var val_fan_speed = payload.find("FanSpeed")
191+
if val_fan_speed != nil
192+
self.set_fan_speed_pct(int(val_fan_speed))
193+
end
194+
# super(self).update_virtual(payload) # not useful as there is nothing in superclass
195+
end
196+
197+
end
198+
matter.Plugin_Fan = Matter_Plugin_Fan

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_3_Sensor_Waterleak.be

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin_3_Sensor_Waterleak.be - implements the behavior for a Water leak Sensor
2+
# Matter_Plugin_2_Sensor_Waterleak.be - implements the behavior for a Water leak Sensor
33
#
44
# Copyright (C) 2024 Stephan Hadinger & Theo Arends
55
#

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_8_Bridge_Sensor_Rain.be

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Matter_Plugin_Bridge_8_Sensor_Rain.be - implements Rain Sensor via HTTP to Tasmota
2+
# Matter_Plugin_Bridge_Sensor_Rain.be - implements Rain Sensor via HTTP to Tasmota
33
#
44
# Copyright (C) 2024 Stephan Hadinger & Theo Arends
55
#
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#
2+
# Matter_Plugin_Virt_Fan.be - implements the behavior for a Virtual Fan
3+
#
4+
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
import matter
21+
22+
# Matter plug-in for core behavior
23+
24+
#@ solidify:Matter_Plugin_Virt_Fan,weak
25+
26+
class Matter_Plugin_Virt_Fan : Matter_Plugin_Fan
27+
static var TYPE = "v_fan" # name of the plug-in in json
28+
static var DISPLAY_NAME = "v.Fan" # display name of the plug-in
29+
static var ARG = "" # no arg for virtual device
30+
static var ARG_HINT = "_Not used_" # Hint for entering the Argument (inside 'placeholder')
31+
static var VIRTUAL = true # virtual device
32+
end
33+
matter.Plugin_Virt_Fan = Matter_Plugin_Virt_Fan

0 commit comments

Comments
 (0)