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

feat(matter) adds Identification callback to all matter endpoints #10734

Merged
merged 11 commits into from
Dec 16, 2024
164 changes: 164 additions & 0 deletions libraries/Matter/examples/MatterOnIdentify/MatterOnIdentify.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
* This example is the smallest code that will create a Matter Device which can be
* commissioned and controlled from a Matter Environment APP.
* It controls a GPIO that could be attached to a LED for visualization.
* Additionally the ESP32 will send debug messages indicating the Matter activity.
* Turning DEBUG Level ON may be useful to following Matter Accessory and Controller messages.
*
* This example is a simple Matter On/Off Light that can be controlled by a Matter Controller.
* It demonstrates how to use On Identify callback when the Identify Cluster is called.
* The Matter user APP can be used to request the device to identify itself by blinking the LED.
*/

// Matter Manager
#include <Matter.h>
#include <WiFi.h>

// List of Matter Endpoints for this Node
// Single On/Off Light Endpoint - at least one per node
MatterOnOffLight OnOffLight;

// WiFi is manually set and started
const char *ssid = "your-ssid"; // Change this to your WiFi SSID
const char *password = "your-password"; // Change this to your WiFi password

// Light GPIO that can be controlled by Matter APP
#ifdef LED_BUILTIN
const uint8_t ledPin = LED_BUILTIN;
#else
const uint8_t ledPin = 2; // Set your pin here if your board has not defined LED_BUILTIN
#endif

// set your board USER BUTTON pin here - decommissioning button
const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button.

// Button control - decommision the Matter Node
uint32_t button_time_stamp = 0; // debouncing control
bool button_state = false; // false = released | true = pressed
const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission

// Identify Flag and blink time - Blink the LED
const uint8_t identifyLedPin = ledPin; // uses the same LED as the Light - change if needed
volatile bool identifyFlag = false; // Flag to start the Blink when in Identify state
bool identifyBlink = false; // Blink state when in Identify state

// Matter Protocol Endpoint (On/OFF Light) Callback
bool onOffLightCallback(bool state) {
digitalWrite(ledPin, state ? HIGH : LOW);
// This callback must return the success state to Matter core
return true;
}

// Identification shall be done by Blink in Red or just the GPIO when no LED_BUILTIN is not defined
bool onIdentifyLightCallback(bool identifyIsActive) {
Serial.printf("Identify Cluster is %s\r\n", identifyIsActive ? "Active" : "Inactive");
if (identifyIsActive) {
// Start Blinking the light in loop()
identifyFlag = true;
identifyBlink = !OnOffLight; // Start with the inverted light state
} else {
// Stop Blinking and restore the light to the its last state
identifyFlag = false;
// force returning to the original state by toggling the light twice
OnOffLight.toggle();
OnOffLight.toggle();
}
return true;
}

void setup() {
// Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node
pinMode(buttonPin, INPUT_PULLUP);
// Initialize the LED GPIO
pinMode(ledPin, OUTPUT);

Serial.begin(115200);

// Manually connect to WiFi
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();

// Initialize at least one Matter EndPoint
OnOffLight.begin();

// On Identify Callback - Blink the LED
OnOffLight.onIdentify(onIdentifyLightCallback);

// Associate a callback to the Matter Controller
OnOffLight.onChange(onOffLightCallback);

// Matter beginning - Last step, after all EndPoints are initialized
Matter.begin();

// Check Matter Accessory Commissioning state, which may change during execution of loop()
if (!Matter.isDeviceCommissioned()) {
Serial.println("");
Serial.println("Matter Node is not commissioned yet.");
Serial.println("Initiate the device discovery in your Matter environment.");
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str());
Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str());
// waits for Matter Occupancy Sensor Commissioning.
uint32_t timeCount = 0;
while (!Matter.isDeviceCommissioned()) {
delay(100);
if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec
Serial.println("Matter Node not commissioned yet. Waiting for commissioning.");
}
}
Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use.");
}
}

void loop() {
// check if the Light is in identify state and blink it every 500ms (delay loop time)
if (identifyFlag) {
#ifdef LED_BUILTIN
uint8_t brightness = 32 * identifyBlink;
rgbLedWrite(identifyLedPin, brightness, 0, 0);
#else
digitalWrite(identifyLedPin, identifyBlink ? HIGH : LOW);
#endif
identifyBlink = !identifyBlink;
}

// Check if the button has been pressed
if (digitalRead(buttonPin) == LOW && !button_state) {
// deals with button debouncing
button_time_stamp = millis(); // record the time while the button is pressed.
button_state = true; // pressed.
}

if (digitalRead(buttonPin) == HIGH && button_state) {
button_state = false; // released
}

// Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node
uint32_t time_diff = millis() - button_time_stamp;
if (button_state && time_diff > decommissioningTimeout) {
Serial.println("Decommissioning the Light Matter Accessory. It shall be commissioned again.");
Matter.decommission();
button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so
}

delay(500); // works as a debounce for the button and also for the LED blink
}
7 changes: 7 additions & 0 deletions libraries/Matter/examples/MatterOnIdentify/ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"fqbn_append": "PartitionScheme=huge_app",
"requires": [
"CONFIG_SOC_WIFI_SUPPORTED=y",
"CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y"
]
}
28 changes: 21 additions & 7 deletions libraries/Matter/src/Matter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using namespace esp_matter;
using namespace esp_matter::attribute;
using namespace esp_matter::endpoint;
using namespace esp_matter::identification;
using namespace chip::app::Clusters;

constexpr auto k_timeout_seconds = 300;
Expand All @@ -29,11 +30,6 @@ static bool _matter_has_started = false;
static node::config_t node_config;
static node_t *deviceNode = NULL;

typedef void *app_driver_handle_t;
esp_err_t matter_light_attribute_update(
app_driver_handle_t driver_handle, uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val
);

// This callback is called for every attribute update. The callback implementation shall
// handle the desired attributes and return an appropriate error code. If the attribute
// is not of your interest, please do not return an error code and strictly return ESP_OK.
Expand Down Expand Up @@ -67,8 +63,26 @@ static esp_err_t app_attribute_update_cb(
// This callback is invoked when clients interact with the Identify Cluster.
// In the callback implementation, an endpoint can identify itself. (e.g., by flashing an LED or light).
static esp_err_t app_identification_cb(identification::callback_type_t type, uint16_t endpoint_id, uint8_t effect_id, uint8_t effect_variant, void *priv_data) {
log_i("Identification callback: type: %u, effect: %u, variant: %u", type, effect_id, effect_variant);
return ESP_OK;
log_d("Identification callback to endpoint %d: type: %u, effect: %u, variant: %u", endpoint_id, type, effect_id, effect_variant);
esp_err_t err = ESP_OK;
MatterEndPoint *ep = (MatterEndPoint *)priv_data; // endpoint pointer to base class
// Identify the endpoint sending a counter to the application
bool identifyIsActive = false;

if (type == identification::callback_type_t::START) {
log_v("Identification callback: START");
identifyIsActive = true;
} else if (type == identification::callback_type_t::EFFECT) {
log_v("Identification callback: EFFECT");
} else if (type == identification::callback_type_t::STOP) {
identifyIsActive = false;
log_v("Identification callback: STOP");
}
if (ep != NULL) {
err = ep->endpointIdentifyCB(endpoint_id, identifyIsActive) ? ESP_OK : ESP_FAIL;
}

return err;
}

// This callback is invoked for all Matter events. The application can handle the events as required.
Expand Down
14 changes: 14 additions & 0 deletions libraries/Matter/src/MatterEndPoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,21 @@ class MatterEndPoint {
// this function is called by Matter internal event processor. It could be overwritten by the application, if necessary.
virtual bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) = 0;

// This callback is invoked when clients interact with the Identify Cluster of an specific endpoint.
bool endpointIdentifyCB(uint16_t endpoint_id, bool identifyIsEnabled) {
if (_onEndPointIdentifyCB) {
return _onEndPointIdentifyCB(identifyIsEnabled);
}
return true;
}
// User callaback for the Identify Cluster functionality
using EndPointIdentifyCB = std::function<bool(bool)>;
void onIdentify(EndPointIdentifyCB onEndPointIdentifyCB) {
_onEndPointIdentifyCB = onEndPointIdentifyCB;
}

protected:
uint16_t endpoint_id = 0;
EndPointIdentifyCB _onEndPointIdentifyCB = NULL;
};
#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */
7 changes: 6 additions & 1 deletion libraries/Matter/src/MatterEndpoints/MatterColorLight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,13 @@ MatterColorLight::~MatterColorLight() {

bool MatterColorLight::begin(bool initialState, espHsvColor_t _colorHSV) {
ArduinoMatter::_init();
rgb_color_light::config_t light_config;

if (getEndPointId() != 0) {
log_e("Matter RGB Color Light with Endpoint Id %d device has already been created.", getEndPointId());
return false;
}

rgb_color_light::config_t light_config;
light_config.on_off.on_off = initialState;
light_config.on_off.lighting.start_up_on_off = nullptr;
onOffState = initialState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ using namespace chip::app::Clusters;
bool MatterColorTemperatureLight::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) {
bool ret = true;
if (!started) {
log_e("Matter CW_WW Light device has not begun.");
log_e("Matter Temperature Light device has not begun.");
return false;
}

log_d("CW_WW Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32);
log_d("Temperature Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32);

if (endpoint_id == getEndPointId()) {
switch (cluster_id) {
case OnOff::Id:
if (attribute_id == OnOff::Attributes::OnOff::Id) {
log_d("CW_WW Light On/Off State changed to %d", val->val.b);
log_d("Temperature Light On/Off State changed to %d", val->val.b);
if (_onChangeOnOffCB != NULL) {
ret &= _onChangeOnOffCB(val->val.b);
}
Expand All @@ -50,7 +50,7 @@ bool MatterColorTemperatureLight::attributeChangeCB(uint16_t endpoint_id, uint32
break;
case LevelControl::Id:
if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) {
log_d("CW_WW Light Brightness changed to %d", val->val.u8);
log_d("Temperature Light Brightness changed to %d", val->val.u8);
if (_onChangeBrightnessCB != NULL) {
ret &= _onChangeBrightnessCB(val->val.u8);
}
Expand All @@ -64,7 +64,7 @@ bool MatterColorTemperatureLight::attributeChangeCB(uint16_t endpoint_id, uint32
break;
case ColorControl::Id:
if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) {
log_d("CW_WW Light Temperature changed to %d", val->val.u16);
log_d("Temperature Light Temperature changed to %d", val->val.u16);
if (_onChangeTemperatureCB != NULL) {
ret &= _onChangeTemperatureCB(val->val.u16);
}
Expand All @@ -89,8 +89,13 @@ MatterColorTemperatureLight::~MatterColorTemperatureLight() {

bool MatterColorTemperatureLight::begin(bool initialState, uint8_t brightness, uint16_t ColorTemperature) {
ArduinoMatter::_init();
color_temperature_light::config_t light_config;

if (getEndPointId() != 0) {
log_e("Matter Temperature Light with Endpoint Id %d device has already been created.", getEndPointId());
return false;
}

color_temperature_light::config_t light_config;
light_config.on_off.on_off = initialState;
light_config.on_off.lighting.start_up_on_off = nullptr;
onOffState = initialState;
Expand All @@ -108,12 +113,12 @@ bool MatterColorTemperatureLight::begin(bool initialState, uint8_t brightness, u
// endpoint handles can be used to add/modify clusters.
endpoint_t *endpoint = color_temperature_light::create(node::get(), &light_config, ENDPOINT_FLAG_NONE, (void *)this);
if (endpoint == nullptr) {
log_e("Failed to create CW_WW light endpoint");
log_e("Failed to create Temperature Light endpoint");
return false;
}

setEndPointId(endpoint::get_id(endpoint));
log_i("CW_WW Light created with endpoint_id %d", getEndPointId());
log_i("Temperature Light created with endpoint_id %d", getEndPointId());

/* Mark deferred persistence for some attributes that might be changed rapidly */
cluster_t *level_control_cluster = cluster::get(endpoint, LevelControl::Id);
Expand All @@ -134,7 +139,7 @@ void MatterColorTemperatureLight::end() {

bool MatterColorTemperatureLight::setOnOff(bool newState) {
if (!started) {
log_e("Matter CW_WW Light device has not begun.");
log_e("Matter Temperature Light device has not begun.");
return false;
}

Expand Down Expand Up @@ -175,7 +180,7 @@ bool MatterColorTemperatureLight::toggle() {

bool MatterColorTemperatureLight::setBrightness(uint8_t newBrightness) {
if (!started) {
log_w("Matter CW_WW Light device has not begun.");
log_w("Matter Temperature Light device has not begun.");
return false;
}

Expand Down Expand Up @@ -206,7 +211,7 @@ uint8_t MatterColorTemperatureLight::getBrightness() {

bool MatterColorTemperatureLight::setColorTemperature(uint16_t newTemperature) {
if (!started) {
log_w("Matter CW_WW Light device has not begun.");
log_w("Matter Temperature Light device has not begun.");
return false;
}

Expand Down
5 changes: 5 additions & 0 deletions libraries/Matter/src/MatterEndpoints/MatterContactSensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ MatterContactSensor::~MatterContactSensor() {
bool MatterContactSensor::begin(bool _contactState) {
ArduinoMatter::_init();

if (getEndPointId() != 0) {
log_e("Matter Contact Sensor with Endpoint Id %d device has already been created.", getEndPointId());
return false;
}

contact_sensor::config_t contact_sensor_config;
contact_sensor_config.boolean_state.state_value = _contactState;

Expand Down
6 changes: 5 additions & 1 deletion libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ MatterDimmableLight::~MatterDimmableLight() {

bool MatterDimmableLight::begin(bool initialState, uint8_t brightness) {
ArduinoMatter::_init();
dimmable_light::config_t light_config;
if (getEndPointId() != 0) {
log_e("Matter Dimmable Light with Endpoint Id %d device has already been created.", getEndPointId());
return false;
}

dimmable_light::config_t light_config;
light_config.on_off.on_off = initialState;
light_config.on_off.lighting.start_up_on_off = nullptr;
onOffState = initialState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,13 @@ MatterEnhancedColorLight::~MatterEnhancedColorLight() {

bool MatterEnhancedColorLight::begin(bool initialState, espHsvColor_t _colorHSV, uint8_t brightness, uint16_t ColorTemperature) {
ArduinoMatter::_init();
enhanced_color_light::config_t light_config;

if (getEndPointId() != 0) {
log_e("Matter Enhanced ColorLight with Endpoint Id %d device has already been created.", getEndPointId());
return false;
}

enhanced_color_light::config_t light_config;
light_config.on_off.on_off = initialState;
light_config.on_off.lighting.start_up_on_off = nullptr;
onOffState = initialState;
Expand Down
Loading
Loading