diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index a478491e4239fd..845b6911b280a9 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -44,6 +44,7 @@ aef AES algs alloc +AlarmCode Ameba amebad amebaiot @@ -423,6 +424,7 @@ Dockerfile Dockerfiles Don'ts DoorLock +DoorState doru DOTBR DOVERLAY diff --git a/examples/lock-app/linux/BUILD.gn b/examples/lock-app/linux/BUILD.gn index 22706fcfb83686..189d37306e5548 100644 --- a/examples/lock-app/linux/BUILD.gn +++ b/examples/lock-app/linux/BUILD.gn @@ -18,6 +18,7 @@ import("//build_overrides/chip.gni") executable("chip-lock-app") { sources = [ "main.cpp", + "src/LockAppCommandDelegate.cpp", "src/LockEndpoint.cpp", "src/LockManager.cpp", "src/ZCLDoorLockCallbacks.cpp", @@ -27,6 +28,7 @@ executable("chip-lock-app") { "${chip_root}/examples/lock-app/lock-common", "${chip_root}/examples/platform/linux:app-main", "${chip_root}/src/lib", + "${chip_root}/third_party/jsoncpp", ] include_dirs = [ diff --git a/examples/lock-app/linux/README.md b/examples/lock-app/linux/README.md index 124bf5999d8d27..3d55dae5130c97 100644 --- a/examples/lock-app/linux/README.md +++ b/examples/lock-app/linux/README.md @@ -1,12 +1,6 @@ # Lock Application for Linux -This application is quite different from regular lock-app. The app showcases the -current implementation of the Door Lock cluster and doesn't rely on the On/Off -cluster to do the job. - -For now it is not possible to change lock parameters from the app, the -functionality should be probably presented either as CLI or something that could -be controlled from tests (like RPC in lighting-app). +Application that showcases abilities of the Door Lock Cluster. ## Building @@ -15,3 +9,57 @@ The application could be build in the same manner as `all-clusters-app`: ``` ? scripts/examples/gn_build_example.sh examples/lock-app/linux out/lock-app chip_config_network_layer_ble=false ``` + +# Named Pipe Interface + +This application accepts commands over named pipe. Pipe is located in +`/tmp/chip_lock_app_fifo-`. + +## Command Format + +Commands interface accepts commands formatted as a JSON object. Structure of the +object: + +| Key | Type | Value Description | Optional | +| -------- | ------ | ----------------------------------------------------------------------------------------------- | -------- | +| `Cmd` | String | Contains name of the command to execute | No | +| `Params` | Object | Contains parameters for the command. Could be omitted if the command does not accept parameters | Yes | + +For example: + +- `{ "Cmd": "SetDoorState", "Params": { "EndpointId": 1, "DoorState": 2 } }` - + command to set the Door Jammed door state. +- `{ "Cmd": "SendDoorLockAlarm", "Params": { "AlarmCode": 0 } }` - command to + send the Door Lock Alarm event with Lock Jammed alarm code. + +## Available commands + +### Set Door State + +- Name: `SetDoorState` +- Parameters: + - `EndpointId` (Uint, Optional): ID of the endpoint where command should + be executed. Could be omitted, in that case the default endpoint with ID + 1 is targeted. + - `DoorState` (DoorState enum): new door state. +- Usage: + ```bash + echo '{"Cmd": "SetDoorState", "Params": { "EndpointId": 1, "DoorState": 1 } }' > /tmp/chip_lock_app_fifo- + ``` + This command will set the door state to "Closed" resulting in Door State + Change event. + +### Send Door Lock Alarm + +- Name: `SetDoorState` +- Parameters: + - `EndpointId` (Uint, Optional): ID of the endpoint where command should + be executed. Could be omitted, in that case the default endpoint with ID + 1 is targeted + - `AlarmCode` (AlarmCode enum): code of the Lock Door Alarm to send. +- Usage: + ```bash + echo '{"Cmd": "SendDoorLockAlarm", "Params": { "EndpointId": 1, "AlarmCode": 0 } }' > /tmp/chip_lock_app_fifo- + ``` + This command will send the Door Lock Alarm event with "Lock Jammed" alarm + code. diff --git a/examples/lock-app/linux/include/LockAppCommandDelegate.h b/examples/lock-app/linux/include/LockAppCommandDelegate.h new file mode 100644 index 00000000000000..332a0835d053f2 --- /dev/null +++ b/examples/lock-app/linux/include/LockAppCommandDelegate.h @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2022 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. + */ + +#pragma once + +#include +#include + +#include + +class LockAppCommandDelegate : public NamedPipeCommandDelegate +{ +public: + void OnEventCommandReceived(const char * json) override; +}; diff --git a/examples/lock-app/linux/include/LockEndpoint.h b/examples/lock-app/linux/include/LockEndpoint.h index 8b1df87979f894..91a4b4f71b727d 100644 --- a/examples/lock-app/linux/include/LockEndpoint.h +++ b/examples/lock-app/linux/include/LockEndpoint.h @@ -75,7 +75,7 @@ class LockEndpoint DlDoorState GetDoorState() const; - bool SendLockJammedAlarm() const; + bool SendLockAlarm(DlAlarmCode alarmCode) const; bool GetCredential(uint16_t credentialIndex, DlCredentialType credentialType, EmberAfPluginDoorLockCredentialInfo & credential) const; diff --git a/examples/lock-app/linux/include/LockManager.h b/examples/lock-app/linux/include/LockManager.h index bbd994e94a286a..a517e07580af3a 100644 --- a/examples/lock-app/linux/include/LockManager.h +++ b/examples/lock-app/linux/include/LockManager.h @@ -27,13 +27,13 @@ class LockManager { public: - LockManager() {} + LockManager() = default; bool InitEndpoint(chip::EndpointId endpointId); - bool ToggleDoorState(chip::EndpointId endpointId); + bool SetDoorState(chip::EndpointId endpointId, DlDoorState doorState); - bool SendLockJammedAlarm(chip::EndpointId endpointId); + bool SendLockAlarm(chip::EndpointId endpointId, DlAlarmCode alarmCode); bool Lock(chip::EndpointId endpointId, const Optional & pin, DlOperationError & err); bool Unlock(chip::EndpointId endpointId, const Optional & pin, DlOperationError & err); diff --git a/examples/lock-app/linux/main.cpp b/examples/lock-app/linux/main.cpp index 49bcba68ba6b16..661aaaa53497a2 100644 --- a/examples/lock-app/linux/main.cpp +++ b/examples/lock-app/linux/main.cpp @@ -20,6 +20,7 @@ #include #include +#include "LockAppCommandDelegate.h" #include "LockManager.h" using namespace chip; @@ -53,50 +54,28 @@ void InitNetworkCommissioning() {} #endif // (CHIP_DEVICE_LAYER_TARGET_LINUX && CHIP_DEVICE_CONFIG_ENABLE_THREAD) || CHIP_DEVICE_CONFIG_ENABLE_WPA -} // anonymous namespace - -static void ToggleDoorStatusSignalHandler(int aSignal); - -static void TriggerDoorLockAlarm(int aSignal); +// Variables for handling named pipe commands +constexpr const char kChipEventFifoPathPrefix[] = "/tmp/chip_lock_app_fifo-"; +NamedPipeCommands sChipNamedPipeCommands; +LockAppCommandDelegate sLockAppCommandDelegate; -static void SetupSignalHandlers() -{ - signal(SIGUSR1, ToggleDoorStatusSignalHandler); - signal(SIGUSR2, TriggerDoorLockAlarm); -} +} // anonymous namespace void ApplicationInit() { InitNetworkCommissioning(); + + auto path = kChipEventFifoPathPrefix + std::to_string(getpid()); + if (sChipNamedPipeCommands.Start(path, &sLockAppCommandDelegate) != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to start CHIP NamedPipeCommands"); + sChipNamedPipeCommands.Stop(); + } } int main(int argc, char * argv[]) { - SetupSignalHandlers(); - VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); ChipLinuxAppMainLoop(); return 0; } - -static void ToggleDoorStatusSignalHandler(int aSignal) -{ - if (aSignal != SIGUSR1) - { - return; - } - // Will toggle the door lock state on the first endpoint. - DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { LockManager::Instance().ToggleDoorState(DOOR_LOCK_SERVER_ENDPOINT); }, - 0); -} - -static void TriggerDoorLockAlarm(int aSignal) -{ - if (aSignal != SIGUSR2) - { - return; - } - // Will send the DoorLockAlarm event with LockJammed alarm code on the first endpoint. - DeviceLayer::PlatformMgr().ScheduleWork( - [](intptr_t) { LockManager::Instance().SendLockJammedAlarm(DOOR_LOCK_SERVER_ENDPOINT); }, 0); -} diff --git a/examples/lock-app/linux/src/LockAppCommandDelegate.cpp b/examples/lock-app/linux/src/LockAppCommandDelegate.cpp new file mode 100644 index 00000000000000..0bb7e6ca92e1c8 --- /dev/null +++ b/examples/lock-app/linux/src/LockAppCommandDelegate.cpp @@ -0,0 +1,152 @@ +/* + * + * Copyright (c) 2022 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 "LockAppCommandDelegate.h" +#include + +#include +#include + +using chip::to_underlying; + +class LockAppCommandHandler +{ +public: + static LockAppCommandHandler * FromJSON(const char * json); + + static void HandleCommand(intptr_t context); + + LockAppCommandHandler(std::string cmd, Json::Value params) : mCommandName(std::move(cmd)), mCommandParameters(std::move(params)) + {} + +private: + std::string mCommandName; + Json::Value mCommandParameters; +}; + +LockAppCommandHandler * LockAppCommandHandler::FromJSON(const char * json) +{ + // Command format: + // { "Cmd": "SetDoorState", "Params": { "EndpointId": 1, "DoorState": 2} } + Json::Reader reader; + Json::Value value; + if (!reader.parse(json, value)) + { + ChipLogError(NotSpecified, "Lock App: Error parsing JSON with error %s:", reader.getFormattedErrorMessages().c_str()); + return nullptr; + } + + if (value.empty() || !value.isObject()) + { + ChipLogError(NotSpecified, "Lock App: Invalid JSON command received"); + return nullptr; + } + + if (!value.isMember("Cmd") || !value["Cmd"].isString()) + { + ChipLogError(NotSpecified, "Lock App: Invalid JSON command received: command name is missing"); + return nullptr; + } + + Json::Value params = Json::objectValue; + if (value.isMember("Params")) + { + if (!value["Params"].isObject()) + { + ChipLogError(NotSpecified, "Lock App: Invalid JSON command received: specified parameters are incorrect"); + return nullptr; + } + params = value["Params"]; + } + auto commandName = value["Cmd"].asString(); + return chip::Platform::New(commandName, params); +} + +void LockAppCommandHandler::HandleCommand(intptr_t context) +{ + auto * self = reinterpret_cast(context); + const auto & params = self->mCommandParameters; + // Determine the endpoint ID from the parameters JSON. If it is missing, use the default endpoint defined in the + // door-lock-server.h + chip::EndpointId endpointId = DOOR_LOCK_SERVER_ENDPOINT; + if (params.isMember("EndpointId")) + { + VerifyOrExit(params["EndpointId"].isUInt(), + ChipLogError(NotSpecified, "Lock App: Unable to execute command \"%s\": invalid endpoint Id", + self->mCommandName.c_str())); + endpointId = static_cast(params["EndpointId"].asUInt()); + } + + // TODO: Make commands separate objects derived from some base class to clean up this mess. + + // Now we can try to execute a command + if (self->mCommandName == "SetDoorState") + { + VerifyOrExit(params.isMember("DoorState"), + ChipLogError(NotSpecified, + "Lock App: Unable to execute command to set the door state: DoorState is missing in command")); + + VerifyOrExit( + params["DoorState"].isUInt(), + ChipLogError(NotSpecified, "Lock App: Unable to execute command to set the door state: invalid type for DoorState")); + + auto doorState = params["DoorState"].asUInt(); + VerifyOrExit(doorState < to_underlying(DlDoorState::kUnknownEnumValue), + ChipLogError(NotSpecified, + "Lock App: Unable to execute command to set door state: DoorState is out of range [doorState=%u]", + doorState)); + LockManager::Instance().SetDoorState(endpointId, static_cast(doorState)); + } + else if (self->mCommandName == "SendDoorLockAlarm") + { + VerifyOrExit( + params.isMember("AlarmCode"), + ChipLogError(NotSpecified, "Lock App: Unable to execute command to send lock alarm: AlarmCode is missing in command")); + + VerifyOrExit( + params["AlarmCode"].isUInt(), + ChipLogError(NotSpecified, "Lock App: Unable to execute command to send lock alarm: invalid type for AlarmCode")); + + auto alarmCode = params["AlarmCode"].asUInt(); + VerifyOrExit( + alarmCode < to_underlying(DlAlarmCode::kUnknownEnumValue), + ChipLogError(NotSpecified, + "Lock App: Unable to execute command to send lock alarm: AlarmCode is out of range [alarmCode=%u]", + alarmCode)); + LockManager::Instance().SendLockAlarm(endpointId, static_cast(alarmCode)); + } + else + { + ChipLogError(NotSpecified, "Lock App: Unable to execute command \"%s\": command not supported", self->mCommandName.c_str()); + } + +exit: + chip::Platform::Delete(self); +} + +void LockAppCommandDelegate::OnEventCommandReceived(const char * json) +{ + auto handler = LockAppCommandHandler::FromJSON(json); + if (nullptr == handler) + { + ChipLogError(NotSpecified, "Lock App: Unable to instantiate a command handler"); + return; + } + + chip::DeviceLayer::PlatformMgr().ScheduleWork(LockAppCommandHandler::HandleCommand, reinterpret_cast(handler)); +} diff --git a/examples/lock-app/linux/src/LockEndpoint.cpp b/examples/lock-app/linux/src/LockEndpoint.cpp index ebd6f3fca1e9f3..396eab9cdbd893 100644 --- a/examples/lock-app/linux/src/LockEndpoint.cpp +++ b/examples/lock-app/linux/src/LockEndpoint.cpp @@ -151,10 +151,10 @@ bool LockEndpoint::SetDoorState(DlDoorState newState) return true; } -bool LockEndpoint::SendLockJammedAlarm() const +bool LockEndpoint::SendLockAlarm(DlAlarmCode alarmCode) const { - ChipLogProgress(Zcl, "Changing the Lock Jammed event [endpointId=%d]", mEndpointId); - return DoorLockServer::Instance().SendLockAlarmEvent(mEndpointId, DlAlarmCode::kLockJammed); + ChipLogProgress(Zcl, "Sending the LockAlarm event [endpointId=%d,alarmCode=%u]", mEndpointId, to_underlying(alarmCode)); + return DoorLockServer::Instance().SendLockAlarmEvent(mEndpointId, alarmCode); } bool LockEndpoint::GetCredential(uint16_t credentialIndex, DlCredentialType credentialType, diff --git a/examples/lock-app/linux/src/LockManager.cpp b/examples/lock-app/linux/src/LockManager.cpp index b2a6098dd51ac8..6531be018df53a 100644 --- a/examples/lock-app/linux/src/LockManager.cpp +++ b/examples/lock-app/linux/src/LockManager.cpp @@ -18,7 +18,6 @@ #include "LockManager.h" -#include #include #include @@ -112,7 +111,7 @@ bool LockManager::InitEndpoint(chip::EndpointId endpointId) return true; } -bool LockManager::ToggleDoorState(chip::EndpointId endpointId) +bool LockManager::SetDoorState(chip::EndpointId endpointId, DlDoorState doorState) { auto lockEndpoint = getEndpoint(endpointId); if (nullptr == lockEndpoint) @@ -121,18 +120,10 @@ bool LockManager::ToggleDoorState(chip::EndpointId endpointId) endpointId); return false; } - - auto currentState = lockEndpoint->GetDoorState(); - if (currentState == DlDoorState::kDoorOpen) - { - return lockEndpoint->SetDoorState(DlDoorState::kDoorClosed); - } - // We assume that toggling the door state from any non-open state means completely opening it. - // The reason is that other states are optional for DPS feature and out of scope of this example. - return lockEndpoint->SetDoorState(DlDoorState::kDoorOpen); + return lockEndpoint->SetDoorState(doorState); } -bool LockManager::SendLockJammedAlarm(chip::EndpointId endpointId) +bool LockManager::SendLockAlarm(chip::EndpointId endpointId, DlAlarmCode alarmCode) { auto lockEndpoint = getEndpoint(endpointId); if (nullptr == lockEndpoint) @@ -140,7 +131,7 @@ bool LockManager::SendLockJammedAlarm(chip::EndpointId endpointId) ChipLogError(Zcl, "Unable to send lock alarm - endpoint does not exist or not initialized [endpointId=%d]", endpointId); return false; } - return lockEndpoint->SendLockJammedAlarm(); + return lockEndpoint->SendLockAlarm(alarmCode); } bool LockManager::Lock(chip::EndpointId endpointId, const Optional & pin, DlOperationError & err)