Skip to content

Commit 7a82b8b

Browse files
authored
feat(Matter): Creates New Matter Fan Controller Endpoint (#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

Diff for: CMakeLists.txt

+1
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

Diff for: libraries/Matter/examples/MatterFan/MatterFan.ino

+202
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+
}

Diff for: libraries/Matter/examples/MatterFan/ci.json

+7
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+
}

Diff for: libraries/Matter/keywords.txt

+33-1
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

Diff for: libraries/Matter/src/Matter.h

+2
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();

Diff for: libraries/Matter/src/MatterEndPoint.h

+71
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

0 commit comments

Comments
 (0)