diff --git a/.github/workflows/examples-esp32.yaml b/.github/workflows/examples-esp32.yaml index 9bb9dde55f47a1..7fd764985c4afc 100644 --- a/.github/workflows/examples-esp32.yaml +++ b/.github/workflows/examples-esp32.yaml @@ -92,6 +92,14 @@ jobs: mkdir -p example_binaries/$BUILD_TYPE-build cp examples/lock-app/esp32/build/chip-lock-app.elf \ example_binaries/$BUILD_TYPE-build/chip-lock-app.elf + - name: Build example Bridge App + timeout-minutes: 5 + run: scripts/examples/esp_example.sh bridge-app + - name: Copy aside build products + run: | + mkdir -p example_binaries/$BUILD_TYPE-build + cp examples/bridge-app/esp32/build/chip-bridge-app.elf \ + example_binaries/$BUILD_TYPE-build/chip-bridge-app.elf - name: Build example Persistent Storage App timeout-minutes: 5 run: scripts/examples/esp_example.sh persistent-storage sdkconfig.defaults diff --git a/examples/bridge-app/esp32/CMakeLists.txt b/examples/bridge-app/esp32/CMakeLists.txt new file mode 100644 index 00000000000000..0f4c14923db697 --- /dev/null +++ b/examples/bridge-app/esp32/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright (c) 2021 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. + +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/QRCode" +) + +project(chip-bridge-app) +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++14;-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..25dc8cd0e7073b --- /dev/null +++ b/examples/bridge-app/esp32/main/CHIPDeviceManager.cpp @@ -0,0 +1,111 @@ +/* + * + * Copyright (c) 2021 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(); + if (err != CHIP_NO_ERROR) + { + return 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(); + if (err != CHIP_NO_ERROR) + { + return 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(); + if (err != CHIP_NO_ERROR) + { + return err; + } + return CHIP_NO_ERROR; +} +} // 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..e80d067542dc08 --- /dev/null +++ b/examples/bridge-app/esp32/main/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright (c) 2021 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. + +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 bt) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 14) +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..f05b849ae69f45 --- /dev/null +++ b/examples/bridge-app/esp32/main/Device.cpp @@ -0,0 +1,121 @@ +/* + * + * 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. + */ + +#include "Device.h" + +#include +#include +#include + +using namespace ::chip::Platform; + +Device::Device(const char * szDeviceName, const char * szLocation) +{ + CopyString(mName, sizeof(mName), szDeviceName); + CopyString(mLocation, sizeof(mLocation), szLocation); + mState = kState_Off; + mReachable = false; + mEndpointId = 0; + mChanged_CB = nullptr; +} + +bool Device::IsOn() const +{ + return mState == kState_On; +} + +bool Device::IsReachable() const +{ + 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); + + CopyString(mName, sizeof(mName), szName); + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_Name); + } +} + +void Device::SetLocation(const char * szLocation) +{ + bool changed = (strncmp(mLocation, szLocation, sizeof(mLocation)) != 0); + + CopyString(mLocation, sizeof(mLocation), szLocation); + + 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..aed257181a1f0e --- /dev/null +++ b/examples/bridge-app/esp32/main/DeviceCallbacks.cpp @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2021 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. + */ + +#include "DeviceCallbacks.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include +#include +#include +#include + +static const char * TAG = "bridge-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); + 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!"); + } +} diff --git a/examples/bridge-app/esp32/main/Kconfig.projbuild b/examples/bridge-app/esp32/main/Kconfig.projbuild new file mode 100644 index 00000000000000..11f2471dad4d2e --- /dev/null +++ b/examples/bridge-app/esp32/main/Kconfig.projbuild @@ -0,0 +1,30 @@ +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 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..32a3ad4a4d598b --- /dev/null +++ b/examples/bridge-app/esp32/main/include/CHIPDeviceManager.h @@ -0,0 +1,120 @@ +/* + * + * Copyright (c) 2021 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..de7bf8913bbdba --- /dev/null +++ b/examples/bridge-app/esp32/main/include/Device.h @@ -0,0 +1,67 @@ +/* + * + * Copyright (c) 2021 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. + */ + +// 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() const; + bool IsReachable() const; + 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..0e1948936563c3 --- /dev/null +++ b/examples/bridge-app/esp32/main/include/DeviceCallbacks.h @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2021 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 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..51d8326d91e90f --- /dev/null +++ b/examples/bridge-app/esp32/main/main.cpp @@ -0,0 +1,415 @@ +/* + * + * 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. + */ + +#include "Device.h" +#include "DeviceCallbacks.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const char * TAG = "bridge-app"; + +using namespace ::chip; +using namespace ::chip::DeviceManager; +using namespace ::chip::DeviceLayer; +using namespace ::chip::Platform; + +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); + +CHIP_ERROR 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; + 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); + gCurrentEndpointId++; + return CHIP_NO_ERROR; + } + else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS) + { + ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint, Insufficient space"); + return CHIP_ERROR_INTERNAL; + } + } + index++; + } + ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!"); + return CHIP_ERROR_INTERNAL; +} + +CHIP_ERROR RemoveDeviceEndpoint(Device * dev) +{ + for (uint8_t index = 0; index < DYNAMIC_ENDPOINT_COUNT; index++) + { + 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 CHIP_NO_ERROR; + } + } + return CHIP_ERROR_INTERNAL; +} + +// 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) +{ + uint16_t listCount = 1; + _LabelStruct labelStruct; + + labelStruct.label = chip::ByteSpan(Uint8::from_const_char(label), strlen(label)); + labelStruct.value = chip::ByteSpan(Uint8::from_const_char(value), strlen(value)); + + 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); + + ReturnErrorCodeIf((attributeId != ZCL_ON_OFF_ATTRIBUTE_ID) || (maxReadLength != 1), EMBER_ZCL_STATUS_FAILURE); + *buffer = dev->IsOn() ? 1 : 0; + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleWriteOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer) +{ + ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%d", attributeId); + + ReturnErrorCodeIf((attributeId != ZCL_ON_OFF_ATTRIBUTE_ID) || (!dev->IsReachable()), EMBER_ZCL_STATUS_FAILURE); + dev->SetOnOff(*buffer == 1); + 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); + + if ((endpointIndex < DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != NULL)) + { + Device * dev = gDevices[endpointIndex]; + + if (clusterId == ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID) + { + return HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == ZCL_DESCRIPTOR_CLUSTER_ID) + { + return HandleReadDescriptorAttribute(endpointIndex, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == ZCL_FIXED_LABEL_CLUSTER_ID) + { + return HandleReadFixedLabelAttribute(dev, attributeMetadata, buffer, maxReadLength); + } + else if (clusterId == ZCL_ON_OFF_CLUSTER_ID) + { + return HandleReadOnOffAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +EmberAfStatus emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, + EmberAfAttributeMetadata * attributeMetadata, uint16_t manufacturerCode, + uint8_t * buffer, int32_t index) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + if (endpointIndex < DYNAMIC_ENDPOINT_COUNT) + { + Device * dev = gDevices[endpointIndex]; + + if ((dev->IsReachable()) && (clusterId == ZCL_ON_OFF_CLUSTER_ID)) + { + return HandleWriteOnOffAttribute(dev, attributeMetadata->attributeId, buffer); + } + else if (clusterId == ZCL_DESCRIPTOR_CLUSTER_ID) + { + return HandleWriteDescriptorAttribute(endpointIndex, attributeMetadata, buffer, attributeMetadata->size, index); + } + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +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() +{ + // Initialize the ESP NVS layer. + esp_err_t err = nvs_flash_init(); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "nvs_flash_init() failed: %s", esp_err_to_name(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 + static Device Light1("Light 1", "Office"); + static Device Light2("Light 2", "Office"); + static Device Light3("Light 3", "Kitchen"); + static 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); + + CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance(); + + chip_err = deviceMgr.Init(&AppCallback); + if (chip_err != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(chip_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); + + // 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); +} 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/sdkconfig.defaults b/examples/bridge-app/esp32/sdkconfig.defaults new file mode 100644 index 00000000000000..956dcfe0eecedd --- /dev/null +++ b/examples/bridge-app/esp32/sdkconfig.defaults @@ -0,0 +1,38 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# Copyright (c) 2018 Nest Labs, Inc. +# 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. +# +# Description: +# Some useful defaults for the demo app configuration. +# + +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +#enable BT +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +#enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" 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