Skip to content

Commit e3cc040

Browse files
feat(matter): new Matter Endpoint for Thermostat (#10755)
* feat(matter): add new matter endpoint for thermostat * fix(matter): not used variable from log_e() message * feat(matter): adds specifc type name for thermostat auto mode enabled * fix(matter): suggested changes in pr review * feat(matter): added the whole list of thermostat operational modes * fix(matter): fixed type in thermostat operational modes * ci(pre-commit): Apply automatic fixes * fix(matter): typos caught by CI codespell --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent b07eb17 commit e3cc040

File tree

7 files changed

+872
-0
lines changed

7 files changed

+872
-0
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS
181181
libraries/Matter/src/MatterEndpoints/MatterPressureSensor.cpp
182182
libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp
183183
libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp
184+
libraries/Matter/src/MatterEndpoints/MatterThermostat.cpp
184185
libraries/Matter/src/Matter.cpp)
185186

186187
set(ARDUINO_LIBRARY_PPP_SRCS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
/*
16+
This example is an example code that will create a Matter Device which can be
17+
commissioned and controlled from a Matter Environment APP.
18+
Additionally the ESP32 will send debug messages indicating the Matter activity.
19+
Turning DEBUG Level ON may be useful to following Matter Accessory and Controller messages.
20+
*/
21+
22+
// Matter Manager
23+
#include <Matter.h>
24+
#include <WiFi.h>
25+
26+
// List of Matter Endpoints for this Node
27+
// Matter Thermostat Endpoint
28+
MatterThermostat SimulatedThermostat;
29+
30+
// WiFi is manually set and started
31+
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
32+
const char *password = "your-password"; // Change this to your WiFi password
33+
34+
// set your board USER BUTTON pin here - decommissioning button
35+
const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button.
36+
37+
// Button control - decommision the Matter Node
38+
uint32_t button_time_stamp = 0; // debouncing control
39+
bool button_state = false; // false = released | true = pressed
40+
const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission
41+
42+
// Simulate a system that will activate heating/cooling in addition to a temperature sensor - add your preferred code here
43+
float getSimulatedTemperature(bool isHeating, bool isCooling) {
44+
// read sensor temperature and apply heating/cooling
45+
float simulatedTempHWSensor = SimulatedThermostat.getLocalTemperature();
46+
47+
if (isHeating) {
48+
// it will increase to simulate a heating system
49+
simulatedTempHWSensor = simulatedTempHWSensor + 0.5;
50+
}
51+
if (isCooling) {
52+
// it will decrease to simulate a colling system
53+
simulatedTempHWSensor = simulatedTempHWSensor - 0.5;
54+
}
55+
// otherwise, it will keep the temperature stable
56+
return simulatedTempHWSensor;
57+
}
58+
59+
void setup() {
60+
// Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node
61+
pinMode(buttonPin, INPUT_PULLUP);
62+
63+
Serial.begin(115200);
64+
65+
// Manually connect to WiFi
66+
WiFi.begin(ssid, password);
67+
// Wait for connection
68+
while (WiFi.status() != WL_CONNECTED) {
69+
delay(500);
70+
Serial.print(".");
71+
}
72+
Serial.println();
73+
74+
// Simulated Thermostat in COOLING and HEATING mode with Auto Mode to keep the temperature between setpoints
75+
// Auto Mode can only be used when the control sequence of operation is Cooling & Heating
76+
SimulatedThermostat.begin(MatterThermostat::THERMOSTAT_SEQ_OP_COOLING_HEATING, MatterThermostat::THERMOSTAT_AUTO_MODE_ENABLED);
77+
78+
// Matter beginning - Last step, after all EndPoints are initialized
79+
Matter.begin();
80+
81+
// Check Matter Accessory Commissioning state, which may change during execution of loop()
82+
if (!Matter.isDeviceCommissioned()) {
83+
Serial.println("");
84+
Serial.println("Matter Node is not commissioned yet.");
85+
Serial.println("Initiate the device discovery in your Matter environment.");
86+
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
87+
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
88+
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
89+
// waits for Matter Thermostat Commissioning.
90+
uint32_t timeCount = 0;
91+
while (!Matter.isDeviceCommissioned()) {
92+
delay(100);
93+
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
94+
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
95+
}
96+
}
97+
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
98+
99+
// after commissioning, set initial thermostat parameters
100+
// start the thermostat in AUTO mode
101+
SimulatedThermostat.setMode(MatterThermostat::THERMOSTAT_MODE_AUTO);
102+
// cooling setpoint must be lower than heating setpoint by at least 2.5C (deadband), in auto mode
103+
SimulatedThermostat.setCoolingHeatingSetpoints(20.0, 23.00); // the target cooler and heating setpoint
104+
// set the local temperature sensor in Celsius
105+
SimulatedThermostat.setLocalTemperature(12.50);
106+
107+
Serial.println();
108+
Serial.printf(
109+
"Initial Setpoints are %.01fC to %.01fC with a minimum 2.5C difference\r\n", SimulatedThermostat.getHeatingSetpoint(),
110+
SimulatedThermostat.getCoolingSetpoint()
111+
);
112+
Serial.printf("Auto mode is ON. Initial Temperature of %.01fC \r\n", SimulatedThermostat.getLocalTemperature());
113+
Serial.println("Local Temperature Sensor will be simulated every 10 seconds and changed by a simulated heater and cooler to move in between setpoints.");
114+
}
115+
}
116+
117+
// This will simulate the thermostat control system (heating and cooling)
118+
// User can set a local temperature using the Serial input (type a number and press Enter)
119+
// New temperature can be an positive or negative temperature in Celsius, between -50C and 50C
120+
// Initial local temperature is 10C as defined in getSimulatedTemperature() function
121+
void readSerialForNewTemperature() {
122+
static String newTemperatureStr;
123+
124+
while (Serial.available()) {
125+
char c = Serial.read();
126+
if (c == '\n' || c == '\r') {
127+
if (newTemperatureStr.length() > 0) {
128+
// convert the string to a float value
129+
float newTemperature = newTemperatureStr.toFloat();
130+
// check if the new temperature is valid
131+
if (newTemperature >= -50.0 && newTemperature <= 50.0) {
132+
// set the new temperature
133+
SimulatedThermostat.setLocalTemperature(newTemperature);
134+
Serial.printf("New Temperature is %.01fC\r\n", newTemperature);
135+
} else {
136+
Serial.println("Invalid Temperature value. Please type a number between -50 and 50");
137+
}
138+
newTemperatureStr = "";
139+
}
140+
} else {
141+
if (c == '+' || c == '-' || (c >= '0' && c <= '9') || c == '.') {
142+
newTemperatureStr += c;
143+
} else {
144+
Serial.println("Invalid character. Please type a number between -50 and 50");
145+
newTemperatureStr = "";
146+
}
147+
}
148+
}
149+
}
150+
151+
// loop will simulate the thermostat control system
152+
// User can set a local temperature using the Serial input (type a number and press Enter)
153+
// User can change the thermostat mode using the Matter APP (smartphone)
154+
// The loop will simulate a heating and cooling system and the associated local temperature change
155+
void loop() {
156+
static uint32_t timeCounter = 0;
157+
158+
// Simulate the heating and cooling systems
159+
static bool isHeating = false;
160+
static bool isCooling = false;
161+
162+
// check if a new temperature is typed in the Serial Monitor
163+
readSerialForNewTemperature();
164+
165+
// simulate thermostat with heating/cooling system and the associated local temperature change, every 10s
166+
if (!(timeCounter++ % 20)) { // delaying for 500ms x 20 = 10s
167+
float localTemperature = getSimulatedTemperature(isHeating, isCooling);
168+
// Print the current thermostat local temperature value
169+
Serial.printf("Current Local Temperature is %.01fC\r\n", localTemperature);
170+
SimulatedThermostat.setLocalTemperature(localTemperature); // publish the new temperature value
171+
172+
// Simulate the thermostat control system - User has 4 modes: OFF, HEAT, COOL, AUTO
173+
switch (SimulatedThermostat.getMode()) {
174+
case MatterThermostat::THERMOSTAT_MODE_OFF:
175+
// turn off the heating and cooling systems
176+
isHeating = false;
177+
isCooling = false;
178+
break;
179+
case MatterThermostat::THERMOSTAT_MODE_AUTO:
180+
// User APP has set the thermostat to AUTO mode -- keeping the tempeature between both setpoints
181+
// check if the heating system should be turned on or off
182+
if (localTemperature < SimulatedThermostat.getHeatingSetpoint() + SimulatedThermostat.getDeadBand()) {
183+
// turn on the heating system and turn off the cooling system
184+
isHeating = true;
185+
isCooling = false;
186+
}
187+
if (localTemperature > SimulatedThermostat.getCoolingSetpoint() - SimulatedThermostat.getDeadBand()) {
188+
// turn off the heating system and turn on the cooling system
189+
isHeating = false;
190+
isCooling = true;
191+
}
192+
break;
193+
case MatterThermostat::THERMOSTAT_MODE_HEAT:
194+
// Simulate the heating system - User has turned the heating system ON
195+
isHeating = true;
196+
isCooling = false; // keep the cooling system off as it is in heating mode
197+
// when the heating system is in HEATING mode, it will be turned off as soon as the local temperature is above the setpoint
198+
if (localTemperature > SimulatedThermostat.getHeatingSetpoint()) {
199+
// turn off the heating system
200+
isHeating = false;
201+
}
202+
break;
203+
case MatterThermostat::THERMOSTAT_MODE_COOL:
204+
// Simulate the cooling system - User has turned the cooling system ON
205+
if (SimulatedThermostat.getMode() == MatterThermostat::THERMOSTAT_MODE_COOL) {
206+
isCooling = true;
207+
isHeating = false; // keep the heating system off as it is in cooling mode
208+
// when the cooling system is in COOLING mode, it will be turned off as soon as the local temperature is bellow the setpoint
209+
if (localTemperature < SimulatedThermostat.getCoolingSetpoint()) {
210+
// turn off the cooling system
211+
isCooling = false;
212+
}
213+
}
214+
break;
215+
default: log_e("Invalid Thermostat Mode %d", SimulatedThermostat.getMode());
216+
}
217+
// Reporting Heating and Cooling status
218+
Serial.printf(
219+
"\tThermostat Mode: %s >>> Heater is %s -- Cooler is %s\r\n", MatterThermostat::getThermostatModeString(SimulatedThermostat.getMode()),
220+
isHeating ? "ON" : "OFF", isCooling ? "ON" : "OFF"
221+
);
222+
}
223+
// Check if the button has been pressed
224+
if (digitalRead(buttonPin) == LOW && !button_state) {
225+
// deals with button debouncing
226+
button_time_stamp = millis(); // record the time while the button is pressed.
227+
button_state = true; // pressed.
228+
}
229+
230+
if (digitalRead(buttonPin) == HIGH && button_state) {
231+
button_state = false; // released
232+
}
233+
234+
// Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node
235+
uint32_t time_diff = millis() - button_time_stamp;
236+
if (button_state && time_diff > decommissioningTimeout) {
237+
Serial.println("Decommissioning the Light Matter Accessory. It shall be commissioned again.");
238+
Matter.decommission();
239+
button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so
240+
}
241+
242+
delay(500);
243+
}
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

+42
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ MatterContactSensor KEYWORD1
2424
MatterPressureSensor KEYWORD1
2525
MatterOccupancySensor KEYWORD1
2626
MatterOnOffPlugin KEYWORD1
27+
MatterThermostat KEYWORD1
28+
ControlSequenceOfOperation_t KEYWORD1
29+
ThermostatMode_t KEYWORD1
30+
EndPointCB KEYWORD1
31+
EndPointHeatingSetpointCB KEYWORD1
32+
EndPointCoolingSetpointCB KEYWORD1
33+
EndPointTemperatureCB KEYWORD1
34+
EndPointModeCB KEYWORD1
35+
EndPointSpeedCB KEYWORD1
36+
EndPointOnOffCB KEYWORD1
37+
EndPointBrightnessCB KEYWORD1
38+
EndPointRGBColorCB KEYWORD1
2739

2840
#######################################
2941
# Methods and Functions (KEYWORD2)
@@ -78,6 +90,24 @@ setPressure KEYWORD2
7890
getPressure KEYWORD2
7991
setOccupancy KEYWORD2
8092
getOccupancy KEYWORD2
93+
getControlSequence KEYWORD2
94+
getMinHeatSetpoint KEYWORD2
95+
getMaxHeatSetpoint KEYWORD2
96+
getMinCoolSetpoint KEYWORD2
97+
getMaxCoolSetpoint KEYWORD2
98+
getDeadBand KEYWORD2
99+
setCoolingSetpoint KEYWORD2
100+
getCoolingSetpoint KEYWORD2
101+
setHeatingSetpoint KEYWORD2
102+
getHeatingSetpoint KEYWORD2
103+
setCoolingHeatingSetpoints KEYWORD2
104+
setLocalTemperature KEYWORD2
105+
getLocalTemperature KEYWORD2
106+
getThermostatModeString KEYWORD2
107+
onChangeMode KEYWORD2
108+
onChangeLocalTemperature KEYWORD2
109+
onChangeCoolingSetpoint KEYWORD2
110+
onChangeHeatingSetpoint KEYWORD2
81111

82112
#######################################
83113
# Constants (LITERAL1)
@@ -104,3 +134,15 @@ FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO LITERAL1
104134
FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO LITERAL1
105135
FAN_MODE_SEQ_OFF_HIGH_AUTO LITERAL1
106136
FAN_MODE_SEQ_OFF_HIGH LITERAL1
137+
THERMOSTAT_SEQ_OP_COOLING LITERAL1
138+
THERMOSTAT_SEQ_OP_COOLING_REHEAT LITERAL1
139+
THERMOSTAT_SEQ_OP_HEATING LITERAL1
140+
THERMOSTAT_SEQ_OP_HEATING_REHEAT LITERAL1
141+
THERMOSTAT_SEQ_OP_COOLING_HEATING LITERAL1
142+
THERMOSTAT_SEQ_OP_COOLING_HEATING_REHEAT LITERAL1
143+
THERMOSTAT_MODE_OFF LITERAL1
144+
THERMOSTAT_MODE_AUTO LITERAL1
145+
THERMOSTAT_MODE_COOL LITERAL1
146+
THERMOSTAT_MODE_HEAT LITERAL1
147+
THERMOSTAT_AUTO_MODE_DISABLED LITERAL1
148+
THERMOSTAT_AUTO_MODE_ENABLED LITERAL1

libraries/Matter/src/Matter.h

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <MatterEndpoints/MatterPressureSensor.h>
3333
#include <MatterEndpoints/MatterOccupancySensor.h>
3434
#include <MatterEndpoints/MatterOnOffPlugin.h>
35+
#include <MatterEndpoints/MatterThermostat.h>
3536

3637
using namespace esp_matter;
3738

@@ -70,6 +71,7 @@ class ArduinoMatter {
7071
friend class MatterPressureSensor;
7172
friend class MatterOccupancySensor;
7273
friend class MatterOnOffPlugin;
74+
friend class MatterThermostat;
7375

7476
protected:
7577
static void _init();

0 commit comments

Comments
 (0)