diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0aff5b6f07b..6a949631bd9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,6 +26,7 @@ repos: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] - id: pretty-format-json + stages: [manual] args: [--autofix] types_or: [json] exclude: | diff --git a/CMakeLists.txt b/CMakeLists.txt index a06f815c9d8..ed3d4d7e44a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,7 @@ set(ARDUINO_ALL_LIBRARIES HTTPUpdate Insights LittleFS + Matter NetBIOS Network OpenThread @@ -164,6 +165,10 @@ set(ARDUINO_LIBRARY_OpenThread_SRCS libraries/OpenThread/src/OThreadCLI.cpp libraries/OpenThread/src/OThreadCLI_Util.cpp) +set(ARDUINO_LIBRARY_Matter_SRCS + libraries/Matter/src/MatterOnOffLight.cpp + libraries/Matter/src/Matter.cpp) + set(ARDUINO_LIBRARY_PPP_SRCS libraries/PPP/src/PPP.cpp libraries/PPP/src/ppp.c) diff --git a/cores/esp32/esp32-hal-tinyusb.c b/cores/esp32/esp32-hal-tinyusb.c index c69fca08fc7..16cd5d831d2 100644 --- a/cores/esp32/esp32-hal-tinyusb.c +++ b/cores/esp32/esp32-hal-tinyusb.c @@ -287,15 +287,14 @@ enum { VENDOR_REQUEST_MICROSOFT = 2 }; -static uint8_t const tinyusb_bos_descriptor[] = { - // total length, number of device caps - TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), +static uint8_t const tinyusb_bos_descriptor[] = {// total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), - // Vendor Code, iLandingPage - TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), - // Microsoft OS 2.0 descriptor - TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) }; /* @@ -831,7 +830,7 @@ esp_err_t tinyusb_init(tinyusb_device_config_t *config) { periph_ll_enable_clk_clear_rst(PERIPH_USB_MODULE); } #endif - + tinyusb_config_t tusb_cfg = { .external_phy = false // In the most cases you need to use a `false` value }; diff --git a/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino b/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino new file mode 100644 index 00000000000..a9afb0c4484 --- /dev/null +++ b/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino @@ -0,0 +1,65 @@ +// Matter Manager +#include +#include + +// List of Matter Endpoints for this Node +// On/Off Light Endpoint +#include +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 + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // enable IPv6 + WiFi.enableIPv6(true); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); + + // Initialize at least one Matter EndPoint + OnOffLight.begin(); + + // Matter beginning - Last step, after all EndPoints are initialized + Matter.begin(); +} + +void loop() { + // Check Matter Commissioning state + 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 Light Commissioning. + while (!Matter.isDeviceCommissioned()) { + delay(5000); + Serial.println("Matter Fabric not commissioned yet. Waiting for commissioning."); + } + } + Serial.println("Matter Node is commissioned and connected to Wi-Fi."); + Serial.println("====> Decommissioning in 30 seconds. <===="); + delay(30000); + Matter.decommission(); + Serial.println("Matter Node is decommissioned. Commsssioning widget shall start over."); +} diff --git a/libraries/Matter/examples/MatterCommissionTest/ci.json b/libraries/Matter/examples/MatterCommissionTest/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterCommissionTest/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/examples/MatterComposedLights/MatterComposedLights.ino b/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino new file mode 100644 index 00000000000..63f154d4492 --- /dev/null +++ b/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino @@ -0,0 +1,94 @@ +// Matter Manager +#include +#include + +// List of Matter Endpoints for this Node +// There will be 3 On/Off Light Endpoints in the same Node +#include +MatterOnOffLight OnOffLight1; +MatterOnOffLight OnOffLight2; +MatterOnOffLight OnOffLight3; + +// Matter Protocol Endpoint Callback for each Light Accessory +bool setLightOnOff1(bool state) { + Serial.printf("CB-Light1 changed state to: %s\r\n", state ? "ON" : "OFF"); + return true; +} + +bool setLightOnOff2(bool state) { + Serial.printf("CB-Light2 changed state to: %s\r\n", state ? "ON" : "OFF"); + return true; +} + +bool setLightOnOff3(bool state) { + Serial.printf("CB-Light3 changed state to: %s\r\n", state ? "ON" : "OFF"); + return true; +} + +// 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 + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // enable IPv6 + WiFi.enableIPv6(true); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); + + // Initialize all 3 Matter EndPoints + OnOffLight1.begin(); + OnOffLight2.begin(); + OnOffLight3.begin(); + OnOffLight1.onChangeOnOff(setLightOnOff1); + OnOffLight2.onChangeOnOff(setLightOnOff2); + OnOffLight3.onChangeOnOff(setLightOnOff3); + + // Matter beginning - Last step, after all EndPoints are initialized + Matter.begin(); +} + +void loop() { + // Check Matter Light Commissioning state + 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 Light 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."); + } + + //displays the Light state every 3 seconds + Serial.println("======================"); + Serial.printf("Matter Light #1 is %s\r\n", OnOffLight1.getOnOff() ? "ON" : "OFF"); + Serial.printf("Matter Light #2 is %s\r\n", OnOffLight2.getOnOff() ? "ON" : "OFF"); + Serial.printf("Matter Light #3 is %s\r\n", OnOffLight3.getOnOff() ? "ON" : "OFF"); + delay(3000); +} diff --git a/libraries/Matter/examples/MatterComposedLights/ci.json b/libraries/Matter/examples/MatterComposedLights/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterComposedLights/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/examples/MatterOnOffLight/MatterOnOffLight.ino b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino new file mode 100644 index 00000000000..64981b23a66 --- /dev/null +++ b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino @@ -0,0 +1,138 @@ +// Matter Manager +#include +#include +#include + +// List of Matter Endpoints for this Node +// On/Off Light Endpoint +#include +MatterOnOffLight OnOffLight; + +// it will keep last OnOff state stored, using Preferences +Preferences lastStatePref; + +// set your board LED pin here +#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 +#warning "Do not forget to set the LED pin" +#endif + +// set your board USER BUTTON pin here +const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. + +// Matter Protocol Endpoint Callback +bool setLightOnOff(bool state) { + Serial.printf("User Callback :: New Light State = %s\r\n", state ? "ON" : "OFF"); + if (state) { + digitalWrite(ledPin, HIGH); + } else { + digitalWrite(ledPin, LOW); + } + // store last OnOff state for when the Light is restarted / power goes off + lastStatePref.putBool("lastOnOffState", state); + // This callback must return the success state to Matter core + return true; +} + +// WiFi is manually set and started + +const char *ssid = "Apartment B15"; // Change this to your WiFi SSID +const char *password = "flat-pony-body"; // Change this to your WiFi password + +void setup() { + // Initialize the USER BUTTON (Boot button) GPIO that will act as a toggle switch + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the LED (light) GPIO and Matter End Point + pinMode(ledPin, OUTPUT); + + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // enable IPv6 + WiFi.enableIPv6(true); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); + + // Initialize Matter EndPoint + lastStatePref.begin("matterLight", false); + bool lastOnOffState = lastStatePref.getBool("lastOnOffState", true); + OnOffLight.begin(lastOnOffState); + OnOffLight.onChangeOnOff(setLightOnOff); + + // Matter beginning - Last step, after all EndPoints are initialized + Matter.begin(); + // This may be a restart of a already commissioned Matter accessory + if (Matter.isDeviceCommissioned()) { + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + Serial.printf("Initial state: %s\r\n", OnOffLight.getOnOff() ? "ON" : "OFF"); + setLightOnOff(OnOffLight.getOnOff()); // configure the Light based on initial state + } +} +// Button control +uint32_t button_time_stamp = 0; // debouncing control +bool button_state = false; // false = released | true = pressed +const uint32_t debouceTime = 250; // button debouncing time (ms) +const uint32_t decommissioningTimeout = 10000; // keep the button pressed for 10s to decommission the light + +void loop() { + // Check Matter Light 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 Light 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.printf("Initial state: %s\r\n", OnOffLight.getOnOff() ? "ON" : "OFF"); + setLightOnOff(OnOffLight.getOnOff()); // configure the Light based on initial state + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + } + + // A button is also used to control the light + // 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. + } + + // Onboard User Button is used as a Light toggle switch or to decommission it + uint32_t time_diff = millis() - button_time_stamp; + if (button_state && time_diff > debouceTime && digitalRead(buttonPin) == HIGH) { + button_state = false; // released + // Toggle button is released - toggle the light + Serial.println("User button released. Toggling Light!"); + OnOffLight.toggle(); // Matter Controller also can see the change + + // Factory reset is triggered if the button is pressed longer than 10 seconds + if (time_diff > decommissioningTimeout) { + Serial.println("Decommissioning the Light Matter Accessory. It shall be commissioned again."); + OnOffLight.setOnOff(false); // turn the light off + Matter.decommission(); + } + } +} diff --git a/libraries/Matter/examples/MatterOnOffLight/ci.json b/libraries/Matter/examples/MatterOnOffLight/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterOnOffLight/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 new file mode 100644 index 00000000000..7ff3e90f3b4 --- /dev/null +++ b/libraries/Matter/keywords.txt @@ -0,0 +1,35 @@ +####################################### +# Syntax Coloring Map For OpenThread +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Matter KEYWORD1 +MatterOnOffLight KEYWORD1 +MatterEndPoint KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +end KEYWORD2 +start KEYWORD2 +getManualPairingCode KEYWORD2 +getOnboardingQRCodeUrl KEYWORD2 +isDeviceCommissioned KEYWORD2 +isWiFiConnected KEYWORD2 +isThreadConnected KEYWORD2 +isDeviceConnected KEYWORD2 +decommission KEYWORD2 +attributeChangeCB KEYWORD2 +setOnOff KEYWORD2 +getOnOff KEYWORD2 +toggle KEYWORD2 +onChangeOnOff KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/Matter/library.properties b/libraries/Matter/library.properties new file mode 100644 index 00000000000..e08c750fb59 --- /dev/null +++ b/libraries/Matter/library.properties @@ -0,0 +1,9 @@ +name=Matter +version=3.1.0 +author=Rodrigo Garcia | GitHub @SuGlider +maintainer=Rodrigo Garcia +sentence=Library for supporting Matter environment on ESP32. +paragraph=This library implements Matter accessories using WiFi network. +category=Communication +url=https://github.com/espressif/arduino-esp32/ +architectures=esp32 diff --git a/libraries/Matter/src/Matter.cpp b/libraries/Matter/src/Matter.cpp new file mode 100644 index 00000000000..49504babac0 --- /dev/null +++ b/libraries/Matter/src/Matter.cpp @@ -0,0 +1,163 @@ +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include "MatterEndPoint.h" + +using namespace esp_matter; +using namespace esp_matter::attribute; +using namespace esp_matter::endpoint; +using namespace chip::app::Clusters; + +constexpr auto k_timeout_seconds = 300; + +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. +static esp_err_t app_attribute_update_cb( + attribute::callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data +) { + esp_err_t err = ESP_OK; + MatterEndPoint *ep = (MatterEndPoint *)priv_data; // endpoint pointer to base class + switch (type) { + case PRE_UPDATE: // Callback before updating the value in the database + log_i("Attribute update callback: PRE_UPDATE"); + if (ep != NULL) { + err = ep->attributeChangeCB(endpoint_id, cluster_id, attribute_id, val) ? ESP_OK : ESP_FAIL; + } + break; + case POST_UPDATE: // Callback after updating the value in the database + log_i("Attribute update callback: POST_UPDATE"); + break; + case READ: // Callback for reading the attribute value. This is used when the `ATTRIBUTE_FLAG_OVERRIDE` is set. + log_i("Attribute update callback: READ"); + break; + case WRITE: // Callback for writing the attribute value. This is used when the `ATTRIBUTE_FLAG_OVERRIDE` is set. + log_i("Attribute update callback: WRITE"); + break; + default: log_i("Attribute update callback: Unknown type %d", type); + } + return err; +} + +// 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; +} + +// This callback is invoked for all Matter events. The application can handle the events as required. +static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg) { + switch (event->Type) { + case chip::DeviceLayer::DeviceEventType::kInterfaceIpAddressChanged: + log_i( + "Interface %s Address changed", event->InterfaceIpAddressChanged.Type == chip::DeviceLayer::InterfaceIpChangeType::kIpV4_Assigned ? "IPv4" : "IPV6" + ); + break; + case chip::DeviceLayer::DeviceEventType::kCommissioningComplete: log_i("Commissioning complete"); break; + case chip::DeviceLayer::DeviceEventType::kFailSafeTimerExpired: log_i("Commissioning failed, fail safe timer expired"); break; + case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStarted: log_i("Commissioning session started"); break; + case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStopped: log_i("Commissioning session stopped"); break; + case chip::DeviceLayer::DeviceEventType::kCommissioningWindowOpened: log_i("Commissioning window opened"); break; + case chip::DeviceLayer::DeviceEventType::kCommissioningWindowClosed: log_i("Commissioning window closed"); break; + case chip::DeviceLayer::DeviceEventType::kFabricRemoved: + { + log_i("Fabric removed successfully"); + if (chip::Server::GetInstance().GetFabricTable().FabricCount() == 0) { + log_i("No fabric left, opening commissioning window"); + chip::CommissioningWindowManager &commissionMgr = chip::Server::GetInstance().GetCommissioningWindowManager(); + constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds16(k_timeout_seconds); + if (!commissionMgr.IsCommissioningWindowOpen()) { + // After removing last fabric, it does not remove the Wi-Fi credentials and still has IP connectivity so, only advertising on DNS-SD. + CHIP_ERROR err = commissionMgr.OpenBasicCommissioningWindow(kTimeoutSeconds, chip::CommissioningWindowAdvertisement::kDnssdOnly); + if (err != CHIP_NO_ERROR) { + log_e("Failed to open commissioning window, err:%" CHIP_ERROR_FORMAT, err.Format()); + } + } + } + break; + } + case chip::DeviceLayer::DeviceEventType::kFabricWillBeRemoved: log_i("Fabric will be removed"); break; + case chip::DeviceLayer::DeviceEventType::kFabricUpdated: log_i("Fabric is updated"); break; + case chip::DeviceLayer::DeviceEventType::kFabricCommitted: log_i("Fabric is committed"); break; + case chip::DeviceLayer::DeviceEventType::kBLEDeinitialized: log_i("BLE deinitialized and memory reclaimed"); break; + default: break; + } +} + +void ArduinoMatter::_init() { + if (_matter_has_started) { + return; + } + + // Create a Matter node and add the mandatory Root Node device type on endpoint 0 + // node handle can be used to add/modify other endpoints. + deviceNode = node::create(&node_config, app_attribute_update_cb, app_identification_cb); + if (deviceNode == nullptr) { + log_e("Failed to create Matter node"); + return; + } + + _matter_has_started = true; +} + +void ArduinoMatter::begin() { + if (!_matter_has_started) { + log_w("No Matter endpoint has been created. Please create an endpoint first."); + return; + } + + /* Matter start */ + esp_err_t err = esp_matter::start(app_event_cb); + if (err != ESP_OK) { + log_e("Failed to start Matter, err:%d", err); + _matter_has_started = false; + } +} + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +bool ArduinoMatter::isThreadConnected() { + return false; // Thread Network TBD +} +#endif + +bool ArduinoMatter::isDeviceCommissioned() { + return chip::Server::GetInstance().GetFabricTable().FabricCount() > 0; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION +bool ArduinoMatter::isWiFiConnected() { + return chip::DeviceLayer::ConnectivityMgr().IsWiFiStationConnected(); +} +#endif + +bool ArduinoMatter::isDeviceConnected() { + bool retCode = false; +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + retCode |= ArduinoMatter::isThreadConnected(); +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION + retCode |= ArduinoMatter::isWiFiConnected(); +#endif + return retCode; +} + +void ArduinoMatter::decommission() { + esp_matter::factory_reset(); +} + +// Global Matter Object +ArduinoMatter Matter; + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h new file mode 100644 index 00000000000..a1ce0f2f644 --- /dev/null +++ b/libraries/Matter/src/Matter.h @@ -0,0 +1,40 @@ +#pragma once +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include + +using namespace esp_matter; + +class ArduinoMatter { +public: + static inline String getManualPairingCode() { + // return the pairing code for manual pairing + return String("34970112332"); + } + static inline String getOnboardingQRCodeUrl() { + // return the URL for the QR code for onboarding + return String("https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT:Y.K9042C00KA0648G00"); + } + static void begin(); + static bool isDeviceCommissioned(); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION + static bool isWiFiConnected(); +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + static bool isThreadConnected(); +#endif + static bool isDeviceConnected(); + static void decommission(); + + // list of Matter EndPoints Friend Classes + friend class MatterOnOffLight; + +protected: + static void _init(); +}; + +extern ArduinoMatter Matter; + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndPoint.h b/libraries/Matter/src/MatterEndPoint.h new file mode 100644 index 00000000000..2be5bf5bb5d --- /dev/null +++ b/libraries/Matter/src/MatterEndPoint.h @@ -0,0 +1,23 @@ +#pragma once +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include + +// Matter Endpoint Base Class. Controls the endpoint ID and allows the child class to overwrite attribute change call +class MatterEndPoint { +public: + uint16_t getEndPointId() { + return endpoint_id; + } + void setEndPointId(uint16_t ep) { + endpoint_id = ep; + } + + virtual bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) = 0; + +protected: + uint16_t endpoint_id = 0; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterOnOffLight.cpp b/libraries/Matter/src/MatterOnOffLight.cpp new file mode 100644 index 00000000000..7e8926ffdef --- /dev/null +++ b/libraries/Matter/src/MatterOnOffLight.cpp @@ -0,0 +1,107 @@ +#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; + +bool MatterOnOffLight::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_w("Matter On-Off Light device has not begun."); + return false; + } + + if (endpoint_id == getEndPointId()) { + if (cluster_id == OnOff::Id) { + if (attribute_id == OnOff::Attributes::OnOff::Id) { + if (_onChangeCB != NULL) { + ret = _onChangeCB(val->val.b); + log_d("OnOffLight state changed to %d", val->val.b); + if (ret == true) { + state = val->val.b; + } + } + } + } + } + return ret; +} + +MatterOnOffLight::MatterOnOffLight() {} + +MatterOnOffLight::~MatterOnOffLight() { + end(); +} + +bool MatterOnOffLight::begin(bool initialState) { + ArduinoMatter::_init(); + on_off_light::config_t light_config; + light_config.on_off.on_off = initialState; + state = initialState; + light_config.on_off.lighting.start_up_on_off = nullptr; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = on_off_light::create(node::get(), &light_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create on-off light endpoint"); + return false; + } + + setEndPointId(endpoint::get_id(endpoint)); + log_i("On-Off Light created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterOnOffLight::end() { + started = false; +} + +bool MatterOnOffLight::setOnOff(bool newState) { + if (!started) { + log_w("Matter On-Off Light device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (state == newState) { + return true; + } + + state = newState; + + endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); + cluster_t *cluster = cluster::get(endpoint, OnOff::Id); + attribute_t *attribute = attribute::get(cluster, OnOff::Attributes::OnOff::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.b != state) { + val.val.b = state; + attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val); + } + return true; +} + +bool MatterOnOffLight::getOnOff() { + return state; +} + +bool MatterOnOffLight::toggle() { + return setOnOff(!state); +} + +MatterOnOffLight::operator bool() { + return getOnOff(); +} + +void MatterOnOffLight::operator=(bool newState) { + setOnOff(newState); +} +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterOnOffLight.h b/libraries/Matter/src/MatterOnOffLight.h new file mode 100644 index 00000000000..39220652e21 --- /dev/null +++ b/libraries/Matter/src/MatterOnOffLight.h @@ -0,0 +1,34 @@ +#pragma once +#include +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include + +class MatterOnOffLight : public MatterEndPoint { +public: + MatterOnOffLight(); + ~MatterOnOffLight(); + virtual bool begin(bool initialState = false); // default initial state is off + void end(); // this will just stop processing Light Matter events + + bool setOnOff(bool newState); // returns true if successful + bool getOnOff(); // returns current light state + bool toggle(); // returns true if successful + + operator bool(); // returns current light state + void operator=(bool state); // turns light on or off + // 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); + // User Callback for whenever the Light state is changed by the Matter Controller + using EndPointCB = std::function; + void onChangeOnOff(EndPointCB onChangeCB) { + _onChangeCB = onChangeCB; + } + +protected: + bool started = false; + bool state = false; // default initial state is off, but it can be changed by begin(bool) + EndPointCB _onChangeCB = NULL; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/USB/src/USBHID.cpp b/libraries/USB/src/USBHID.cpp index 4bc555b8e30..1d5d86fb3a3 100644 --- a/libraries/USB/src/USBHID.cpp +++ b/libraries/USB/src/USBHID.cpp @@ -206,7 +206,9 @@ extern "C" uint16_t tusb_hid_load_descriptor(uint8_t *dst, uint8_t *itf) { uint8_t descriptor[TUD_HID_INOUT_DESC_LEN] = { // HID Input & Output descriptor // Interface number, string index, protocol, report descriptor len, EP OUT & IN address, size & polling interval - TUD_HID_INOUT_DESCRIPTOR(*itf, str_index, tinyusb_interface_protocol, tinyusb_hid_device_descriptor_len, ep_out, (uint8_t)(0x80 | ep_in), CFG_TUD_ENDOINT_SIZE, 1) + TUD_HID_INOUT_DESCRIPTOR( + *itf, str_index, tinyusb_interface_protocol, tinyusb_hid_device_descriptor_len, ep_out, (uint8_t)(0x80 | ep_in), CFG_TUD_ENDOINT_SIZE, 1 + ) }; *itf += 1; memcpy(dst, descriptor, TUD_HID_INOUT_DESC_LEN);