diff --git a/examples/bridge-app/esp32/CMakeLists.txt b/examples/bridge-app/esp32/CMakeLists.txt new file mode 100644 index 00000000000000..579436405eb92c --- /dev/null +++ b/examples/bridge-app/esp32/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/config/esp32/components" + "${CMAKE_CURRENT_LIST_DIR}/../../common/m5stack-tft/repo/components/tft" + "${CMAKE_CURRENT_LIST_DIR}/../../common/m5stack-tft/repo/components/spidriver" + "${CMAKE_CURRENT_LIST_DIR}/../../common/QRCode" + "${CMAKE_CURRENT_LIST_DIR}/../../common/screen-framework" +) + +project(chip-bridge-app) +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DLWIP_IPV6_SCOPES=0;-DCHIP_HAVE_CONFIG_H;-DDYNAMIC_ENDPOINT_COUNT=16" APPEND) +idf_build_set_property(C_COMPILE_OPTIONS "-Os;-DLWIP_IPV6_SCOPES=0" APPEND) diff --git a/examples/bridge-app/esp32/main/CHIPDeviceManager.cpp b/examples/bridge-app/esp32/main/CHIPDeviceManager.cpp new file mode 100644 index 00000000000000..c88cdc44246c5e --- /dev/null +++ b/examples/bridge-app/esp32/main/CHIPDeviceManager.cpp @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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. + */ + +/** + * @file + * This file implements the CHIP Device Interface that is used by + * applications to interact with the CHIP stack + * + */ + +#include + +#include "CHIPDeviceManager.h" +#include +#include +#include +#include +#include + +using namespace ::chip; + +namespace chip { + +namespace DeviceManager { + +using namespace ::chip::DeviceLayer; + +void CHIPDeviceManager::CommonDeviceEventHandler(const ChipDeviceEvent * event, intptr_t arg) +{ + CHIPDeviceManagerCallbacks * cb = reinterpret_cast(arg); + if (cb != nullptr) + { + cb->DeviceEventCallback(event, reinterpret_cast(cb)); + } +} + +CHIP_ERROR CHIPDeviceManager::Init(CHIPDeviceManagerCallbacks * cb) +{ + CHIP_ERROR err; + mCB = cb; + RendezvousInformationFlags flags = RendezvousInformationFlags(CONFIG_RENDEZVOUS_MODE); + + // Initialize the CHIP stack. + err = PlatformMgr().InitChipStack(); + SuccessOrExit(err); + + if (flags.Has(RendezvousInformationFlag::kBLE)) + { + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + } + else if (flags.Has(RendezvousInformationFlag::kSoftAP)) + { + // TODO(cecille): Fix for the case where BLE and SoftAP are both enabled.` + ConnectivityMgr().SetBLEAdvertisingEnabled(false); + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + else + { + // If rendezvous is bypassed, enable SoftAP so that the device can still + // be communicated with via its SoftAP as needed. + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + + err = Platform::MemoryInit(); + SuccessOrExit(err); + + // Register a function to receive events from the CHIP device layer. Note that calls to + // this function will happen on the CHIP event loop thread, not the app_main thread. + PlatformMgr().AddEventHandler(CHIPDeviceManager::CommonDeviceEventHandler, reinterpret_cast(cb)); + + // Start a task to run the CHIP Device event loop. + err = PlatformMgr().StartEventLoopTask(); + SuccessOrExit(err); + +exit: + return err; +} +} // namespace DeviceManager +} // namespace chip + +void emberAfPostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t mask, + uint16_t manufacturerCode, uint8_t type, uint16_t size, uint8_t * value) +{ + chip::DeviceManager::CHIPDeviceManagerCallbacks * cb = + chip::DeviceManager::CHIPDeviceManager::GetInstance().GetCHIPDeviceManagerCallbacks(); + if (cb != nullptr) + { + cb->PostAttributeChangeCallback(endpointId, clusterId, attributeId, mask, manufacturerCode, type, size, value); + } +} diff --git a/examples/bridge-app/esp32/main/CMakeLists.txt b/examples/bridge-app/esp32/main/CMakeLists.txt new file mode 100644 index 00000000000000..3206a8d5b1abdf --- /dev/null +++ b/examples/bridge-app/esp32/main/CMakeLists.txt @@ -0,0 +1,24 @@ +idf_component_register(PRIV_INCLUDE_DIRS + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/bridge-app/bridge-common" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/platform/esp32" + "${CMAKE_CURRENT_LIST_DIR}/include" + SRC_DIRS + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/bridge-app/bridge-common/gen" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/reporting" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/basic" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/level-control" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/diagnostic-logs-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/descriptor" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/network-commissioning" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/on-off-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/operational-credentials-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/general-commissioning-server" + PRIV_REQUIRES chip QRCode tft spidriver bt screen-framework) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) +target_compile_options(${COMPONENT_LIB} PRIVATE "-DLWIP_IPV6_SCOPES=0" "-DCHIP_HAVE_CONFIG_H") + diff --git a/examples/bridge-app/esp32/main/Device.cpp b/examples/bridge-app/esp32/main/Device.cpp new file mode 100644 index 00000000000000..c6b486e0c82377 --- /dev/null +++ b/examples/bridge-app/esp32/main/Device.cpp @@ -0,0 +1,102 @@ +#include "Device.h" + +#include +#include + +Device::Device(const char * szDeviceName, const char * szLocation) +{ + strncpy(mName, szDeviceName, sizeof(mName)); + strncpy(mLocation, szLocation, sizeof(mLocation)); + mState = kState_Off; + mReachable = false; + mEndpointId = 0; + mChanged_CB = nullptr; +} + +bool Device::IsOn() +{ + return mState == kState_On; +} + +bool Device::IsReachable() +{ + return mReachable; +} + +void Device::SetOnOff(bool aOn) +{ + bool changed; + + if (aOn) + { + changed = (mState != kState_On); + mState = kState_On; + ChipLogProgress(DeviceLayer, "Device[%s]: ON", mName); + } + else + { + changed = (mState != kState_Off); + mState = kState_Off; + ChipLogProgress(DeviceLayer, "Device[%s]: OFF", mName); + } + + if ((changed) && (mChanged_CB)) + { + mChanged_CB(this, kChanged_State); + } +} + +void Device::SetReachable(bool aReachable) +{ + bool changed = (mReachable != aReachable); + + mReachable = aReachable; + + if (aReachable) + { + ChipLogProgress(DeviceLayer, "Device[%s]: ONLINE", mName); + } + else + { + ChipLogProgress(DeviceLayer, "Device[%s]: OFFLINE", mName); + } + + if ((changed) && (mChanged_CB)) + { + mChanged_CB(this, kChanged_Reachable); + } +} + +void Device::SetName(const char * szName) +{ + bool changed = (strncmp(mName, szName, sizeof(mName)) != 0); + + ChipLogProgress(DeviceLayer, "Device[%s]: New Name=\"%s\"", mName, szName); + + strncpy(mName, szName, sizeof(mName)); + + if ((changed) && (mChanged_CB)) + { + mChanged_CB(this, kChanged_Name); + } +} + +void Device::SetLocation(const char * szLocation) +{ + bool changed = (strncmp(mLocation, szLocation, sizeof(mLocation)) != 0); + + strncpy(mLocation, szLocation, sizeof(mLocation)); + + ChipLogProgress(DeviceLayer, "Device[%s]: Location=\"%s\"", mName, mLocation); + + if ((changed) && (mChanged_CB)) + { + mChanged_CB(this, kChanged_Location); + } +} + +void Device::SetChangeCallback(DeviceCallback_fn aChanged_CB) +{ + mChanged_CB = aChanged_CB; +} + diff --git a/examples/bridge-app/esp32/main/DeviceCallbacks.cpp b/examples/bridge-app/esp32/main/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..43a03eb9d651e7 --- /dev/null +++ b/examples/bridge-app/esp32/main/DeviceCallbacks.cpp @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ + +#include "DeviceCallbacks.h" +#include "freertos/FreeRTOS.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include +#include +#include +#include + +static const char * TAG = "lock-devicecallbacks"; + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; + +void DeviceCallbacks::DeviceEventCallback(const ChipDeviceEvent * event, intptr_t arg) +{ + switch (event->Type) + { + case DeviceEventType::kInternetConnectivityChange: + OnInternetConnectivityChange(event); + break; + + case DeviceEventType::kSessionEstablished: + OnSessionEstablished(event); + break; + + case DeviceEventType::kInterfaceIpAddressChanged: + if ((event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV4_Assigned) || + (event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV6_Assigned)) + { + // MDNS server restart on any ip assignment: if link local ipv6 is configured, that + // will not trigger a 'internet connectivity change' as there is no internet + // connectivity. MDNS still wants to refresh its listening interfaces to include the + // newly selected address. + chip::app::Mdns::StartServer(); + } + break; + } + + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} +void DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t mask, + uint16_t manufacturerCode, uint8_t type, uint16_t size, uint8_t * value) +{ + ESP_LOGI(TAG, "PostAttributeChangeCallback - Cluster ID: '0x%04x', EndPoint ID: '0x%02x', Attribute ID: '0x%04x'", clusterId, + endpointId, attributeId); + + switch (clusterId) + { + case ZCL_ON_OFF_CLUSTER_ID: + OnOnOffPostAttributeChangeCallback(endpointId, attributeId, value); + break; + + default: + ESP_LOGI(TAG, "Unhandled cluster ID: %d", clusterId); + break; + } + + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} + +void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event) +{ + if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established) + { + ESP_LOGI(TAG, "Server ready at: %s:%d", event->InternetConnectivityChange.address, CHIP_PORT); + chip::app::Mdns::StartServer(); + } + else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv4 connectivity..."); + } + if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) + { + ESP_LOGI(TAG, "IPv6 Server ready..."); + chip::app::Mdns::StartServer(); + } + else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv6 connectivity..."); + } +} + +void DeviceCallbacks::OnSessionEstablished(const ChipDeviceEvent * event) +{ + if (event->SessionEstablished.IsCommissioner) + { + ESP_LOGI(TAG, "Commissioner detected!"); + } +} + +void DeviceCallbacks::OnOnOffPostAttributeChangeCallback(EndpointId endpointId, AttributeId attributeId, uint8_t * value) +{ +/* VerifyOrExit(attributeId == ZCL_ON_OFF_ATTRIBUTE_ID, ESP_LOGI(TAG, "Unhandled Attribute ID: '0x%04x", attributeId)); + VerifyOrExit(endpointId == 1 || endpointId == 2, ESP_LOGE(TAG, "Unexpected EndPoint ID: `0x%02x'", endpointId)); + if (*value) + { + BoltLockMgr().InitiateAction(AppEvent::kEventType_Lock, BoltLockManager::LOCK_ACTION); + } + else + { + BoltLockMgr().InitiateAction(AppEvent::kEventType_Lock, BoltLockManager::UNLOCK_ACTION); + } +exit:*/ + return; +} diff --git a/examples/bridge-app/esp32/main/Kconfig.projbuild b/examples/bridge-app/esp32/main/Kconfig.projbuild new file mode 100644 index 00000000000000..a911a4391c1406 --- /dev/null +++ b/examples/bridge-app/esp32/main/Kconfig.projbuild @@ -0,0 +1,44 @@ +menu "Demo" + + choice + prompt "Rendezvous Mode" + default RENDEZVOUS_MODE_WIFI + help + Specifies the Rendezvous mode of the peripheral. + + config RENDEZVOUS_MODE_BYPASS + bool "Bypass" + config RENDEZVOUS_MODE_WIFI + bool "Wi-Fi" + config RENDEZVOUS_MODE_BLE + bool "BLE" + config RENDEZVOUS_MODE_THREAD + bool "Thread" + config RENDEZVOUS_MODE_ETHERNET + bool "Ethernet" + endchoice + + config USE_ECHO_CLIENT + bool "Enable the built-in Echo Client" + default "n" + help + This enables a local FreeRTOS Echo Client so that the end-to-end echo server can be + tested easily + + config ECHO_HOST_IP + string "IPV4 address" + default "127.0.0.1" + depends on USE_ECHO_CLIENT + help + The IPV4 Address of the ECHO Server. + + config RENDEZVOUS_MODE + int + range 0 8 + default 0 if RENDEZVOUS_MODE_BYPASS + default 1 if RENDEZVOUS_MODE_WIFI + default 2 if RENDEZVOUS_MODE_BLE + default 4 if RENDEZVOUS_MODE_THREAD + default 8 if RENDEZVOUS_MODE_ETHERNET + +endmenu diff --git a/examples/bridge-app/esp32/main/include/CHIPDeviceManager.h b/examples/bridge-app/esp32/main/include/CHIPDeviceManager.h new file mode 100644 index 00000000000000..5283b55d27a3d4 --- /dev/null +++ b/examples/bridge-app/esp32/main/include/CHIPDeviceManager.h @@ -0,0 +1,120 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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. + */ + +/** + * @file + * This file contains definitions for the CHIP DeviceManager Interface + * + * This object will co-ordinate multiple activities such as + * initialisation, rendezvous, session mgmt and other such + * activities within the CHIP stack. This is a singleton object. + */ + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace chip { +namespace DeviceManager { + +/** + * @brief + * This class provides a skeleton for all the callback functions. The functions will be + * called by other objects within the CHIP stack for specific events. + * Applications interested in receiving specific callbacks can specialize this class and handle + * these events in their implementation of this class. + */ +class DLL_EXPORT CHIPDeviceManagerCallbacks +{ +public: + /** + * @brief + * Called when CHIP Device events (PublicEventTypes) are triggered. + * + * @param event ChipDeviceEvent that occurred + * @param arg arguments specific to the event, if any + */ + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + /** + * @brief + * Called after an attribute has been changed + * + * @param endpoint endpoint id + * @param clusterID cluster id + * @param attributeId attribute id that was changed + * @param mask mask of the attribute + * @param manufacturerCode manufacturer code + * @param type attribute type + * @param size size of the attribute + * @param value pointer to the new value + */ + virtual void PostAttributeChangeCallback(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint16_t manufacturerCode, uint8_t type, uint16_t size, uint8_t * value) + {} + virtual ~CHIPDeviceManagerCallbacks() {} +}; + +/** + * @brief + * A common class that drives other components of the CHIP stack + */ +class DLL_EXPORT CHIPDeviceManager +{ +public: + CHIPDeviceManager(const CHIPDeviceManager &) = delete; + CHIPDeviceManager(const CHIPDeviceManager &&) = delete; + CHIPDeviceManager & operator=(const CHIPDeviceManager &) = delete; + + static CHIPDeviceManager & GetInstance() + { + static CHIPDeviceManager instance; + return instance; + } + + /** + * @brief + * Initialise CHIPDeviceManager + * + * @param cb Application's instance of the CHIPDeviceManagerCallbacks for consuming events + */ + CHIP_ERROR Init(CHIPDeviceManagerCallbacks * cb); + /** + * @brief + * Fetch a pointer to the registered CHIPDeviceManagerCallbacks object. + * + */ + CHIPDeviceManagerCallbacks * GetCHIPDeviceManagerCallbacks() { return mCB; } + + /** + * Use internally for registration of the ChipDeviceEvents + */ + static void CommonDeviceEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + +private: + CHIPDeviceManagerCallbacks * mCB = nullptr; + CHIPDeviceManager() {} +}; + +} // namespace DeviceManager +} // namespace chip diff --git a/examples/bridge-app/esp32/main/include/Device.h b/examples/bridge-app/esp32/main/include/Device.h new file mode 100644 index 00000000000000..864092d58ca1ad --- /dev/null +++ b/examples/bridge-app/esp32/main/include/Device.h @@ -0,0 +1,52 @@ +//These are the bridged devices +#include +#include +#include +#include + +class Device +{ +public: + static const int kDeviceNameSize = 32; + static const int kDeviceLocationSize = 32; + + enum State_t + { + kState_On = 0, + kState_Off, + } State; + + enum Changed_t + { + kChanged_Reachable = 0x01, + kChanged_State = 0x02, + kChanged_Location = 0x04, + kChanged_Name = 0x08, + } Changed; + + Device(const char * szDeviceName, const char * szLocation); + + bool IsOn(); + bool IsReachable(); + void SetOnOff(bool aOn); + void SetReachable(bool aReachable); + void SetName(const char * szDeviceName); + void SetLocation(const char * szLocation); + inline void SetEndpointId(chip::EndpointId id) { mEndpointId = id; }; + inline chip::EndpointId GetEndpointId() { return mEndpointId; }; + inline char * GetName() { return mName; }; + inline char * GetLocation() { return mLocation; }; + + using DeviceCallback_fn = std::function; + void SetChangeCallback(DeviceCallback_fn aChanged_CB); + +private: + State_t mState; + bool mReachable; + char mName[kDeviceNameSize]; + char mLocation[kDeviceLocationSize]; + chip::EndpointId mEndpointId; + DeviceCallback_fn mChanged_CB; + +}; + diff --git a/examples/bridge-app/esp32/main/include/DeviceCallbacks.h b/examples/bridge-app/esp32/main/include/DeviceCallbacks.h new file mode 100644 index 00000000000000..1e77d9e9d3c576 --- /dev/null +++ b/examples/bridge-app/esp32/main/include/DeviceCallbacks.h @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.h + * + * Implementations for the DeviceManager callbacks for this application + * + **/ + +#include "CHIPDeviceManager.h" +#include +#include + +class DeviceCallbacks : public chip::DeviceManager::CHIPDeviceManagerCallbacks +{ +public: + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + virtual void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint16_t manufacturerCode, uint8_t type, uint16_t size, uint8_t * value); + +private: + void OnInternetConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnSessionEstablished(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); + void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); +}; diff --git a/examples/bridge-app/esp32/main/main.cpp b/examples/bridge-app/esp32/main/main.cpp new file mode 100644 index 00000000000000..88b439f7c0f80f --- /dev/null +++ b/examples/bridge-app/esp32/main/main.cpp @@ -0,0 +1,442 @@ +#include "DeviceCallbacks.h" +#include +#include +#include +#include +#include +#include +#include "nvs_flash.h" +#include +#include "esp_log.h" +#include "Device.h" +#include +#define DYNAMIC_ENDPOINT_COUNT 16 +const char * TAG = "bridge-app"; + +using namespace ::chip; +using namespace ::chip::DeviceManager; +using namespace ::chip::DeviceLayer; + +static DeviceCallbacks AppCallback; + +static const int kUserLabelSize = 32; +// Current ZCL implementation of Struct uses a max-size array of 254 bytes +static const int kDescriptorAttributeArraySize = 254; +static const int kFixedLabelAttributeArraySize = 254; +// Four attributes in descriptor cluster: DeviceTypeList, ServerList, ClientList, PartsList +static const int kDescriptorAttributeCount = 4; +static const int kFixedLabelElementsOctetStringSize = 16; + +static EndpointId gCurrentEndpointId; +static EndpointId gFirstDynamicEndpointId; +static Device * gDevices[DYNAMIC_ENDPOINT_COUNT]; // number of dynamic endpoints count + +// Descriptor attribute storage on dynamic endpoint +static uint8_t gDescriptorAttrStorage[DYNAMIC_ENDPOINT_COUNT][kDescriptorAttributeCount][kDescriptorAttributeArraySize]; + +// (taken from chip-devices.xml) +#define DEVICE_TYPE_CHIP_BRIDGE 0x0a0b +// (taken from lo-devices.xml) +#define DEVICE_TYPE_LO_ON_OFF_LIGHT 0x0100 +// Device Version for dynamic endpoints: +#define DEVICE_VERSION_DEFAULT 1 + +/* BRIDGED DEVICE ENDPOINT: contains the following clusters: + - On/Off + - Descriptor + - Bridged Device Basic + - Fixed Label +*/ + +// Declare On/Off cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* on/off */ +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(0x0001); + +// Declare Descriptor cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_DEVICE_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */ +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_SERVER_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */ +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CLIENT_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */ +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0) /* parts list */ +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(0x0001); + +// Declare Bridged Device Basic information cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_USER_LABEL_ATTRIBUTE_ID, CHAR_STRING, kUserLabelSize, 0), /* UserLabel */ +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* Reachable */ +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(0x0003); + +// Declare Fixed Label cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(fixedLabelAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0) /* label list */ +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(0x0001); + +// Declare Cluster List for Bridged Light endpoint +DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters) +DECLARE_DYNAMIC_CLUSTER(ZCL_ON_OFF_CLUSTER_ID, onOffAttrs), +DECLARE_DYNAMIC_CLUSTER(ZCL_DESCRIPTOR_CLUSTER_ID, descriptorAttrs), +DECLARE_DYNAMIC_CLUSTER(ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID, bridgedDeviceBasicAttrs), +DECLARE_DYNAMIC_CLUSTER(ZCL_FIXED_LABEL_CLUSTER_ID, fixedLabelAttrs) +DECLARE_DYNAMIC_CLUSTER_LIST_END; + +// Declare Bridged Light endpoint +DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters); + +int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, uint16_t deviceType) +{ + uint8_t index = 0; + while (index < DYNAMIC_ENDPOINT_COUNT) + { + if (NULL == gDevices[index]) + { + gDevices[index] = dev; + EmberAfStatus ret; + while (1) + { + ret = emberAfSetDynamicEndpoint(index, gCurrentEndpointId, ep, deviceType, DEVICE_VERSION_DEFAULT); + if (ret == EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(), + gCurrentEndpointId, index); + return index; + } + else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS) + { + return -1; + } + // Handle wrap condition + if (++gCurrentEndpointId < gFirstDynamicEndpointId) + { + gCurrentEndpointId = gFirstDynamicEndpointId; + } + } + } + index++; + } + ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!"); + return -1; +} + +int RemoveDeviceEndpoint(Device * dev) +{ + uint8_t index = 0; + while (index < DYNAMIC_ENDPOINT_COUNT) + { + if (gDevices[index] == dev) + { + EndpointId ep = emberAfClearDynamicEndpoint(index); + gDevices[index] = NULL; + ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index); + // Silence complaints about unused ep when progress logging + // disabled. + UNUSED_VAR(ep); + return index; + } + index++; + } + return -1; +} + +//ZCL format -> (len, string) +uint8_t * ToZclCharString(uint8_t * zclString, const char * cString, uint8_t maxLength) +{ + size_t len = strlen(cString); + if (len > maxLength) + { + len = maxLength; + } + zclString[0] = static_cast(len); + memcpy(&zclString[1], cString, zclString[0]); + return zclString; +} + +//Converted into bytes and mapped the (label, value) +void EncodeFixedLabel(const char * label, const char * value, uint8_t * buffer, uint16_t length, EmberAfAttributeMetadata * am) +{ + char zclOctetStrBuf[kFixedLabelElementsOctetStringSize]; + uint16_t listCount = 1; + _LabelStruct labelStruct; + + labelStruct.label = chip::ByteSpan(reinterpret_cast(label), kFixedLabelElementsOctetStringSize); + + strncpy(zclOctetStrBuf, value, sizeof(zclOctetStrBuf)); + labelStruct.value = chip::ByteSpan(reinterpret_cast(&zclOctetStrBuf[0]), sizeof(zclOctetStrBuf)); + + emberAfCopyList(ZCL_FIXED_LABEL_CLUSTER_ID, am, true, buffer, reinterpret_cast(&labelStruct), 1); + emberAfCopyList(ZCL_FIXED_LABEL_CLUSTER_ID, am, true, buffer, reinterpret_cast(&listCount), 0); +} + +EmberAfStatus HandleReadBridgedDeviceBasicAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer, + uint16_t maxReadLength) +{ + ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength); + + if ((attributeId == ZCL_REACHABLE_ATTRIBUTE_ID) && (maxReadLength == 1)) + { + *buffer = dev->IsReachable() ? 1 : 0; + } + else if ((attributeId == ZCL_USER_LABEL_ATTRIBUTE_ID) && (maxReadLength == 32)) + { + ToZclCharString(buffer, dev->GetName(), static_cast(maxReadLength - 1)); + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleReadDescriptorAttribute(uint16_t endpointIndex, chip::AttributeId attributeId, uint8_t * buffer, + uint16_t maxReadLength) +{ + if ((maxReadLength <= kDescriptorAttributeArraySize) && (attributeId < kDescriptorAttributeCount)) + { + memcpy(buffer, &gDescriptorAttrStorage[endpointIndex][attributeId][0], maxReadLength); + return EMBER_ZCL_STATUS_SUCCESS; + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +EmberAfStatus HandleReadFixedLabelAttribute(Device * dev, EmberAfAttributeMetadata * am, uint8_t * buffer, uint16_t maxReadLength) +{ + if ((am->attributeId == ZCL_LABEL_LIST_ATTRIBUTE_ID) && (maxReadLength <= kFixedLabelAttributeArraySize)) + { + EncodeFixedLabel("room", dev->GetLocation(), buffer, maxReadLength, am); + + return EMBER_ZCL_STATUS_SUCCESS; + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +EmberAfStatus HandleReadOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer, uint16_t maxReadLength) +{ + ChipLogProgress(DeviceLayer, "HandleReadOnOffAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength); + + if ((attributeId == ZCL_ON_OFF_ATTRIBUTE_ID) && (maxReadLength == 1)) + { + *buffer = dev->IsOn() ? 1 : 0; + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleWriteOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer) +{ + ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%d", attributeId); + + if ((attributeId == ZCL_ON_OFF_ATTRIBUTE_ID) && (dev->IsReachable())) + { + if (*buffer) + { + dev->SetOnOff(true); + } + else + { + dev->SetOnOff(false); + } + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleWriteDescriptorAttribute(uint16_t endpointIndex, EmberAfAttributeMetadata * am, uint8_t * buffer, + uint16_t length, int32_t index) +{ + chip::AttributeId attributeId = am->attributeId; + + if (emberAfCopyList(ZCL_DESCRIPTOR_CLUSTER_ID, am, true, &gDescriptorAttrStorage[endpointIndex][attributeId][0], buffer, + index) > 0) + { + return EMBER_ZCL_STATUS_SUCCESS; + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +EmberAfStatus emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, + EmberAfAttributeMetadata * attributeMetadata, uint16_t manufacturerCode, + uint8_t * buffer, uint16_t maxReadLength, int32_t index) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + EmberAfStatus ret = EMBER_ZCL_STATUS_FAILURE; + + if ((endpointIndex < DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != NULL)) + { + Device * dev = gDevices[endpointIndex]; + + if (clusterId == ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID) + { + ret = HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == ZCL_DESCRIPTOR_CLUSTER_ID) + { + ret = HandleReadDescriptorAttribute(endpointIndex, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == ZCL_FIXED_LABEL_CLUSTER_ID) + { + ret = HandleReadFixedLabelAttribute(dev, attributeMetadata, buffer, maxReadLength); + } + else if (clusterId == ZCL_ON_OFF_CLUSTER_ID) + { + ret = HandleReadOnOffAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + } + + return ret; +} + +EmberAfStatus emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, + EmberAfAttributeMetadata * attributeMetadata, uint16_t manufacturerCode, + uint8_t * buffer, int32_t index) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + EmberAfStatus ret = EMBER_ZCL_STATUS_FAILURE; + + // ChipLogProgress(DeviceLayer, "emberAfExternalAttributeWriteCallback: ep=%d", endpoint); + + if (endpointIndex < DYNAMIC_ENDPOINT_COUNT) + { + Device * dev = gDevices[endpointIndex]; + + if ((dev->IsReachable()) && (clusterId == ZCL_ON_OFF_CLUSTER_ID)) + { + ret = HandleWriteOnOffAttribute(dev, attributeMetadata->attributeId, buffer); + } + else if (clusterId == ZCL_DESCRIPTOR_CLUSTER_ID) + { + ret = HandleWriteDescriptorAttribute(endpointIndex, attributeMetadata, buffer, attributeMetadata->size, index); + } + } + + return ret; +} + +void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask) +{ + if (itemChangedMask & Device::kChanged_Reachable) + { + uint8_t reachable = dev->IsReachable() ? 1 : 0; + emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID, + ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, + &reachable); + } + + if (itemChangedMask & Device::kChanged_State) + { + uint8_t isOn = dev->IsOn() ? 1 : 0; + emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, + CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn); + } + + if (itemChangedMask & Device::kChanged_Name) + { + uint8_t zclName[kUserLabelSize]; + ToZclCharString(zclName, dev->GetName(), kUserLabelSize - 1); + emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID, + ZCL_USER_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_CHAR_STRING_ATTRIBUTE_TYPE, + zclName); + } + if (itemChangedMask & Device::kChanged_Location) + { + uint8_t buffer[kFixedLabelAttributeArraySize]; + EmberAfAttributeMetadata am = { .attributeId = ZCL_LABEL_LIST_ATTRIBUTE_ID, + .size = kFixedLabelAttributeArraySize, + .defaultValue = nullptr }; + + EncodeFixedLabel("room", dev->GetLocation(), buffer, sizeof(buffer), &am); + + emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID, ZCL_LABEL_LIST_ATTRIBUTE_ID, + CLUSTER_MASK_SERVER, 0, ZCL_ARRAY_ATTRIBUTE_TYPE, buffer); + } +} + +extern "C" void app_main() +{ + int err = 0; + // Initialize the ESP NVS layer. + err = nvs_flash_init(); + if (err != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "nvs_flash_init() failed: %s", ErrorStr(err)); + return; + } + + CHIP_ERROR chip_err = CHIP_NO_ERROR; + + //bridge will have own database named gDevices. + //Clear database + memset(gDevices, 0, sizeof(gDevices)); + + //Bridged devices 4 + Device Light1("Light 1", "Office"); + Device Light2("Light 2", "Office"); + Device Light3("Light 3", "Kitchen"); + Device Light4("Light 4", "Den"); + + //Whenever bridged device changes its state + Light1.SetChangeCallback(&HandleDeviceStatusChanged); + Light2.SetChangeCallback(&HandleDeviceStatusChanged); + Light3.SetChangeCallback(&HandleDeviceStatusChanged); + Light4.SetChangeCallback(&HandleDeviceStatusChanged); + + Light1.SetReachable(true); + Light2.SetReachable(true); + Light3.SetReachable(true); + Light4.SetReachable(true); + + //Till above the bridged device was created, if there is a change in any attribute then with respective to its endpoint + //the change is made. + + /*Initialize CHIP*/ + CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance(); + + err = deviceMgr.Init(&AppCallback); + if (err != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(err)); + return; + } + + InitServer(); + + // Set starting endpoint id where dynamic endpoints will be assigned, which + // will be the next consecutive endpoint id after the last fixed endpoint. + gFirstDynamicEndpointId = static_cast( + static_cast(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1))) + 1); + gCurrentEndpointId = gFirstDynamicEndpointId; + + // Disable last fixed endpoint, which is used as a placeholder for all of the + // supported clusters so that ZAP will generated the requisite code. + emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1)), false); // Demo purpose + + // Add lights 1..3 --> will be mapped to ZCL endpoints 2, 3, 4 + AddDeviceEndpoint(&Light1, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT); + AddDeviceEndpoint(&Light2, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT); + AddDeviceEndpoint(&Light3, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT); + + // Remove Light 2 -- Lights 1 & 3 will remain mapped to endpoints 2 & 4 + RemoveDeviceEndpoint(&Light2); + + // Add Light 4 -- > will be mapped to ZCL endpoint 5 + AddDeviceEndpoint(&Light4, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT); + + // Re-add Light 2 -- > will be mapped to ZCL endpoint 6 + AddDeviceEndpoint(&Light2, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT); + + /*Run CHIP*/ + +} diff --git a/examples/bridge-app/esp32/partitions.csv b/examples/bridge-app/esp32/partitions.csv new file mode 100644 index 00000000000000..b338ff11a11589 --- /dev/null +++ b/examples/bridge-app/esp32/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +# Factory partition size about 1.9MB +factory, app, factory, , 1945K, diff --git a/examples/bridge-app/esp32/third_party/connectedhomeip b/examples/bridge-app/esp32/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/bridge-app/esp32/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file