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 Fan support (virtual only) #21637

Merged
merged 2 commits into from
Jun 15, 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 @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Extend command ``SetOption147 1`` to disable publish of IRReceived MQTT messages (#21574)
- Matter support for Rain sensor (#21633)
- Matter add internal debug option
- Matter add Fan support (virtual only)

### 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 @@ -258,6 +258,8 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_3_Sensor_Contact.h"
#include "solidify/solidified_Matter_Plugin_3_Sensor_Rain.h"
#include "solidify/solidified_Matter_Plugin_3_Sensor_Waterleak.h"
#include "solidify/solidified_Matter_Plugin_2_Fan.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Fan.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Contact.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Occupancy.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Rain.h"
Expand Down
3 changes: 2 additions & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin.be - generic superclass for all Matter plugins, used to define specific behaviors (light, switch, media...)
# Matter_Plugin_0.be - generic superclass for all Matter plugins, used to define specific behaviors (light, switch, media...)
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
Expand Down Expand Up @@ -48,6 +48,7 @@ class Matter_Plugin
static var FEATURE_MAPS = { # feature map per cluster
0x0031: 0x04, # Put Eth for now which should work for any on-network
0x0102: 1 + 4, # Lift + PA_LF
0x0202: 2, # Fan: Auto
}
# `CLUSTER_REVISIONS` contains revision numbers for each cluster, or `1` if not present
static var CLUSTER_REVISIONS = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin_Aggregator.be - implements the Aggregator endpoint
# Matter_Plugin_1_Aggregator.be - implements the Aggregator endpoint
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin_Device.be - implements the behavior for a standard Device
# Matter_Plugin_1_Device.be - implements the behavior for a standard Device
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin_Root.be - implements the core features that a Matter device must implemment
# Matter_Plugin_1_Root.be - implements the core features that a Matter device must implemment
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
Expand Down
198 changes: 198 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_Fan.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#
# Matter_Plugin_2_Fan.be - implements the behavior for a generic Fan
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import matter

# Matter plug-in for core behavior

#@ solidify:Matter_Plugin_Fan,weak

class Matter_Plugin_Fan : Matter_Plugin_Device
static var TYPE = "fan" # name of the plug-in in json
static var DISPLAY_NAME = "Fan" # display name of the plug-in
# static var ARG = "" # additional argument name (or empty if none)
static var CLUSTERS = matter.consolidate_clusters(_class, {
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
# 0x0003: inherited # Identify 1.2 p.16
# 0x0004: inherited # Groups 1.3 p.21
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
0x0202: [0,1,2,3], # Fan
})
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "FanMode", "FanSpeed")
static var TYPES = { 0x002B: 2 } # Fan

# Inherited
# 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
# var node_label # name of the endpoint, used only in bridge mode, "" if none
var shadow_fan_mode
var shadow_fan_speed_pct
#############################################################
# FanMode:
# 0: Off
# 1: Low
# 2: Medium
# 3: High
# 4: On -- deprecated
# 5: Auto -- not declared as supported
# 6: Smart -- deprecated

#############################################################
# Constructor
def init(device, endpoint, config)
super(self).init(device, endpoint, config)
self.shadow_fan_mode = 0 # Off by default
self.shadow_fan_speed_pct = 0
end

#############################################################
# Model
#
def set_fan_mode(fan_mode)
fan_mode = int(fan_mode)
if (fan_mode < 0) fan_mode = 0 end # force positive
if fan_mode != self.shadow_fan_mode
self.attribute_updated(0x0202, 0x0000)
self.shadow_fan_mode = int(fan_mode)
# compute new speed
var new_speed_pct = self.shadow_fan_speed_pct
if self.shadow_fan_mode == 0 # set to Off, we need to adjust speed to 0 (4.4.6.1.1)
new_speed_pct = 0
elif self.shadow_fan_mode > 3 # Auto mode or unsupported modes, since we don't support AUTO, set speed to max
self.shadow_fan_mode = 3 # HIGH
new_speed_pct = 100
else # set to value
new_speed_pct = tasmota.scale_uint(fan_mode, 0, 3, 0, 100)
end
# adjust and advertize if speed changed
if self.shadow_fan_speed_pct != new_speed_pct
self.shadow_fan_speed_pct = new_speed_pct
self.attribute_updated(0x0202, 0x0002)
end
end
end

def set_fan_speed_pct(fan_speed_pct)
# guard value
fan_speed_pct = int(fan_speed_pct)
if (fan_speed_pct < 0) fan_speed_pct = 0 end
if (fan_speed_pct > 100) fan_speed_pct = 100 end
if fan_speed_pct != self.shadow_fan_speed_pct
self.attribute_updated(0x0202, 0x0002)
self.shadow_fan_speed_pct = fan_speed_pct
# adjust mode if needed
var new_mode = self.shadow_fan_mode
if (fan_speed_pct == 0)
new_mode = 0
else
new_mode = tasmota.scale_uint(fan_speed_pct, 1, 100, 1, 3)
end
# adjust and advertize if mode changed
if (new_mode != self.shadow_fan_mode)
self.shadow_fan_mode = new_mode
self.attribute_updated(0x0202, 0x0000)
end
end
end

#############################################################
# read an attribute
#
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute

# ====================================================================================================
if cluster == 0x0202 # ========== Fan ==========
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- FanMode / enum8 ----------
return tlv_solo.set(TLV.U1, self.shadow_fan_mode)
elif attribute == 0x0001 # ---------- FanModeSequence / enum8 ----------
return tlv_solo.set(TLV.U1, 2) # Off/Low/Med/High/Auto
elif attribute == 0x0002 # ---------- PercentSetting / enum8 ----------
return tlv_solo.set(TLV.U1, self.shadow_fan_speed_pct)
elif attribute == 0x0003 # ---------- PercentSetting / enum8 ----------
return tlv_solo.set(TLV.U1, self.shadow_fan_speed_pct)
end

end
return super(self).read_attribute(session, ctx, tlv_solo)
end

#############################################################
# MVC Model
#
# Controller write attributes
#############################################################
#############################################################
# write attribute
def write_attribute(session, ctx, write_data)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute

# ====================================================================================================
if cluster == 0x0202 # ========== Fan ==========
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- FanMode / enum8 ----------
if type(write_data) == 'int'
self.set_fan_mode(write_data)
self.publish_command('FanMode', self.shadow_fan_mode, 'FanSpeed', self.shadow_fan_speed_pct)
return true
else
ctx.status = matter.CONSTRAINT_ERROR
return false
end
elif attribute == 0x0002 # ---------- PercentSetting / enum8 ----------
if type(write_data) == 'int'
self.set_fan_speed_pct(write_data)
self.publish_command('FanMode', self.shadow_fan_mode, 'FanSpeed', self.shadow_fan_speed_pct)
return true
else
ctx.status = matter.CONSTRAINT_ERROR
return false
end
end

end
# return super(self).read_attribute(session, ctx, tlv_solo) # not useful as there is nothing in superclass
return nil
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload)
var val_fan_mode = payload.find("FanMode")
if val_fan_mode != nil
self.set_fan_mode(int(val_fan_mode))
end
var val_fan_speed = payload.find("FanSpeed")
if val_fan_speed != nil
self.set_fan_speed_pct(int(val_fan_speed))
end
# super(self).update_virtual(payload) # not useful as there is nothing in superclass
end

end
matter.Plugin_Fan = Matter_Plugin_Fan
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin_3_Sensor_Waterleak.be - implements the behavior for a Water leak Sensor
# Matter_Plugin_2_Sensor_Waterleak.be - implements the behavior for a Water leak Sensor
#
# Copyright (C) 2024 Stephan Hadinger & Theo Arends
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Matter_Plugin_Bridge_8_Sensor_Rain.be - implements Rain Sensor via HTTP to Tasmota
# Matter_Plugin_Bridge_Sensor_Rain.be - implements Rain Sensor via HTTP to Tasmota
#
# Copyright (C) 2024 Stephan Hadinger & Theo Arends
#
Expand Down
33 changes: 33 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_9_Virt_Fan.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# Matter_Plugin_Virt_Fan.be - implements the behavior for a Virtual Fan
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import matter

# Matter plug-in for core behavior

#@ solidify:Matter_Plugin_Virt_Fan,weak

class Matter_Plugin_Virt_Fan : Matter_Plugin_Fan
static var TYPE = "v_fan" # name of the plug-in in json
static var DISPLAY_NAME = "v.Fan" # display name of the plug-in
static var ARG = "" # no arg for virtual device
static var ARG_HINT = "_Not used_" # Hint for entering the Argument (inside 'placeholder')
static var VIRTUAL = true # virtual device
end
matter.Plugin_Virt_Fan = Matter_Plugin_Virt_Fan
1 change: 1 addition & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_UI.be
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Matter_UI
"|temperature|pressure|illuminance|humidity|occupancy|onoff|contact|flow|rain|waterleak"
"|airquality"
"|-virtual|v_relay|v_light0|v_light1|v_light2|v_light3"
"|v_fan"
"|v_temp|v_pressure|v_illuminance|v_humidity|v_occupancy|v_contact|v_flow|v_rain|v_waterleak"
"|v_airquality"
static var _CLASSES_TYPES2= "|http_relay|http_light0|http_light1|http_light2|http_light3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1359,10 +1359,11 @@ be_local_class(Matter_Plugin,
be_nested_map(51,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key_weak(FEATURE_MAPS, -1), be_const_simple_instance(be_nested_simple_instance(&be_class_map, {
be_const_map( * be_nested_map(2,
be_const_map( * be_nested_map(3,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key_int(258, -1), be_const_int(5) },
{ be_const_key_int(49, -1), be_const_int(4) },
{ be_const_key_int(49, 2), be_const_int(4) },
{ be_const_key_int(514, -1), be_const_int(2) },
})) ) } )) },
{ be_const_key_weak(append_state_json, -1), be_const_closure(class_Matter_Plugin_append_state_json_closure) },
{ be_const_key_weak(parse_configuration, -1), be_const_closure(class_Matter_Plugin_parse_configuration_closure) },
Expand Down
Loading