Skip to content

Commit bc1c55f

Browse files
committed
feat(matter): creates new matter fan controller endpoint
1 parent af84da6 commit bc1c55f

File tree

8 files changed

+684
-1
lines changed

8 files changed

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

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
// WiFi is manually set and started
30+
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
31+
const char *password = "your-password"; // Change this to your WiFi password
32+
33+
void setup() {
34+
// Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off)
35+
pinMode(buttonPin, INPUT_PULLUP);
36+
// Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly
37+
pinMode(analogPin, INPUT);
38+
analogReadResolution(10); // 10 bits resolution reading 0..1023
39+
40+
Serial.begin(115200);
41+
while (!Serial) {
42+
delay(100);
43+
}
44+
45+
// We start by connecting to a WiFi network
46+
Serial.print("Connecting to ");
47+
Serial.println(ssid);
48+
// enable IPv6
49+
WiFi.enableIPv6(true);
50+
// Manually connect to WiFi
51+
WiFi.begin(ssid, password);
52+
// Wait for connection
53+
while (WiFi.status() != WL_CONNECTED) {
54+
delay(500);
55+
Serial.print(".");
56+
}
57+
Serial.println("\r\nWiFi connected");
58+
Serial.println("IP address: ");
59+
Serial.println(WiFi.localIP());
60+
delay(500);
61+
62+
// On Boot or Reset, Fan is set at 0% speed, OFF, changing between OFF, ON, SMART and HIGH
63+
Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH);
64+
65+
// callback functions would control Fan motor
66+
// the Matter Controller will send new data whenver the User APP or Automation request
67+
68+
// single feature callbacks take place before the generic (all features) callback
69+
// This callback will be executed whenever the speed percent matter attribute is updated
70+
Fan.onChangeSpeedPercent([](uint8_t speedPercent) {
71+
// setting speed to Zero, while the Fan is ON, shall turn the Fan OFF
72+
if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) {
73+
// ATTR_SET do not update the attribute, just SET it to avoid inifinite loop
74+
return Fan.setOnOff(false, Fan.ATTR_SET);
75+
}
76+
// changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON
77+
if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) {
78+
// ATTR_SET do not update the attribute, just SET it to avoid inifinite loop
79+
return Fan.setOnOff(true, Fan.ATTR_SET);
80+
}
81+
// for other case, just return true
82+
return true;
83+
});
84+
85+
// This callback will be executed whenever the fan mode matter attribute is updated
86+
// This will take action when user APP starts the Fan by changing the mode
87+
Fan.onChangeMode([](MatterFan::FanMode_t fanMode) {
88+
// when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50%
89+
if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) {
90+
Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode));
91+
// ATTR_SET do not update the attribute, just SET it to avoid inifinite loop
92+
return Fan.setSpeedPercent(50, Fan.ATTR_SET);
93+
}
94+
return true;
95+
});
96+
97+
// Generic callback will be executed as soon as a single feature callback is done
98+
// In this example, it will just print status messages
99+
Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) {
100+
// just report state
101+
Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent);
102+
// returns success
103+
return true;
104+
});
105+
106+
// Matter beginning - Last step, after all EndPoints are initialized
107+
Matter.begin();
108+
// This may be a restart of a already commissioned Matter accessory
109+
if (Matter.isDeviceCommissioned()) {
110+
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
111+
}
112+
}
113+
114+
// Builtin Button control
115+
uint32_t button_time_stamp = 0; // debouncing control
116+
bool button_state = false; // false = released | true = pressed
117+
const uint32_t debouceTime = 250; // button debouncing time (ms)
118+
const uint32_t decommissioningTimeout = 10000; // keep the button pressed for 10s to decommission the Matter Fabric
119+
120+
void loop() {
121+
// Check Matter Accessory Commissioning state, which may change during execution of loop()
122+
if (!Matter.isDeviceCommissioned()) {
123+
Serial.println("");
124+
Serial.println("Matter Node is not commissioned yet.");
125+
Serial.println("Initiate the device discovery in your Matter environment.");
126+
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
127+
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
128+
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
129+
// waits for Matter Generic Switch Commissioning.
130+
uint32_t timeCount = 0;
131+
while (!Matter.isDeviceCommissioned()) {
132+
delay(100);
133+
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
134+
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
135+
}
136+
}
137+
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
138+
}
139+
140+
// A builtin button is used to trigger and send a command to the Matter Controller
141+
// Check if the button has been pressed
142+
if (digitalRead(buttonPin) == LOW && !button_state) {
143+
// deals with button debouncing
144+
button_time_stamp = millis(); // record the time while the button is pressed.
145+
button_state = true; // pressed.
146+
}
147+
148+
// Onboard User Button is used as a smart button or to decommission it
149+
uint32_t time_diff = millis() - button_time_stamp;
150+
if (button_state && time_diff > debouceTime && digitalRead(buttonPin) == HIGH) {
151+
button_state = false; // released
152+
// button is released - toggle Fan On/Off
153+
Fan.toggle();
154+
Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF");
155+
156+
// Factory reset is triggered if the button is pressed longer than 10 seconds
157+
if (time_diff > decommissioningTimeout) {
158+
Serial.println("Decommissioning the Generic Switch Matter Accessory. It shall be commissioned again.");
159+
Matter.decommission();
160+
}
161+
}
162+
163+
// checks Analog pin and adjust the speed only if it has changed
164+
static int lastRead = 0;
165+
// analog values (0..1023) / 103 => mapped into 10 steps (0..9)
166+
int anaVal = analogRead(analogPin) / 103;
167+
if (lastRead != anaVal) {
168+
// speed percent moves in steps of 10. Range is 10..100
169+
if (Fan.setSpeedPercent((anaVal + 1) * 10)) {
170+
lastRead = anaVal;
171+
}
172+
}
173+
}

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

+72
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,87 @@
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+
28+
enum attrOperation_t {
29+
ATTR_SET = false,
30+
ATTR_UPDATE = true
31+
};
32+
2533
uint16_t getEndPointId() {
2634
return endpoint_id;
2735
}
36+
2837
void setEndPointId(uint16_t ep) {
2938
endpoint_id = ep;
3039
}
40+
41+
// helper functions for attribute manipulation
42+
attribute_t * getAttribute(uint32_t cluster_id, uint32_t attribute_id){
43+
if (endpoint_id == 0) {
44+
log_e("Endpoint ID is not set");
45+
return NULL;
46+
}
47+
endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id);
48+
if (endpoint == NULL) {
49+
log_e("Endpoint [%d] not found", endpoint_id);
50+
return NULL;
51+
}
52+
cluster_t *cluster = cluster::get(endpoint, cluster_id);
53+
if (cluster == NULL) {
54+
log_e("Cluster [%d] not found", cluster_id);
55+
return NULL;
56+
}
57+
attribute_t *attribute = attribute::get(cluster, attribute_id);
58+
if (attribute == NULL) {
59+
log_e("Attribute [%d] not found", attribute_id);
60+
return NULL;
61+
}
62+
return attribute;
63+
}
64+
65+
// get the value of an attribute from its cluster id and attribute it
66+
bool getAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){
67+
attribute_t *attribute = getAttribute(cluster_id, attribute_id);
68+
if (attribute == NULL) {
69+
return false;
70+
}
71+
if (attribute::get_val(attribute, attrVal) == ESP_OK) {
72+
log_v("GET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
73+
return true;
74+
}
75+
log_e("GET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
76+
return false;
77+
}
78+
79+
// set the value of an attribute from its cluster id and attribute it
80+
bool setAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){
81+
attribute_t *attribute = getAttribute(cluster_id, attribute_id);
82+
if (attribute == NULL) {
83+
return false;
84+
}
85+
if (attribute::set_val(attribute, attrVal) == ESP_OK) {
86+
log_v("SET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
87+
return true;
88+
}
89+
log_e("SET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
90+
return false;
91+
}
92+
93+
// update the value of an attribute from its cluster id and attribute it
94+
bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){
95+
if (attribute::update(endpoint_id, cluster_id, attribute_id, attrVal) == ESP_OK) {
96+
log_v("Update Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
97+
return true;
98+
}
99+
log_e("Update FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32);
100+
return false;
101+
}
102+
31103
// this function is called by Matter internal event processor. It could be overwritten by the application, if necessary.
32104
virtual bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) = 0;
33105

0 commit comments

Comments
 (0)