diff --git a/.github/workflows/examples-mbed.yaml b/.github/workflows/examples-mbed.yaml index 16fe097907e695..6b4894833cdd69 100644 --- a/.github/workflows/examples-mbed.yaml +++ b/.github/workflows/examples-mbed.yaml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: False matrix: - EXAMPLE_APP: [lock-app] + EXAMPLE_APP: [lock-app, lighting-app] EXAMPLE_TARGET: [CY8CPROTO_062_4343W] name: "${{matrix.EXAMPLE_APP}}: ${{matrix.EXAMPLE_TARGET}}" diff --git a/.vscode/launch.json b/.vscode/launch.json index 3965083ba2a8ce..883ca66e18e1ed 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -201,7 +201,7 @@ "type": "pickString", "id": "mbedApp", "description": "What mbed application do you want to use?", - "options": ["lock-app"], + "options": ["lock-app", "lighting-app"], "default": "shell" } ] diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d4a03ab8a0b578..1791767dbc38a2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -340,7 +340,7 @@ "type": "pickString", "id": "mbedApp", "description": "What mbed application do you want to use?", - "options": ["lock-app"], + "options": ["lock-app", "lighting-app"], "default": "shell" }, { diff --git a/examples/lighting-app/mbed/.gitignore b/examples/lighting-app/mbed/.gitignore new file mode 100644 index 00000000000000..aa40e98ae380dc --- /dev/null +++ b/examples/lighting-app/mbed/.gitignore @@ -0,0 +1,5 @@ +cmake_build/ +build-*/ +mbed-os +wifi-ism43362 +mbed-os-posix-socket diff --git a/examples/lighting-app/mbed/CMakeLists.txt b/examples/lighting-app/mbed/CMakeLists.txt new file mode 100644 index 00000000000000..3b8007972a0a8d --- /dev/null +++ b/examples/lighting-app/mbed/CMakeLists.txt @@ -0,0 +1,118 @@ +# Copyright (c) 2021 ARM Limited. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.19.0) + +get_filename_component(CHIP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../.. REALPATH) +get_filename_component(APP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/.. REALPATH) +get_filename_component(MBED_COMMON ${CHIP_ROOT}/examples/platform/mbed REALPATH) +get_filename_component(LIGHTING_COMMON ${CHIP_ROOT}/examples/lighting-app/lighting-common REALPATH) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/config.in + ${CMAKE_CURRENT_BINARY_DIR}/chip_build/config + @ONLY +) + +set(MBED_PATH ${CMAKE_CURRENT_SOURCE_DIR}/mbed-os CACHE INTERNAL "") +set(MBED_CONFIG_PATH ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "") +set(APP_TARGET chip-mbed-lighting-app-example) + +include(${MBED_PATH}/tools/cmake/app.cmake) +add_subdirectory(${MBED_PATH} ./mbed_build) + +add_subdirectory(mbed-os-posix-socket) + +if("wifi_ism43362" IN_LIST MBED_TARGET_LABELS) + add_subdirectory(wifi-ism43362) +endif() + +add_executable(${APP_TARGET}) + +target_include_directories(${APP_TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/main/include/ + ${APP_ROOT}/lighting-common + ${MBED_COMMON}/util/include + ${CHIP_ROOT}/src/app + ${CHIP_ROOT}/third_party/nlio/repo/include +) + +target_sources(${APP_TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/main/AppTask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main/LightingManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main/ZclCallbacks.cpp + ${LIGHTING_COMMON}/gen/attribute-size.cpp + ${APP_ROOT}/lighting-common/gen/callback-stub.cpp + ${LIGHTING_COMMON}/gen/IMClusterCommandHandler.cpp + ${MBED_COMMON}/util/LEDWidget.cpp + ${CHIP_ROOT}/src/app/util/DataModelHandler.cpp + ${CHIP_ROOT}/src/app/reporting/reporting-default-configuration.cpp + ${CHIP_ROOT}/src/app/reporting/reporting.cpp + ${CHIP_ROOT}/src/app/util/af-event.cpp + ${CHIP_ROOT}/src/app/util/af-main-common.cpp + ${CHIP_ROOT}/src/app/util/attribute-list-byte-span.cpp + ${CHIP_ROOT}/src/app/util/attribute-size-util.cpp + ${CHIP_ROOT}/src/app/util/attribute-storage.cpp + ${CHIP_ROOT}/src/app/util/attribute-table.cpp + ${CHIP_ROOT}/src/app/util/binding-table.cpp + ${CHIP_ROOT}/src/app/util/chip-message-send.cpp + ${CHIP_ROOT}/src/app/util/client-api.cpp + ${CHIP_ROOT}/src/app/util/ember-compatibility-functions.cpp + ${CHIP_ROOT}/src/app/util/ember-print.cpp + ${CHIP_ROOT}/src/app/util/message.cpp + ${CHIP_ROOT}/src/app/util/process-cluster-message.cpp + ${CHIP_ROOT}/src/app/util/process-global-message.cpp + ${CHIP_ROOT}/src/app/util/util.cpp + ${CHIP_ROOT}/src/app/server/EchoHandler.cpp + ${CHIP_ROOT}/src/app/server/Mdns.cpp + ${CHIP_ROOT}/src/app/server/OnboardingCodesUtil.cpp + ${CHIP_ROOT}/src/app/server/RendezvousServer.cpp + ${CHIP_ROOT}/src/app/server/Server.cpp + ${CHIP_ROOT}/src/app/server/StorablePeerConnection.cpp + ${CHIP_ROOT}/src/app/clusters/basic/basic.cpp + ${CHIP_ROOT}/src/app/clusters/bindings/bindings.cpp + ${CHIP_ROOT}/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp + ${CHIP_ROOT}/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp + ${CHIP_ROOT}/src/app/clusters/on-off-server/on-off-server.cpp + ${CHIP_ROOT}/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp + ${CHIP_ROOT}/src/app/clusters/level-control/level-control.cpp + ${CHIP_ROOT}/src/app/clusters/network-commissioning/network-commissioning-ember.cpp + ${CHIP_ROOT}/src/app/clusters/network-commissioning/network-commissioning.cpp +) + +add_subdirectory(${CHIP_ROOT}/config/mbed ./chip_build) + +mbed_configure_app_target(${APP_TARGET}) + +mbed_set_mbed_target_linker_script(${APP_TARGET}) + +project(${APP_TARGET}) + +target_link_libraries(${APP_TARGET} mbed-os-posix-socket mbed-os mbed-ble mbed-events mbed-netsocket mbed-storage mbed-storage-kv-global-api mbed-mbedtls mbed-mbedtls-cryptocell310 chip) + +if("wifi_ism43362" IN_LIST MBED_TARGET_LABELS) + target_link_libraries(${APP_TARGET} + wifi-ism43362 + ) +endif() + +if("BlueNRG_MS" IN_LIST MBED_TARGET_LABELS) + target_link_libraries(${APP_TARGET} + mbed-ble-blue_nrg + ) +endif() + +if("WHD" IN_LIST MBED_TARGET_LABELS) + target_link_libraries(${APP_TARGET} + mbed-cy_psoc6_common_network mbed-emac mbed-cy_psoc6_whd + ) +endif() + +mbed_set_post_build(${APP_TARGET}) + +option(VERBOSE_BUILD "Have a verbose build process") +if(VERBOSE_BUILD) + set(CMAKE_VERBOSE_MAKEFILE ON) +endif() diff --git a/examples/lighting-app/mbed/config.in b/examples/lighting-app/mbed/config.in new file mode 100644 index 00000000000000..0281e3bf37edd5 --- /dev/null +++ b/examples/lighting-app/mbed/config.in @@ -0,0 +1,5 @@ +CONFIG_CHIP_BUILD_TESTS=n +CONFIG_CHIP_WITH_EXTERNAL_MBEDTLS=y +CONFIG_CHIP_WITH_LWIP=y +CONFIG_CHIP_PROJECT_CONFIG=main/include/CHIPProjectConfig.h +CONFIG_CHIP_BYPASS_RENDEZVOUS=n \ No newline at end of file diff --git a/examples/lighting-app/mbed/main/AppTask.cpp b/examples/lighting-app/mbed/main/AppTask.cpp new file mode 100644 index 00000000000000..f09def75f405e5 --- /dev/null +++ b/examples/lighting-app/mbed/main/AppTask.cpp @@ -0,0 +1,427 @@ +/* + * + * 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 "AppTask.h" +#include "LEDWidget.h" +#include "LightingManager.h" +#include + +// FIXME: Undefine the `sleep()` function included by the CHIPDeviceLayer.h +// from unistd.h to avoid a conflicting declaration with the `sleep()` provided +// by Mbed-OS in mbed_power_mgmt.h. +#define sleep unistd_sleep +#include +#include +#include +#undef sleep + +#include + +// ZAP -- ZCL Advanced Platform +#include +#include +#include +#include + +// mbed-os headers +#include "drivers/InterruptIn.h" +#include "drivers/Timeout.h" +#include "events/EventQueue.h" +#include "platform/Callback.h" + +#define FACTORY_RESET_TRIGGER_TIMEOUT (MBED_CONF_APP_FACTORY_RESET_TRIGGER_TIMEOUT) +#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT (MBED_CONF_APP_FACTORY_RESET_CANCEL_WINDOW_TIMEOUT) +#define LIGHTING_BUTTON (MBED_CONF_APP_LIGHTING_BUTTON) +#define FUNCTION_BUTTON (MBED_CONF_APP_FUNCTION_BUTTON) +#define BUTTON_PUSH_EVENT 1 +#define BUTTON_RELEASE_EVENT 0 + +constexpr uint32_t kPublishServicePeriodUs = 5000000; + +static LEDWidget sStatusLED(MBED_CONF_APP_SYSTEM_STATE_LED); + +static mbed::InterruptIn sLightingButton(LIGHTING_BUTTON); +static mbed::InterruptIn sFunctionButton(FUNCTION_BUTTON); + +static bool sIsWiFiStationProvisioned = false; +static bool sIsWiFiStationEnabled = false; +static bool sIsWiFiStationConnected = false; +static bool sIsPairedToAccount = false; +static bool sHaveBLEConnections = false; +static bool sHaveServiceConnectivity = false; + +static mbed::Timeout sFunctionTimer; + +// TODO: change EventQueue default event size +static events::EventQueue sAppEventQueue; + +using namespace ::chip::DeviceLayer; + +AppTask AppTask::sAppTask; + +int AppTask::Init() +{ + // Register the callback to init the MDNS server when connectivity is available + PlatformMgr().AddEventHandler( + [](const ChipDeviceEvent * event, intptr_t arg) { + // Restart the server whenever an ip address is renewed + if (event->Type == DeviceEventType::kInternetConnectivityChange) + { + if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established || + event->InternetConnectivityChange.IPv6 == kConnectivity_Established) + { + chip::app::Mdns::StartServer(); + } + } + }, + 0); + + //------------- + // Initialize button + sLightingButton.fall(mbed::callback(this, &AppTask::LightingButtonPressEventHandler)); + sFunctionButton.fall(mbed::callback(this, &AppTask::FunctionButtonPressEventHandler)); + sFunctionButton.rise(mbed::callback(this, &AppTask::FunctionButtonReleaseEventHandler)); + //---------------- + // Initialize lighting manager + LightingMgr().Init(MBED_CONF_APP_LIGHTING_STATE_LED); + LightingMgr().SetCallbacks(ActionInitiated, ActionCompleted); + + // Start BLE advertising if needed + if (!CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART) + { + ChipLogProgress(NotSpecified, "Enabling BLE advertising."); + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + } +#ifdef MBED_CONF_APP_DEVICE_NAME + ConnectivityMgr().SetBLEDeviceName(MBED_CONF_APP_DEVICE_NAME); +#endif + + chip::DeviceLayer::ConnectivityMgrImpl().StartWiFiManagement(); + + // Init ZCL Data Model and start server + InitServer(); + ConfigurationMgr().LogDeviceConfig(); + // QR code will be used with CHIP Tool + PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + + return 0; +} + +int AppTask::StartApp() +{ + int ret = Init(); + uint64_t mLastPublishServiceTimeUS = 0; + + if (ret) + { + ChipLogError(NotSpecified, "AppTask.Init() failed"); + return ret; + } + + while (true) + { + sAppEventQueue.dispatch(100); + + // Collect connectivity and configuration state from the CHIP stack. Because the + // CHIP event loop is being run in a separate task, the stack must be locked + // while these values are queried. However we use a non-blocking lock request + // (TryLockChipStack()) to avoid blocking other UI activities when the CHIP + // task is busy (e.g. with a long crypto operation). + + if (PlatformMgr().TryLockChipStack()) + { + sIsWiFiStationProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); + sIsWiFiStationEnabled = ConnectivityMgr().IsWiFiStationEnabled(); + sIsWiFiStationConnected = ConnectivityMgr().IsWiFiStationConnected(); + sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0); + sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity(); + PlatformMgr().UnlockChipStack(); + } + + // Consider the system to be "fully connected" if it has service + // connectivity and it is able to interact with the service on a regular basis. + bool isFullyConnected = sHaveServiceConnectivity; + + // Update the status LED if factory reset has not been initiated. + // + // If system has "full connectivity", keep the LED On constantly. + // + // If thread and service provisioned, but not attached to the thread network yet OR no + // connectivity to the service OR subscriptions are not fully established + // THEN blink the LED Off for a short period of time. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even + // rate of 100ms. + // + // Otherwise, blink the LED ON for a very short time. + if (sAppTask.mFunction != kFunction_FactoryReset) + { + if (isFullyConnected) + { + sStatusLED.Set(true); + } + else if (sIsWiFiStationProvisioned && sIsWiFiStationEnabled && sIsPairedToAccount && + (!sIsWiFiStationConnected || !isFullyConnected)) + { + sStatusLED.Blink(950, 50); + } + else if (sHaveBLEConnections) + { + sStatusLED.Blink(100, 100); + } + else + { + sStatusLED.Blink(50, 950); + } + } + + sStatusLED.Animate(); + + uint64_t nowUS = chip::System::Platform::Layer::GetClock_Monotonic(); + uint64_t nextChangeTimeUS = mLastPublishServiceTimeUS + kPublishServicePeriodUs; + + if (nowUS > nextChangeTimeUS) + { + // TODO: + // PublishService(); + mLastPublishServiceTimeUS = nowUS; + } + } +} + +void AppTask::LightingActionEventHandler(AppEvent * aEvent) +{ + LightingManager::Action_t action = LightingManager::INVALID_ACTION; + int32_t actor = 0; + + if (aEvent->Type == AppEvent::kEventType_Lighting) + { + action = static_cast(aEvent->LightingEvent.Action); + actor = aEvent->LightingEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + action = LightingMgr().IsTurnedOn() ? LightingManager::OFF_ACTION : LightingManager::ON_ACTION; + actor = AppEvent::kEventType_Button; + } + + if (action != LightingManager::INVALID_ACTION && !LightingMgr().InitiateAction(action, actor, 0, NULL)) + ChipLogProgress(NotSpecified, "Action is already in progress or active."); +} + +void AppTask::LightingButtonPressEventHandler() +{ + AppEvent button_event; + button_event.Type = AppEvent::kEventType_Button; + button_event.ButtonEvent.Pin = LIGHTING_BUTTON; + button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; + button_event.Handler = LightingActionEventHandler; + sAppTask.PostEvent(&button_event); +} + +void AppTask::FunctionButtonPressEventHandler() +{ + AppEvent button_event; + button_event.Type = AppEvent::kEventType_Button; + button_event.ButtonEvent.Pin = FUNCTION_BUTTON; + button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; + button_event.Handler = FunctionHandler; + sAppTask.PostEvent(&button_event); +} + +void AppTask::FunctionButtonReleaseEventHandler() +{ + AppEvent button_event; + button_event.Type = AppEvent::kEventType_Button; + button_event.ButtonEvent.Pin = FUNCTION_BUTTON; + button_event.ButtonEvent.Action = BUTTON_RELEASE_EVENT; + button_event.Handler = FunctionHandler; + sAppTask.PostEvent(&button_event); +} + +void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) +{ + if (aAction == LightingManager::ON_ACTION) + { + ChipLogProgress(NotSpecified, "Turn On Action has been initiated"); + } + else if (aAction == LightingManager::OFF_ACTION) + { + ChipLogProgress(NotSpecified, "Turn Off Action has been initiated"); + } + else if (aAction == LightingManager::LEVEL_ACTION) + { + ChipLogProgress(NotSpecified, "Level Action has been initiated"); + } +} + +void AppTask::ActionCompleted(LightingManager::Action_t aAction, int32_t aActor) +{ + if (aAction == LightingManager::ON_ACTION) + { + ChipLogProgress(NotSpecified, "Turn On Action has been completed"); + } + else if (aAction == LightingManager::OFF_ACTION) + { + ChipLogProgress(NotSpecified, "Turn Off Action has been completed"); + } + else if (aAction == LightingManager::LEVEL_ACTION) + { + ChipLogProgress(NotSpecified, "Level Action has been completed"); + } + + if (aActor == AppEvent::kEventType_Button) + { + sAppTask.UpdateClusterState(); + } +} + +void AppTask::CancelTimer() +{ + sFunctionTimer.detach(); + mFunctionTimerActive = false; +} + +void AppTask::StartTimer(uint32_t aTimeoutInMs) +{ + auto chronoTimeoutMs = std::chrono::duration(aTimeoutInMs); + sFunctionTimer.attach(mbed::callback(this, &AppTask::TimerEventHandler), chronoTimeoutMs); + mFunctionTimerActive = true; +} + +void AppTask::PostEvent(AppEvent * aEvent) +{ + auto handle = sAppEventQueue.call([event = *aEvent, this] { DispatchEvent(&event); }); + if (!handle) + { + ChipLogError(NotSpecified, "Failed to post event to app task event queue: Not enough memory"); + } +} + +void AppTask::DispatchEvent(const AppEvent * aEvent) +{ + if (aEvent->Handler) + { + aEvent->Handler(const_cast(aEvent)); + } + else + { + ChipLogError(NotSpecified, "Event received with no handler. Dropping event."); + } +} + +void AppTask::TimerEventHandler() +{ + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + // event.TimerEvent.Context = nullptr; + event.Handler = FunctionTimerEventHandler; + sAppTask.PostEvent(&event); +} + +// static +void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +{ + if (aEvent->Type != AppEvent::kEventType_Timer) + return; + + // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset + if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + { + ChipLogProgress(NotSpecified, "Factory Reset Triggered. Release button within %ums to cancel.", + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); + + // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to + // cancel, if required. + sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); + sAppTask.mFunction = kFunction_FactoryReset; + + sStatusLED.Set(false); + sStatusLED.Blink(500); + } + else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + { + // Actually trigger Factory Reset + ChipLogProgress(NotSpecified, "Factory Reset initiated"); + sAppTask.mFunction = kFunction_NoneSelected; + ConfigurationMgr().InitiateFactoryReset(); + } +} + +void AppTask::FunctionHandler(AppEvent * aEvent) +{ + if (aEvent->ButtonEvent.Pin != FUNCTION_BUTTON) + return; + + // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT) + // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT + // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. + // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the + // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT + if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + { + if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected) + { + sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + + sAppTask.mFunction = kFunction_SoftwareUpdate; + } + } + else + { + // If the button was released before factory reset got initiated, trigger a software update. + if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + { + sAppTask.CancelTimer(); + sAppTask.mFunction = kFunction_NoneSelected; + ChipLogError(NotSpecified, "Software Update not supported."); + } + else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + { + sAppTask.CancelTimer(); + + // Change the function to none selected since factory reset has been canceled. + sAppTask.mFunction = kFunction_NoneSelected; + + ChipLogProgress(NotSpecified, "Factory Reset has been Canceled"); + } + } +} + +void AppTask::UpdateClusterState() +{ + uint8_t onoff = LightingMgr().IsTurnedOn(); + + // write the new on/off value + EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, &onoff, + ZCL_BOOLEAN_ATTRIBUTE_TYPE); + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(NotSpecified, "Updating on/off cluster failed: %x", status); + } + + uint8_t level = LightingMgr().GetLevel(); + + status = emberAfWriteAttribute(1, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, &level, + ZCL_INT8U_ATTRIBUTE_TYPE); + + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(NotSpecified, "Updating level cluster failed: %x", status); + } +} diff --git a/examples/lighting-app/mbed/main/LightingManager.cpp b/examples/lighting-app/mbed/main/LightingManager.cpp new file mode 100644 index 00000000000000..b747b92f332407 --- /dev/null +++ b/examples/lighting-app/mbed/main/LightingManager.cpp @@ -0,0 +1,125 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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 "LightingManager.h" +#include "AppTask.h" + +#include + +// mbed-os headers +#include "platform/Callback.h" + +#ifdef MBED_CONF_APP_LED_ACTIVE_STATE +#define LED_ACTIVE_STATE (MBED_CONF_APP_LED_ACTIVE_STATE) +#else +#define LED_ACTIVE_STATE 0 +#endif + +LightingManager LightingManager::sLight; + +void LightingManager::Init(PinName pwmPinName) +{ + mState = kState_On; + mLevel = kMaxLevel; + mPwmDevice = new mbed::PwmOut(pwmPinName); + + Set(false); +} + +void LightingManager::SetCallbacks(LightingCallback_fn aActionInitiated_CB, LightingCallback_fn aActionCompleted_CB) +{ + mActionInitiated_CB = aActionInitiated_CB; + mActionCompleted_CB = aActionCompleted_CB; +} + +bool LightingManager::InitiateAction(Action_t aAction, int32_t aActor, uint8_t size, uint8_t * value) +{ + bool action_initiated = false; + State_t new_state; + + // Initiate On/Off Action only when the previous one is complete. + if (mState == kState_Off && aAction == ON_ACTION) + { + action_initiated = true; + new_state = kState_On; + } + else if (mState == kState_On && aAction == OFF_ACTION) + { + action_initiated = true; + new_state = kState_Off; + } + else if (aAction == LEVEL_ACTION && *value != mLevel) + { + action_initiated = true; + if (*value == 0) + { + new_state = kState_Off; + } + else + { + new_state = kState_On; + } + } + + if (action_initiated) + { + if (mActionInitiated_CB) + { + mActionInitiated_CB(aAction, aActor); + } + + if (aAction == ON_ACTION || aAction == OFF_ACTION) + { + Set(new_state == kState_On); + } + else if (aAction == LEVEL_ACTION) + { + SetLevel(*value); + } + + if (mActionCompleted_CB) + { + mActionCompleted_CB(aAction, aActor); + } + } + + return action_initiated; +} + +void LightingManager::SetLevel(uint8_t aLevel) +{ + ChipLogProgress(NotSpecified, "Setting brightness level to %u", aLevel); + mLevel = aLevel; + UpdateLight(); +} + +void LightingManager::Set(bool aOn) +{ + mState = aOn ? kState_On : kState_Off; + UpdateLight(); +} + +void LightingManager::UpdateLight() +{ + constexpr uint32_t kPwmWidthUs = 20000u; + const uint8_t level = mState == kState_On ? mLevel : 0; + mPwmDevice->period_us(kPwmWidthUs); + + mPwmDevice->write((float) (LED_ACTIVE_STATE ? level : kMaxLevel - level) / kMaxLevel); +} diff --git a/examples/lighting-app/mbed/main/ZclCallbacks.cpp b/examples/lighting-app/mbed/main/ZclCallbacks.cpp new file mode 100644 index 00000000000000..497f26f679db08 --- /dev/null +++ b/examples/lighting-app/mbed/main/ZclCallbacks.cpp @@ -0,0 +1,91 @@ +/* + * + * 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 + +#include +#include +#include +#include +#include + +#include "AppTask.h" +#include "LightingManager.h" + +using namespace chip; + +void emberAfPostAttributeChangeCallback(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, uint8_t mask, + uint16_t manufacturerCode, uint8_t type, uint16_t size, uint8_t * value) +{ + ChipLogProgress(Zcl, "Cluster callback: %" PRIx32, clusterId); + + if (clusterId == ZCL_ON_OFF_CLUSTER_ID) + { + if (attributeId != ZCL_ON_OFF_ATTRIBUTE_ID) + { + ChipLogProgress(Zcl, "Unknown attribute ID: %" PRIx32, attributeId); + return; + } + + LightingMgr().InitiateAction(*value ? LightingManager::ON_ACTION : LightingManager::OFF_ACTION, + AppEvent::kEventType_Lighting, size, value); + } + else if (clusterId == ZCL_LEVEL_CONTROL_CLUSTER_ID) + { + if (attributeId != ZCL_CURRENT_LEVEL_ATTRIBUTE_ID) + { + ChipLogProgress(Zcl, "Unknown attribute ID: %" PRIx32, attributeId); + return; + } + + ChipLogProgress(Zcl, "Value: %u, length %u", *value, size); + if (size == 1) + { + LightingMgr().InitiateAction(LightingManager::LEVEL_ACTION, AppEvent::kEventType_Lighting, size, value); + } + else + { + ChipLogError(Zcl, "wrong length for level: %d", size); + } + } + else + { + ChipLogProgress(Zcl, "Unknown cluster ID: %" PRIx32, clusterId); + return; + } +} + +/** @brief OnOff Cluster Init + * + * This function is called when a specific cluster is initialized. It gives the + * application an opportunity to take care of cluster initialization procedures. + * It is called exactly once for each endpoint where cluster is present. + * + * @param endpoint Ver.: always + * + * TODO Issue #3841 + * emberAfOnOffClusterInitCallback happens before the stack initialize the cluster + * attributes to the default value. + * The logic here expects something similar to the deprecated Plugins callback + * emberAfPluginOnOffClusterServerPostInitCallback. + * + */ +void emberAfOnOffClusterInitCallback(EndpointId endpoint) +{ + GetAppTask().UpdateClusterState(); +} diff --git a/examples/lighting-app/mbed/main/include/AppEvent.h b/examples/lighting-app/mbed/main/include/AppEvent.h new file mode 100644 index 00000000000000..bd411a3313efee --- /dev/null +++ b/examples/lighting-app/mbed/main/include/AppEvent.h @@ -0,0 +1,58 @@ +/* + * + * 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. + */ + +#pragma once + +#include + +struct AppEvent; +typedef void (*EventHandler)(AppEvent *); + +struct AppEvent +{ + enum AppEventTypes + { + kEventType_Button = 0, + kEventType_Timer, + kEventType_Lighting, + kEventType_Install, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t Pin; + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + int32_t Actor; + } LightingEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/lighting-app/mbed/main/include/AppTask.h b/examples/lighting-app/mbed/main/include/AppTask.h new file mode 100644 index 00000000000000..b016696e93f6df --- /dev/null +++ b/examples/lighting-app/mbed/main/include/AppTask.h @@ -0,0 +1,74 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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. + */ + +#pragma once + +#include "AppEvent.h" +#include "LightingManager.h" + +class AppTask +{ +public: + int StartApp(); + + void PostLightingActionRequest(LightingManager::Action_t aAction); + void PostEvent(AppEvent * aEvent); + void UpdateClusterState(void); + +private: + friend AppTask & GetAppTask(void); + + int Init(); + + static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); + static void ActionCompleted(LightingManager::Action_t aAction, int32_t aActor); + + void CancelTimer(void); + + void DispatchEvent(const AppEvent * event); + + static void FunctionTimerEventHandler(AppEvent * aEvent); + static void FunctionHandler(AppEvent * aEvent); + static void LightingActionEventHandler(AppEvent * aEvent); + + void LightingButtonPressEventHandler(void); + void FunctionButtonPressEventHandler(void); + void FunctionButtonReleaseEventHandler(void); + void TimerEventHandler(void); + + void StartTimer(uint32_t aTimeoutInMs); + + enum Function_t + { + kFunction_NoneSelected = 0, + kFunction_SoftwareUpdate = 0, + kFunction_FactoryReset, + + kFunction_Invalid + }; + + Function_t mFunction; + bool mFunctionTimerActive; + static AppTask sAppTask; +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/lighting-app/mbed/main/include/CHIPProjectConfig.h b/examples/lighting-app/mbed/main/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..49f93f30e931b1 --- /dev/null +++ b/examples/lighting-app/mbed/main/include/CHIPProjectConfig.h @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2020-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 + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 1 + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 + +// Enable log filtering. +#define CHIP_LOG_FILTERING 1 diff --git a/examples/lighting-app/mbed/main/include/LightingManager.h b/examples/lighting-app/mbed/main/include/LightingManager.h new file mode 100644 index 00000000000000..eb2185f8802bc5 --- /dev/null +++ b/examples/lighting-app/mbed/main/include/LightingManager.h @@ -0,0 +1,75 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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. + */ + +#pragma once + +#include + +#include "AppEvent.h" + +#include "drivers/PwmOut.h" + +class LightingManager +{ +public: + enum Action_t : uint8_t + { + ON_ACTION = 0, + OFF_ACTION, + LEVEL_ACTION, + + INVALID_ACTION + }; + + enum State_t : uint8_t + { + kState_On = 0, + kState_Off, + }; + + using LightingCallback_fn = void (*)(Action_t, int32_t); + + void Init(PinName pwmPinName); + bool IsTurnedOn() const { return mState == kState_On; } + uint8_t GetLevel() const { return mLevel; } + bool InitiateAction(Action_t aAction, int32_t aActor, uint8_t size, uint8_t * value); + void SetCallbacks(LightingCallback_fn aActionInitiated_CB, LightingCallback_fn aActionCompleted_CB); + +private: + static constexpr uint8_t kMaxLevel = 255; + + friend LightingManager & LightingMgr(); + State_t mState; + uint8_t mLevel; + mbed::PwmOut * mPwmDevice; + + LightingCallback_fn mActionInitiated_CB; + LightingCallback_fn mActionCompleted_CB; + + void Set(bool aOn); + void SetLevel(uint8_t aLevel); + void UpdateLight(); + + static LightingManager sLight; +}; + +inline LightingManager & LightingMgr(void) +{ + return LightingManager::sLight; +} diff --git a/examples/lighting-app/mbed/main/main.cpp b/examples/lighting-app/mbed/main/main.cpp new file mode 100644 index 00000000000000..199a8c70ba2279 --- /dev/null +++ b/examples/lighting-app/mbed/main/main.cpp @@ -0,0 +1,82 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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 "AppTask.h" + +#include "mbedtls/platform.h" +#include +#include +#include + +#ifdef MBED_CONF_MBED_TRACE_ENABLE +#include "mbed-trace/mbed_trace.h" +#endif + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::DeviceLayer; + +int main() +{ + int ret = 0; + +#ifdef MBED_CONF_MBED_TRACE_ENABLE + mbed_trace_init(); + mbed_trace_include_filters_set("BSDS,NETS"); + mbed_trace_config_set(TRACE_ACTIVE_LEVEL_ALL | TRACE_MODE_COLOR); +#endif + + // note: Make sure to turn the filtering on with CHIP_LOG_FILTERING=1 + chip::Logging::SetLogFilter(chip::Logging::LogCategory::kLogCategory_Progress); + + ret = mbedtls_platform_setup(NULL); + if (ret) + { + ChipLogError(NotSpecified, "Mbed TLS platform initialization failed with error %d", ret); + goto exit; + } + + ret = chip::Platform::MemoryInit(); + if (ret != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Platform::MemoryInit() failed"); + goto exit; + } + + ChipLogProgress(NotSpecified, "Init CHIP Stack\r\n"); + ret = PlatformMgr().InitChipStack(); + if (ret != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "PlatformMgr().InitChipStack() failed"); + } + + ChipLogProgress(NotSpecified, "Starting CHIP task"); + ret = PlatformMgr().StartEventLoopTask(); + if (ret != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "PlatformMgr().StartEventLoopTask() failed"); + goto exit; + } + + ret = GetAppTask().StartApp(); + +exit: + ChipLogProgress(NotSpecified, "Exited with code %d", ret); + return ret; +} diff --git a/examples/lighting-app/mbed/mbed-os.lib b/examples/lighting-app/mbed/mbed-os.lib new file mode 100644 index 00000000000000..b1f64f02f1511f --- /dev/null +++ b/examples/lighting-app/mbed/mbed-os.lib @@ -0,0 +1 @@ +https://github.com/ARMmbed/mbed-os#master \ No newline at end of file diff --git a/examples/lighting-app/mbed/mbed_app.json b/examples/lighting-app/mbed/mbed_app.json new file mode 100644 index 00000000000000..0c8e4c3dda1fd7 --- /dev/null +++ b/examples/lighting-app/mbed/mbed_app.json @@ -0,0 +1,82 @@ +{ + "macros": ["MBEDTLS_USER_CONFIG_FILE=\"chip_mbedtls_config.h\""], + "target_overrides": { + "*": { + "platform.stdio-baud-rate": 115200, + "lwip.ipv6-enabled": true, + "lwip.raw-socket-enabled": true, + "nsapi.default-wifi-security": "WPA_WPA2", + "nsapi.default-wifi-ssid": "\"YOUR_SSID\"", + "nsapi.default-wifi-password": "\"YOUR_PASSWORD\"", + "mbed-trace.max-level": "TRACE_LEVEL_DEBUG", + "mbed-trace.enable": false + }, + "DISCO_L475VG_IOT01A": { + "target.components_add": ["wifi_ism43362", "BlueNRG_MS"], + "ism43362.provide-default": true, + "target.network-default-interface-type": "WIFI", + "target.macros_add": ["MBEDTLS_SHA1_C"], + "target.features_add": ["BLE"], + "target.extra_labels_add": ["CORDIO"], + "led-active-state": 1, + "lighting-button": "USER_BUTTON", + "function-button": "NC" + }, + "CY8CPROTO_062_4343W": { + "target.network-default-interface-type": "WIFI", + "target.macros_add": [ + "MXCRYPTO_DISABLED", + "NL_ASSERT_LOG=NL_ASSERT_LOG_DEFAULT", + "NL_ASSERT_EXPECT_FLAGS=NL_ASSERT_FLAG_LOG", + "WHD_PRINT_DISABLE" + ], + "led-active-state": 0, + "system-state-led": "LED1", + "lighting-state-led": "P9_6", + "lighting-button": "USER_BUTTON", + "function-button": "P9_7" + } + }, + "config": { + "system-state-led": { + "help": "System status LED.", + "value": "LED1" + }, + "lighting-state-led": { + "help": "lighting status LED.", + "value": "LED2" + }, + "actuator-movement-period-ms": { + "help": "Time it takes in ms for the simulated actuator to move from one state to another.", + "value": 2000 + }, + "led-active-state": { + "help": "GPIO output to turn the LED on.", + "value": 0 + }, + "lighting-button": { + "help": "lighting botton pin.", + "value": "BUTTON2" + }, + "function-button": { + "help": "Function button pin.", + "value": "BUTTON1" + }, + "factory-reset-trigger-timeout": { + "help": "'function-button' press timeout to trigger a factory reset (in milliseconds).", + "value": 3000 + }, + "factory-reset-cancel-window-timeout": { + "help": "'function-button' press timeout to cancel a factory reset (in milliseconds). The total 'function-button' press time to have the factory reset actually initiated is equal to 'factory-reset-trigger-timeout' + 'factory-reset-cancel-window-timeout'.", + "value": 3000 + }, + "device-name": { + "help": "Name used for BLE advertising.", + "value": "\"CHIP-lighting\"" + }, + "use-gatt-indication-ack-hack": { + "help": "Fake a TX transfer confirmation. Send a 'kCHIPoBLEIndicateConfirm' event as soon as data is sent, without waiting for the actual ACK from the GATT client. This hack has to stay until we provide a fix in the Mbed OS repo.", + "value": 1 + } + } +} diff --git a/examples/lighting-app/mbed/wifi-ism43362.lib b/examples/lighting-app/mbed/wifi-ism43362.lib new file mode 100644 index 00000000000000..fd1d1bba4efcf9 --- /dev/null +++ b/examples/lighting-app/mbed/wifi-ism43362.lib @@ -0,0 +1 @@ +https://github.com/ATmobica/wifi-ism43362#master \ No newline at end of file