Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7a82b8b

Browse files
authoredDec 9, 2024
feat(Matter): Creates New Matter Fan Controller Endpoint (espressif#10691)
* feat(matter): creates new matter fan controller endpoint
1 parent 76d1f9e commit 7a82b8b

File tree

8 files changed

+709
-1
lines changed

8 files changed

+709
-1
lines changed
 

‎CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS
174174
libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp
175175
libraries/Matter/src/MatterEndpoints/MatterColorLight.cpp
176176
libraries/Matter/src/MatterEndpoints/MatterEnhancedColorLight.cpp
177+
libraries/Matter/src/MatterEndpoints/MatterFan.cpp
177178
libraries/Matter/src/Matter.cpp)
178179

179180
set(ARDUINO_LIBRARY_PPP_SRCS
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Matter Manager
16+
#include <Matter.h>
17+
#include <WiFi.h>
18+
19+
// List of Matter Endpoints for this Node
20+
// Fan Endpoint - On/Off control + Speed Percent Control + Fan Modes
21+
MatterFan Fan;
22+
23+
// set your board USER BUTTON pin here - used for toggling On/Off
24+
const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9.
25+
26+
// set your board Analog Pin here - used for changing the Fan speed
27+
const uint8_t analogPin = A0; // Analog Pin depends on each board
28+
29+
// set your board PWM Pin here - used for controlling the Fan speed (DC motor example)
30+
// for this example, it will use the builtin board RGB LED to simulate the Fan DC motor using its brightness
31+
#ifdef RGB_BUILTIN
32+
const uint8_t dcMotorPin = RGB_BUILTIN;
33+
#else
34+
const uint8_t dcMotorPin = 2; // Set your pin here if your board has not defined LED_BUILTIN
35+
#warning "Do not forget to set the RGB LED pin"
36+
#endif
37+
38+
// WiFi is manually set and started
39+
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
40+
const char *password = "your-password"; // Change this to your WiFi password
41+
42+
void fanDCMotorDrive(bool fanState, uint8_t speedPercent) {
43+
// drive the Fan DC motor
44+
if (fanState == false) {
45+
// turn off the Fan
46+
digitalWrite(dcMotorPin, LOW);
47+
} else {
48+
// set the Fan speed
49+
uint8_t fanDCMotorPWM = map(speedPercent, 0, 100, 0, 255);
50+
#ifdef RGB_BUILTIN
51+
rgbLedWrite(dcMotorPin, fanDCMotorPWM, fanDCMotorPWM, fanDCMotorPWM);
52+
#else
53+
analogWrite(dcMotorPin, fanDCMotorPWM);
54+
#endif
55+
}
56+
}
57+
58+
void setup() {
59+
// Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off)
60+
pinMode(buttonPin, INPUT_PULLUP);
61+
// Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly
62+
pinMode(analogPin, INPUT);
63+
analogReadResolution(10); // 10 bits resolution reading 0..1023
64+
// Initialize the PWM output pin for a Fan DC motor
65+
pinMode(dcMotorPin, OUTPUT);
66+
67+
Serial.begin(115200);
68+
while (!Serial) {
69+
delay(100);
70+
}
71+
72+
// We start by connecting to a WiFi network
73+
Serial.print("Connecting to ");
74+
Serial.println(ssid);
75+
// enable IPv6
76+
WiFi.enableIPv6(true);
77+
// Manually connect to WiFi
78+
WiFi.begin(ssid, password);
79+
// Wait for connection
80+
while (WiFi.status() != WL_CONNECTED) {
81+
delay(500);
82+
Serial.print(".");
83+
}
84+
Serial.println("\r\nWiFi connected");
85+
Serial.println("IP address: ");
86+
Serial.println(WiFi.localIP());
87+
delay(500);
88+
89+
// On Boot or Reset, Fan is set at 0% speed, OFF, changing between OFF, ON, SMART and HIGH
90+
Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH);
91+
92+
// callback functions would control Fan motor
93+
// the Matter Controller will send new data whenever the User APP or Automation request
94+
95+
// single feature callbacks take place before the generic (all features) callback
96+
// This callback will be executed whenever the speed percent matter attribute is updated
97+
Fan.onChangeSpeedPercent([](uint8_t speedPercent) {
98+
// setting speed to Zero, while the Fan is ON, shall turn the Fan OFF
99+
if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) {
100+
// ATTR_SET do not update the attribute, just SET it to avoid infinite loop
101+
return Fan.setOnOff(false, Fan.ATTR_SET);
102+
}
103+
// changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON
104+
if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) {
105+
// ATTR_SET do not update the attribute, just SET it to avoid infinite loop
106+
return Fan.setOnOff(true, Fan.ATTR_SET);
107+
}
108+
// for other case, just return true
109+
return true;
110+
});
111+
112+
// This callback will be executed whenever the fan mode matter attribute is updated
113+
// This will take action when user APP starts the Fan by changing the mode
114+
Fan.onChangeMode([](MatterFan::FanMode_t fanMode) {
115+
// when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50%
116+
if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) {
117+
Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode));
118+
// ATTR_SET do not update the attribute, just SET it to avoid infinite loop
119+
return Fan.setSpeedPercent(50, Fan.ATTR_SET);
120+
}
121+
return true;
122+
});
123+
124+
// Generic callback will be executed as soon as a single feature callback is done
125+
// In this example, it will just print status messages
126+
Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) {
127+
// just report state
128+
Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent);
129+
// drive the Fan DC motor
130+
fanDCMotorDrive(fanMode != MatterFan::FAN_MODE_OFF, speedPercent);
131+
// returns success
132+
return true;
133+
});
134+
135+
// Matter beginning - Last step, after all EndPoints are initialized
136+
Matter.begin();
137+
// This may be a restart of a already commissioned Matter accessory
138+
if (Matter.isDeviceCommissioned()) {
139+
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
140+
}
141+
}
142+
143+
// Builtin Button control
144+
uint32_t button_time_stamp = 0; // debouncing control
145+
bool button_state = false; // false = released | true = pressed
146+
const uint32_t debouceTime = 250; // button debouncing time (ms)
147+
const uint32_t decommissioningTimeout = 10000; // keep the button pressed for 10s to decommission the Matter Fabric
148+
149+
void loop() {
150+
// Check Matter Accessory Commissioning state, which may change during execution of loop()
151+
if (!Matter.isDeviceCommissioned()) {
152+
Serial.println("");
153+
Serial.println("Matter Node is not commissioned yet.");
154+
Serial.println("Initiate the device discovery in your Matter environment.");
155+
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
156+
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
157+
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
158+
// waits for Matter Generic Switch Commissioning.
159+
uint32_t timeCount = 0;
160+
while (!Matter.isDeviceCommissioned()) {
161+
delay(100);
162+
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
163+
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
164+
}
165+
}
166+
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
167+
}
168+
169+
// A builtin button is used to trigger and send a command to the Matter Controller
170+
// Check if the button has been pressed
171+
if (digitalRead(buttonPin) == LOW && !button_state) {
172+
// deals with button debouncing
173+
button_time_stamp = millis(); // record the time while the button is pressed.
174+
button_state = true; // pressed.
175+
}
176+
177+
// Onboard User Button is used as a smart button or to decommission it
178+
uint32_t time_diff = millis() - button_time_stamp;
179+
if (button_state && time_diff > debouceTime && digitalRead(buttonPin) == HIGH) {
180+
button_state = false; // released
181+
// button is released - toggle Fan On/Off
182+
Fan.toggle();
183+
Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF");
184+
185+
// Factory reset is triggered if the button is pressed longer than 10 seconds
186+
if (time_diff > decommissioningTimeout) {
187+
Serial.println("Decommissioning the Generic Switch Matter Accessory. It shall be commissioned again.");
188+
Matter.decommission();
189+
}
190+
}
191+
192+
// checks Analog pin and adjust the speed only if it has changed
193+
static int lastRead = 0;
194+
// analog values (0..1023) / 103 => mapped into 10 steps (0..9)
195+
int anaVal = analogRead(analogPin) / 103;
196+
if (lastRead != anaVal) {
197+
// speed percent moves in steps of 10. Range is 10..100
198+
if (Fan.setSpeedPercent((anaVal + 1) * 10)) {
199+
lastRead = anaVal;
200+
}
201+
}
202+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"fqbn_append": "PartitionScheme=huge_app",
3+
"requires": [
4+
"CONFIG_SOC_WIFI_SUPPORTED=y",
5+
"CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y"
6+
]
7+
}

‎libraries/Matter/keywords.txt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ MatterColorTemperatureLight KEYWORD1
1515
MatterColorLight KEYWORD1
1616
MatterEnhancedColorLight KEYWORD1
1717
MatterEndPoint KEYWORD1
18+
MatterFan KEYWORD1
19+
FanMode_t KEYWORD1
20+
FanModeSequence_t KEYWORD1
1821

1922
#######################################
2023
# Methods and Functions (KEYWORD2)
@@ -32,6 +35,7 @@ decommission KEYWORD2
3235
attributeChangeCB KEYWORD2
3336
setOnOff KEYWORD2
3437
getOnOff KEYWORD2
38+
toggle KEYWORD2
3539
setBrightness KEYWORD2
3640
getBrightness KEYWORD2
3741
setColorTemperature KEYWORD2
@@ -40,14 +44,24 @@ setColorRGB KEYWORD2
4044
getColorRGB KEYWORD2
4145
setColorHSV KEYWORD2
4246
getColorHSV KEYWORD2
43-
toggle KEYWORD2
4447
updateAccessory KEYWORD2
4548
onChange KEYWORD2
4649
onChangeOnOff KEYWORD2
4750
onChangeBrightness KEYWORD2
4851
onChangeColorTemperature KEYWORD2
4952
onChangeColorHSV KEYWORD2
5053
click KEYWORD2
54+
getAttribute KEYWORD2
55+
getAttributeVal KEYWORD2
56+
setAttributeVal KEYWORD2
57+
updateAttributeVal KEYWORD2
58+
getFanModeString KEYWORD2
59+
setSpeedPercent KEYWORD2
60+
getSpeedPercent KEYWORD2
61+
setMode KEYWORD2
62+
getMode KEYWORD2
63+
onChangeMode KEYWORD2
64+
onChangeSpeedPercent KEYWORD2
5165

5266
#######################################
5367
# Constants (LITERAL1)
@@ -56,3 +70,21 @@ click KEYWORD2
5670
MAX_BRIGHTNESS LITERAL1
5771
MAX_COLOR_TEMPERATURE LITERAL1
5872
MIN_COLOR_TEMPERATURE LITERAL1
73+
ATTR_SET LITERAL1
74+
ATTR_UPDATE LITERAL1
75+
MAX_SPEED LITERAL1
76+
MIN_SPEED LITERAL1
77+
OFF_SPEED LITERAL1
78+
FAN_MODE_OFF LITERAL1
79+
FAN_MODE_LOW LITERAL1
80+
FAN_MODE_MEDIUM LITERAL1
81+
FAN_MODE_HIGH LITERAL1
82+
FAN_MODE_ON LITERAL1
83+
FAN_MODE_AUTO LITERAL1
84+
FAN_MODE_SMART LITERAL1
85+
FAN_MODE_SEQ_OFF_LOW_MED_HIGH LITERAL1
86+
FAN_MODE_SEQ_OFF_LOW_HIGH LITERAL1
87+
FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO LITERAL1
88+
FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO LITERAL1
89+
FAN_MODE_SEQ_OFF_HIGH_AUTO LITERAL1
90+
FAN_MODE_SEQ_OFF_HIGH LITERAL1

‎libraries/Matter/src/Matter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <MatterEndpoints/MatterColorTemperatureLight.h>
2626
#include <MatterEndpoints/MatterColorLight.h>
2727
#include <MatterEndpoints/MatterEnhancedColorLight.h>
28+
#include <MatterEndpoints/MatterFan.h>
2829

2930
using namespace esp_matter;
3031

@@ -56,6 +57,7 @@ class ArduinoMatter {
5657
friend class MatterColorTemperatureLight;
5758
friend class MatterColorLight;
5859
friend class MatterEnhancedColorLight;
60+
friend class MatterFan;
5961

6062
protected:
6163
static void _init();

‎libraries/Matter/src/MatterEndPoint.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,86 @@
1919
#include <Matter.h>
2020
#include <functional>
2121

22+
using namespace esp_matter;
23+
2224
// Matter Endpoint Base Class. Controls the endpoint ID and allows the child class to overwrite attribute change call
2325
class MatterEndPoint {
2426
public:
27+
enum attrOperation_t {
28+
ATTR_SET = false,
29+
ATTR_UPDATE = true
30+
};
31+
2532
uint16_t getEndPointId() {
2633
return endpoint_id;
2734
}
35+
2836
void setEndPointId(uint16_t ep) {
2937
endpoint_id = ep;
3038
}
39+
40+
// helper functions for attribute manipulation
41+
attribute_t *getAttribute(uint32_t cluster_id, uint32_t attribute_id) {
42+
if (endpoint_id == 0) {
43+
log_e("Endpoint ID is not set");
44+
return NULL;
45+
}
46+
endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id);
47+
if (endpoint == NULL) {
48+
log_e("Endpoint [%d] not found", endpoint_id);
49+
return NULL;
50+
}
51+
cluster_t *cluster = cluster::get(endpoint, cluster_id);
52+
if (cluster == NULL) {
53+
log_e("Cluster [%d] not found", cluster_id);
54+
return NULL;
55+
}
56+
attribute_t *attribute = attribute::get(cluster, attribute_id);
57+
if (attribute == NULL) {
58+
log_e("Attribute [%d] not found", attribute_id);
59+
return NULL;
60+
}
61+
return attribute;
62+
}
63+
64+
// get the value of an attribute from its cluster id and attribute it
65+
bool getAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) {
66+
attribute_t *attribute = getAttribute(cluster_id, attribute_id);
67+
if (attribute == NULL) {
68+
return false;
69+
}
70+
if (attribute::get_val(attribute, attrVal) == ESP_OK) {
71+
log_v("GET_VAL Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
72+
return true;
73+
}
74+
log_e("GET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
75+
return false;
76+
}
77+
78+
// set the value of an attribute from its cluster id and attribute it
79+
bool setAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) {
80+
attribute_t *attribute = getAttribute(cluster_id, attribute_id);
81+
if (attribute == NULL) {
82+
return false;
83+
}
84+
if (attribute::set_val(attribute, attrVal) == ESP_OK) {
85+
log_v("SET_VAL Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
86+
return true;
87+
}
88+
log_e("SET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
89+
return false;
90+
}
91+
92+
// update the value of an attribute from its cluster id and attribute it
93+
bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) {
94+
if (attribute::update(endpoint_id, cluster_id, attribute_id, attrVal) == ESP_OK) {
95+
log_v("Update Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
96+
return true;
97+
}
98+
log_e("Update FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
99+
return false;
100+
}
101+
31102
// this function is called by Matter internal event processor. It could be overwritten by the application, if necessary.
32103
virtual bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) = 0;
33104

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <sdkconfig.h>
16+
#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL
17+
18+
#include <Matter.h>
19+
#include <MatterEndpoints/MatterFan.h>
20+
21+
using namespace esp_matter;
22+
using namespace esp_matter::endpoint;
23+
using namespace esp_matter::cluster;
24+
using namespace chip::app::Clusters;
25+
26+
// string helper for the FAN MODE
27+
const char *MatterFan::fanModeString[7] = {"OFF", "LOW", "MEDIUM", "HIGH", "ON", "AUTO", "SMART"};
28+
// bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums
29+
const uint8_t MatterFan::fanModeSequence[6] = {fanSeqModeOffLowMedHigh, fanSeqModeOffLowHigh, fanSeqModeOffLowMedHighAuto,
30+
fanSeqModeOffLowHighAuto, fanSeqModeOffHighAuto, fanSeqModeOffHigh};
31+
32+
// Constructor and Method Definitions
33+
MatterFan::MatterFan() {}
34+
35+
MatterFan::~MatterFan() {
36+
end();
37+
}
38+
39+
bool MatterFan::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) {
40+
bool ret = true;
41+
42+
if (!started) {
43+
log_e("Matter Fan device has not begun.");
44+
return false;
45+
}
46+
47+
log_d("Fan Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32);
48+
49+
if (endpoint_id == getEndPointId() && cluster_id == FanControl::Id) {
50+
switch (attribute_id) {
51+
case FanControl::Attributes::FanMode::Id:
52+
log_v("FanControl Fan Mode changed to %s (%x)", val->val.u8 < 7 ? fanModeString[val->val.u8] : "Unknown", val->val.u8);
53+
if (_onChangeModeCB != NULL) {
54+
ret &= _onChangeModeCB((FanMode_t)val->val.u8);
55+
}
56+
if (_onChangeCB != NULL) {
57+
ret &= _onChangeCB((FanMode_t)val->val.u8, currentPercent);
58+
}
59+
if (ret == true) {
60+
currentFanMode = (FanMode_t)val->val.u8;
61+
}
62+
break;
63+
case FanControl::Attributes::PercentSetting::Id:
64+
case FanControl::Attributes::PercentCurrent::Id:
65+
log_v("FanControl Percent %s changed to %d", attribute_id == FanControl::Attributes::PercentSetting::Id ? "SETTING" : "CURRENT", val->val.u8);
66+
if (_onChangeSpeedCB != NULL) {
67+
ret &= _onChangeSpeedCB(val->val.u8);
68+
}
69+
if (_onChangeCB != NULL) {
70+
ret &= _onChangeCB(currentFanMode, val->val.u8);
71+
}
72+
if (ret == true) {
73+
// change setting speed percent
74+
currentPercent = val->val.u8;
75+
setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, val);
76+
setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, val);
77+
}
78+
break;
79+
}
80+
}
81+
82+
return ret;
83+
}
84+
85+
bool MatterFan::begin(uint8_t percent, FanMode_t fanMode, FanModeSequence_t fanModeSeq) {
86+
ArduinoMatter::_init();
87+
88+
// endpoint handles can be used to add/modify clusters.
89+
fan::config_t fan_config;
90+
fan_config.fan_control.fan_mode = fanMode;
91+
fan_config.fan_control.percent_current = percent;
92+
fan_config.fan_control.percent_setting = percent;
93+
fan_config.fan_control.fan_mode_sequence = fanModeSeq;
94+
validFanModes = fanModeSequence[fanModeSeq];
95+
96+
endpoint_t *endpoint = fan::create(node::get(), &fan_config, ENDPOINT_FLAG_NONE, (void *)this);
97+
98+
if (endpoint == nullptr) {
99+
log_e("Failed to create Fan endpoint");
100+
return false;
101+
}
102+
103+
currentFanMode = fanMode;
104+
currentPercent = percent;
105+
106+
setEndPointId(endpoint::get_id(endpoint));
107+
log_i("Fan created with endpoint_id %d", getEndPointId());
108+
started = true;
109+
return true;
110+
}
111+
112+
void MatterFan::end() {
113+
started = false;
114+
}
115+
116+
bool MatterFan::setMode(FanMode_t newMode, bool performUpdate) {
117+
if (!started) {
118+
log_w("Matter Fan device has not begun.");
119+
return false;
120+
}
121+
// avoid processing the a "no-change"
122+
if (currentFanMode == newMode) {
123+
return true;
124+
}
125+
126+
// check if the mode is valid based on the sequence used in its creation
127+
if (!(validFanModes & (1 << newMode))) {
128+
log_e("Invalid Fan Mode %s for the current Fan Mode Sequence.", fanModeString[newMode]);
129+
return false;
130+
}
131+
132+
esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL);
133+
if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) {
134+
log_e("Failed to get Fan Mode Attribute.");
135+
return false;
136+
}
137+
if (modeVal.val.u8 != (uint8_t)newMode) {
138+
modeVal.val.u8 = (uint8_t)newMode;
139+
bool ret;
140+
if (performUpdate) {
141+
ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal);
142+
} else {
143+
ret = setAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal);
144+
}
145+
if (!ret) {
146+
log_e("Failed to %s Fan Mode Attribute.", performUpdate ? "update" : "set");
147+
return false;
148+
}
149+
}
150+
currentFanMode = newMode;
151+
log_v("Fan Mode %s to %s ==> onOffState[%s]", performUpdate ? "updated" : "set", fanModeString[currentFanMode], getOnOff() ? "ON" : "OFF");
152+
return true;
153+
}
154+
155+
// this function will change the Fan Speed by calling the user application callback
156+
// it is up to the application to decide to turn on, off or change the speed of the fan
157+
bool MatterFan::setSpeedPercent(uint8_t newPercent, bool performUpdate) {
158+
if (!started) {
159+
log_w("Matter Fan device has not begun.");
160+
return false;
161+
}
162+
// avoid processing the a "no-change"
163+
if (currentPercent == newPercent) {
164+
return true;
165+
}
166+
167+
esp_matter_attr_val_t speedVal = esp_matter_invalid(NULL);
168+
if (!getAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal)) {
169+
log_e("Failed to get Fan Speed Percent Attribute.");
170+
return false;
171+
}
172+
if (speedVal.val.u8 != newPercent) {
173+
speedVal.val.u8 = newPercent;
174+
bool ret;
175+
if (performUpdate) {
176+
ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal);
177+
} else {
178+
ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal);
179+
ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, &speedVal);
180+
}
181+
if (!ret) {
182+
log_e("Failed to %s Fan Speed Percent Attribute.", performUpdate ? "update" : "set");
183+
return false;
184+
}
185+
}
186+
currentPercent = newPercent;
187+
log_v("Fan Speed %s to %d ==> onOffState[%s]", performUpdate ? "updated" : "set", currentPercent, getOnOff() ? "ON" : "OFF");
188+
return true;
189+
}
190+
191+
bool MatterFan::setOnOff(bool newState, bool performUpdate) {
192+
if (!started) {
193+
log_w("Matter Fan device has not begun.");
194+
return false;
195+
}
196+
// avoid processing the a "no-change"
197+
if (getOnOff() == newState) {
198+
return true;
199+
}
200+
201+
esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL);
202+
if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) {
203+
log_e("Failed to get Fan Mode Attribute.");
204+
return false;
205+
}
206+
if (modeVal.val.u8 != (uint8_t)newState) {
207+
FanMode_t newMode = newState ? FAN_MODE_ON : FAN_MODE_OFF;
208+
if (!setMode(newMode, performUpdate)) {
209+
return false;
210+
}
211+
}
212+
log_v(
213+
"Fan State %s to %s :: Mode[%s]|Speed[%d]", performUpdate ? "updated" : "set", getOnOff() ? "ON" : "OFF", fanModeString[currentFanMode], currentPercent
214+
);
215+
return true;
216+
}
217+
218+
bool MatterFan::getOnOff() {
219+
return currentFanMode == FAN_MODE_OFF ? false : true;
220+
}
221+
222+
bool MatterFan::toggle(bool performUpdate) {
223+
if (getOnOff() == true) {
224+
return setOnOff(false, performUpdate);
225+
} else {
226+
return setOnOff(true, performUpdate);
227+
}
228+
}
229+
230+
#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
#include <sdkconfig.h>
17+
#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL
18+
19+
#include <Matter.h>
20+
#include <MatterEndPoint.h>
21+
#include <app-common/zap-generated/cluster-objects.h>
22+
23+
using namespace chip::app::Clusters::FanControl;
24+
25+
// Matter Fan endpoint with On/Off, Mode and Speed control
26+
27+
class MatterFan : public MatterEndPoint {
28+
public:
29+
// Fan feature constants
30+
static const uint8_t MAX_SPEED = 100; // maximum High speed
31+
static const uint8_t MIN_SPEED = 1; // minimum Low speed
32+
static const uint8_t OFF_SPEED = 0; // speed set by Matter when FAN_MODE_OFF
33+
34+
// Default Fan Modes: ON, SMART, HIGH and OFF
35+
36+
// Other mode will depend on what is the configured Fan Mode Sequence
37+
enum FanMode_t {
38+
FAN_MODE_OFF = (uint8_t)FanModeEnum::kOff,
39+
FAN_MODE_LOW = (uint8_t)FanModeEnum::kLow,
40+
FAN_MODE_MEDIUM = (uint8_t)FanModeEnum::kMedium,
41+
FAN_MODE_HIGH = (uint8_t)FanModeEnum::kHigh,
42+
FAN_MODE_ON = (uint8_t)FanModeEnum::kOn,
43+
FAN_MODE_AUTO = (uint8_t)FanModeEnum::kAuto,
44+
FAN_MODE_SMART = (uint8_t)FanModeEnum::kSmart
45+
};
46+
47+
// Menu will always have ON, OFF, HIGH and SMART.
48+
// AUTO will show up only when a AUTO SEQ is CONFIGURED
49+
// LOW and MEDIUM depend on the SEQ MODE configuration
50+
enum FanModeSequence_t {
51+
FAN_MODE_SEQ_OFF_LOW_MED_HIGH = (uint8_t)FanModeSequenceEnum::kOffLowMedHigh,
52+
FAN_MODE_SEQ_OFF_LOW_HIGH = (uint8_t)FanModeSequenceEnum::kOffLowHigh,
53+
FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffLowMedHighAuto,
54+
FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffLowHighAuto,
55+
FAN_MODE_SEQ_OFF_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffHighAuto,
56+
FAN_MODE_SEQ_OFF_HIGH = (uint8_t)FanModeSequenceEnum::kOffHigh
57+
};
58+
59+
MatterFan();
60+
~MatterFan();
61+
virtual bool begin(uint8_t percent = 0, FanMode_t fanMode = FAN_MODE_OFF, FanModeSequence_t fanModeSeq = FAN_MODE_SEQ_OFF_HIGH);
62+
void end(); // this will just stop processing Matter events
63+
64+
// returns a friendly string for the Fan Mode
65+
static const char *getFanModeString(uint8_t mode) {
66+
return fanModeString[mode];
67+
}
68+
69+
// Fan Control of current On/Off state
70+
71+
bool setOnOff(bool newState, bool performUpdate = true); // sets Fan On/Off state
72+
bool getOnOff(); // returns current Fan state
73+
bool toggle(bool performUpdate = true); // toggle Fun On/Off state
74+
75+
// Fan Control of current speed percent
76+
77+
bool setSpeedPercent(uint8_t newPercent, bool performUpdate = true); // returns true if successful
78+
uint8_t getSpeedPercent() { // returns current Fan Speed Percent
79+
return currentPercent;
80+
}
81+
82+
// Fan Control of current Fan Mode
83+
84+
bool setMode(FanMode_t newMode, bool performUpdate = true); // returns true if successful
85+
FanMode_t getMode() { // returns current Fan Mode
86+
return currentFanMode;
87+
}
88+
// used to update the state of the Fan using the current Matter Fan internal state
89+
// It is necessary to set a user callback function using onChange() to handle the physical Fan motor state
90+
91+
void updateAccessory() {
92+
if (_onChangeCB != NULL) {
93+
_onChangeCB(currentFanMode, currentPercent);
94+
}
95+
}
96+
97+
// returns current Fan speed percent
98+
operator uint8_t() {
99+
return getSpeedPercent();
100+
}
101+
// sets Fan speed percent
102+
void operator=(uint8_t speedPercent) {
103+
setSpeedPercent(speedPercent);
104+
}
105+
106+
// this function is called by Matter internal event processor. It could be overwritten by the application, if necessary.
107+
bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val);
108+
109+
// User Callback for whenever the Fan Mode (state) is changed by the Matter Controller
110+
using EndPointModeCB = std::function<bool(FanMode_t)>;
111+
void onChangeMode(EndPointModeCB onChangeCB) {
112+
_onChangeModeCB = onChangeCB;
113+
}
114+
115+
// User Callback for whenever the Fan Speed Percentage value [0..100] is changed by the Matter Controller
116+
using EndPointSpeedCB = std::function<bool(uint8_t)>;
117+
void onChangeSpeedPercent(EndPointSpeedCB onChangeCB) {
118+
_onChangeSpeedCB = onChangeCB;
119+
}
120+
121+
// User Callback for whenever any parameter is changed by the Matter Controller
122+
using EndPointCB = std::function<bool(FanMode_t, uint8_t)>;
123+
void onChange(EndPointCB onChangeCB) {
124+
_onChangeCB = onChangeCB;
125+
}
126+
127+
protected:
128+
bool started = false;
129+
uint8_t validFanModes = 0; // bitmap for valid Fan Modes - index of fanModeSequence[]
130+
131+
uint8_t currentPercent = 0; // current speed percent
132+
FanMode_t currentFanMode = FAN_MODE_OFF; // current Fan Mode
133+
EndPointModeCB _onChangeModeCB = NULL;
134+
EndPointSpeedCB _onChangeSpeedCB = NULL;
135+
EndPointCB _onChangeCB = NULL;
136+
137+
// bitmap for Fan Sequence Modes (OFF, LOW, MEDIUM, HIGH, AUTO)
138+
static const uint8_t fanSeqModeOff = 0x01;
139+
static const uint8_t fanSeqModeLow = 0x02;
140+
static const uint8_t fanSeqModeMedium = 0x04;
141+
static const uint8_t fanSeqModeHigh = 0x08;
142+
static const uint8_t fanSeqModeOn = 0x10;
143+
static const uint8_t fanSeqModeAuto = 0x20;
144+
static const uint8_t fanSeqModeSmart = 0x40;
145+
146+
// bitmap for common modes: ON, OFF, HIGH and SMART
147+
static const uint8_t fanSeqCommonModes = fanSeqModeOff | fanSeqModeOn | fanSeqModeHigh | fanSeqModeSmart;
148+
149+
static const uint8_t fanSeqModeOffLowMedHigh = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium;
150+
static const uint8_t fanSeqModeOffLowHigh = fanSeqCommonModes | fanSeqModeLow;
151+
static const uint8_t fanSeqModeOffLowMedHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium | fanSeqModeAuto;
152+
static const uint8_t fanSeqModeOffLowHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeAuto;
153+
static const uint8_t fanSeqModeOffHighAuto = fanSeqCommonModes | fanSeqModeAuto;
154+
static const uint8_t fanSeqModeOffHigh = fanSeqCommonModes;
155+
156+
// bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums
157+
static const uint8_t fanModeSequence[6];
158+
159+
// string helper for the FAN MODE
160+
static const char *fanModeString[7];
161+
};
162+
163+
#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */

0 commit comments

Comments
 (0)
Please sign in to comment.