diff --git a/CMakeLists.txt b/CMakeLists.txt index b6478aa16a3..322824f11ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterHumiditySensor.cpp libraries/Matter/src/MatterEndpoints/MatterContactSensor.cpp libraries/Matter/src/MatterEndpoints/MatterPressureSensor.cpp + libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp libraries/Matter/src/Matter.cpp) set(ARDUINO_LIBRARY_PPP_SRCS diff --git a/libraries/Matter/examples/MatterOccupancySensor/MatterOccupancySensor.ino b/libraries/Matter/examples/MatterOccupancySensor/MatterOccupancySensor.ino new file mode 100644 index 00000000000..7582a423dc0 --- /dev/null +++ b/libraries/Matter/examples/MatterOccupancySensor/MatterOccupancySensor.ino @@ -0,0 +1,128 @@ +// 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 an example code that will create a Matter Device which can be + * commissioned and controlled from a Matter Environment APP. + * 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. + * + * The example will create a Matter Occupancy Sensor Device. + * The Occupancy Sensor will be simulated to change its state every 2 minutes. + * + * The onboard button can be kept pressed for 5 seconds to decommission the Matter Node. + * The example will also show the manual commissioning code and QR code to be used in the Matter environment. + * + */ + +// Matter Manager +#include +#include + +// List of Matter Endpoints for this Node +// Matter Occupancy Sensor Endpoint +MatterOccupancySensor OccupancySensor; + +// set your board USER BUTTON pin here - decommissioning only +const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. + +// 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 + +// Button control +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 + +void setup() { + // Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node + pinMode(buttonPin, INPUT_PULLUP); + + 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(); + + // set initial occupancy sensor state as false and connected to a PIR sensor type (default) + OccupancySensor.begin(); + + // 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."); + } +} + +bool simulatedHWOccupancySensor() { + // Simulated Occupancy Sensor + static bool occupancyState = false; + static uint32_t lastTime = millis(); + const uint32_t occupancyTimeout = 120000; // 2 minutes to toggle the state + + // Simulate a Occupancy Sensor state change every 2 minutes + if (millis() - lastTime > occupancyTimeout) { + occupancyState = !occupancyState; + lastTime = millis(); + } + return occupancyState; +} + +void loop() { + // 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 (button_state && digitalRead(buttonPin) == HIGH) { + 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 Generic Switch Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } + + // Check Simulated Occupancy Sensor and set Matter Attribute + OccupancySensor.setOccupancy(simulatedHWOccupancySensor()); + + delay(50); +} diff --git a/libraries/Matter/examples/MatterOccupancySensor/ci.json b/libraries/Matter/examples/MatterOccupancySensor/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterOccupancySensor/ci.json @@ -0,0 +1,7 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_WIFI_SUPPORTED=y", + "CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y" + ] +} diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index d75e9888afd..1daf65974ed 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -22,6 +22,7 @@ MatterTemperatureSensor KEYWORD1 MatterHumiditySensor KEYWORD1 MatterContactSensor KEYWORD1 MatterPressureSensor KEYWORD1 +MatterOccupancySensor KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -74,6 +75,8 @@ setContact KEYWORD2 getContact KEYWORD2 setPressure KEYWORD2 getPressure KEYWORD2 +setOccupancy KEYWORD2 +getOccupancy KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index 02571dbcf40..bc0a0ec1cc7 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -30,6 +30,7 @@ #include #include #include +#include using namespace esp_matter; @@ -66,6 +67,7 @@ class ArduinoMatter { friend class MatterHumiditySensor; friend class MatterContactSensor; friend class MatterPressureSensor; + friend class MatterOccupancySensor; protected: static void _init(); diff --git a/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp b/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp new file mode 100644 index 00000000000..d893f14b6bb --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp @@ -0,0 +1,109 @@ +// 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. + +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace chip::app::Clusters; + +// clang-format off +const uint8_t MatterOccupancySensor::occupancySensorTypeBitmap[4] = { + MatterOccupancySensor::occupancySensorTypePir, + MatterOccupancySensor::occupancySensorTypePir | MatterOccupancySensor::occupancySensorTypeUltrasonic, + MatterOccupancySensor::occupancySensorTypeUltrasonic, + MatterOccupancySensor::occupancySensorTypePhysicalContact +}; +// clang-format on + +bool MatterOccupancySensor::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 Occupancy Sensor device has not begun."); + return false; + } + + log_d("Occupancy Sensor Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + return ret; +} + +MatterOccupancySensor::MatterOccupancySensor() {} + +MatterOccupancySensor::~MatterOccupancySensor() { + end(); +} + +bool MatterOccupancySensor::begin(bool _occupancyState, OccupancySensorType_t _occupancySensorType) { + ArduinoMatter::_init(); + + occupancy_sensor::config_t occupancy_sensor_config; + occupancy_sensor_config.occupancy_sensing.occupancy = _occupancyState; + occupancy_sensor_config.occupancy_sensing.occupancy_sensor_type = _occupancySensorType; + occupancy_sensor_config.occupancy_sensing.occupancy_sensor_type_bitmap = occupancySensorTypeBitmap[_occupancySensorType]; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = occupancy_sensor::create(node::get(), &occupancy_sensor_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Occupancy Sensor endpoint"); + return false; + } + occupancyState = _occupancyState; + setEndPointId(endpoint::get_id(endpoint)); + log_i("Occupancy Sensor created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterOccupancySensor::end() { + started = false; +} + +bool MatterOccupancySensor::setOccupancy(bool _occupancyState) { + if (!started) { + log_e("Matter Occupancy Sensor device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (occupancyState == _occupancyState) { + return true; + } + + esp_matter_attr_val_t occupancyVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(OccupancySensing::Id, OccupancySensing::Attributes::Occupancy::Id, &occupancyVal)) { + log_e("Failed to get Occupancy Sensor Attribute."); + return false; + } + if (occupancyVal.val.u8 != _occupancyState) { + occupancyVal.val.u8 = _occupancyState; + bool ret; + ret = updateAttributeVal(OccupancySensing::Id, OccupancySensing::Attributes::Occupancy::Id, &occupancyVal); + if (!ret) { + log_e("Failed to update Occupancy Sensor Attribute."); + return false; + } + occupancyState = _occupancyState; + } + log_v("Occupancy Sensor set to %s", _occupancyState ? "Occupied" : "Vacant"); + + return true; +} + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.h b/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.h new file mode 100644 index 00000000000..30f312a9841 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.h @@ -0,0 +1,73 @@ +// 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. + +#pragma once +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include + +using namespace chip::app::Clusters::OccupancySensing; + +class MatterOccupancySensor : public MatterEndPoint { +public: + // Different Occupancy Sensor Types + enum OccupancySensorType_t { + OCCUPANCY_SENSOR_TYPE_PIR = (uint8_t)OccupancySensorTypeEnum::kPir, + OCCUPANCY_SENSOR_TYPE_ULTRASONIC = (uint8_t)OccupancySensorTypeEnum::kUltrasonic, + OCCUPANCY_SENSOR_TYPE_PIR_AND_ULTRASONIC = (uint8_t)OccupancySensorTypeEnum::kPIRAndUltrasonic, + OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT = (uint8_t)OccupancySensorTypeEnum::kPhysicalContact + }; + + MatterOccupancySensor(); + ~MatterOccupancySensor(); + // begin Matter Occupancy Sensor endpoint with initial occupancy state and default PIR sensor type + bool begin(bool _occupancyState = false, OccupancySensorType_t _occupancySensorType = OCCUPANCY_SENSOR_TYPE_PIR); + // this will just stop processing Occupancy Sensor Matter events + void end(); + + // set the occupancy state + bool setOccupancy(bool _occupancyState); + // returns the occupancy state + bool getOccupancy() { + return occupancyState; + } + + // bool conversion operator + void operator=(bool _occupancyState) { + setOccupancy(_occupancyState); + } + // bool conversion operator + operator bool() { + return getOccupancy(); + } + + // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. + bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val); + +protected: + // bitmap for Occupancy Sensor Types + static const uint8_t occupancySensorTypePir = 0x01; + static const uint8_t occupancySensorTypeUltrasonic = 0x02; + static const uint8_t occupancySensorTypePhysicalContact = 0x04; + + // bitmap for Occupancy Sensor Type Bitmap mapped array + static const uint8_t occupancySensorTypeBitmap[4]; + + bool started = false; + bool occupancyState = false; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */