| /** |
| * |
| * Copyright (c) 2023 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 |
| * Routines for the Smoke CO Alarm Server plugin. |
| * |
| */ |
| |
| #include "smoke-co-alarm-server.h" |
| |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app/EventLogging.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters::SmokeCoAlarm; |
| using namespace chip::app::Clusters::SmokeCoAlarm::Attributes; |
| using chip::Protocols::InteractionModel::Status; |
| |
| SmokeCoAlarmServer SmokeCoAlarmServer::sInstance; |
| |
| /********************************************************** |
| * SmokeCoAlarmServer public methods |
| *********************************************************/ |
| |
| SmokeCoAlarmServer & SmokeCoAlarmServer::Instance() |
| { |
| return sInstance; |
| } |
| |
| void SmokeCoAlarmServer::SetExpressedStateByPriority(EndpointId endpointId, |
| const std::array<ExpressedStateEnum, kPriorityOrderLength> & priorityOrder) |
| { |
| for (ExpressedStateEnum priority : priorityOrder) |
| { |
| AlarmStateEnum alarmState = AlarmStateEnum::kNormal; |
| EndOfServiceEnum endOfServiceState = EndOfServiceEnum::kNormal; |
| bool active = false; |
| bool success = false; |
| |
| switch (priority) |
| { |
| case ExpressedStateEnum::kSmokeAlarm: |
| success = GetSmokeState(endpointId, alarmState); |
| break; |
| case ExpressedStateEnum::kCOAlarm: |
| success = GetCOState(endpointId, alarmState); |
| break; |
| case ExpressedStateEnum::kBatteryAlert: |
| success = GetBatteryAlert(endpointId, alarmState); |
| break; |
| case ExpressedStateEnum::kTesting: |
| success = GetTestInProgress(endpointId, active); |
| break; |
| case ExpressedStateEnum::kHardwareFault: |
| success = GetHardwareFaultAlert(endpointId, active); |
| break; |
| case ExpressedStateEnum::kEndOfService: |
| success = GetEndOfServiceAlert(endpointId, endOfServiceState); |
| break; |
| case ExpressedStateEnum::kInterconnectSmoke: |
| success = GetInterconnectSmokeAlarm(endpointId, alarmState); |
| break; |
| case ExpressedStateEnum::kInterconnectCO: |
| success = GetInterconnectCOAlarm(endpointId, alarmState); |
| break; |
| default: |
| break; |
| } |
| |
| if (success && ((alarmState != AlarmStateEnum::kNormal) || (endOfServiceState != EndOfServiceEnum::kNormal) || active)) |
| { |
| SetExpressedState(endpointId, priority); |
| return; |
| } |
| } |
| |
| SetExpressedState(endpointId, ExpressedStateEnum::kNormal); |
| } |
| |
| bool SmokeCoAlarmServer::RequestSelfTest(EndpointId endpointId) |
| { |
| ExpressedStateEnum expressedState; |
| VerifyOrReturnValue(GetExpressedState(endpointId, expressedState), false); |
| |
| // If the value is busy then return busy |
| if (expressedState == ExpressedStateEnum::kSmokeAlarm || expressedState == ExpressedStateEnum::kCOAlarm || |
| expressedState == ExpressedStateEnum::kTesting || expressedState == ExpressedStateEnum::kInterconnectSmoke || |
| expressedState == ExpressedStateEnum::kInterconnectCO) |
| { |
| return false; |
| } |
| |
| VerifyOrReturnValue(SetTestInProgress(endpointId, true), false); |
| SetExpressedState(endpointId, ExpressedStateEnum::kTesting); |
| |
| emberAfPluginSmokeCoAlarmSelfTestRequestCommand(endpointId); |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetSmokeState(EndpointId endpointId, AlarmStateEnum newSmokeState) |
| { |
| AlarmStateEnum alarmState; |
| VerifyOrReturnValue(GetAttribute(endpointId, SmokeState::Id, SmokeState::Get, alarmState), false); |
| VerifyOrReturnValue(alarmState != newSmokeState, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, SmokeState::Id, SmokeState::Set, newSmokeState), false); |
| if (newSmokeState == AlarmStateEnum::kWarning || newSmokeState == AlarmStateEnum::kCritical) |
| { |
| Events::SmokeAlarm::Type event{ newSmokeState }; |
| SendEvent(endpointId, event); |
| } |
| |
| if (newSmokeState == AlarmStateEnum::kCritical) |
| { |
| SetDeviceMuted(endpointId, MuteStateEnum::kNotMuted); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetCOState(EndpointId endpointId, AlarmStateEnum newCOState) |
| { |
| AlarmStateEnum alarmState; |
| VerifyOrReturnValue(GetAttribute(endpointId, COState::Id, COState::Get, alarmState), false); |
| VerifyOrReturnValue(alarmState != newCOState, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, COState::Id, COState::Set, newCOState), false); |
| if (newCOState == AlarmStateEnum::kWarning || newCOState == AlarmStateEnum::kCritical) |
| { |
| Events::COAlarm::Type event{ newCOState }; |
| SendEvent(endpointId, event); |
| } |
| |
| if (newCOState == AlarmStateEnum::kCritical) |
| { |
| SetDeviceMuted(endpointId, MuteStateEnum::kNotMuted); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetBatteryAlert(EndpointId endpointId, AlarmStateEnum newBatteryAlert) |
| { |
| AlarmStateEnum alarmState; |
| VerifyOrReturnValue(GetAttribute(endpointId, BatteryAlert::Id, BatteryAlert::Get, alarmState), false); |
| VerifyOrReturnValue(alarmState != newBatteryAlert, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, BatteryAlert::Id, BatteryAlert::Set, newBatteryAlert), false); |
| if (newBatteryAlert == AlarmStateEnum::kWarning || newBatteryAlert == AlarmStateEnum::kCritical) |
| { |
| Events::LowBattery::Type event{ newBatteryAlert }; |
| SendEvent(endpointId, event); |
| } |
| |
| if (newBatteryAlert == AlarmStateEnum::kCritical) |
| { |
| SetDeviceMuted(endpointId, MuteStateEnum::kNotMuted); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetDeviceMuted(EndpointId endpointId, MuteStateEnum newDeviceMuted) |
| { |
| MuteStateEnum deviceMuted; |
| VerifyOrReturnValue(GetAttribute(endpointId, DeviceMuted::Id, DeviceMuted::Get, deviceMuted), false); |
| VerifyOrReturnValue(deviceMuted != newDeviceMuted, true); |
| |
| if (newDeviceMuted == MuteStateEnum::kMuted) |
| { |
| AlarmStateEnum alarmState; |
| |
| // If the attribute has been read and the attribute is Critical, return false |
| |
| bool success = GetSmokeState(endpointId, alarmState); |
| VerifyOrReturnValue(!success || alarmState != AlarmStateEnum::kCritical, false); |
| |
| success = GetCOState(endpointId, alarmState); |
| VerifyOrReturnValue(!success || alarmState != AlarmStateEnum::kCritical, false); |
| |
| success = GetBatteryAlert(endpointId, alarmState); |
| VerifyOrReturnValue(!success || alarmState != AlarmStateEnum::kCritical, false); |
| |
| success = GetInterconnectSmokeAlarm(endpointId, alarmState); |
| VerifyOrReturnValue(!success || alarmState != AlarmStateEnum::kCritical, false); |
| |
| success = GetInterconnectCOAlarm(endpointId, alarmState); |
| VerifyOrReturnValue(!success || alarmState != AlarmStateEnum::kCritical, false); |
| } |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, DeviceMuted::Id, DeviceMuted::Set, newDeviceMuted), false); |
| if (newDeviceMuted == MuteStateEnum::kMuted) |
| { |
| Events::AlarmMuted::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| else if (newDeviceMuted == MuteStateEnum::kNotMuted) |
| { |
| Events::MuteEnded::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetTestInProgress(EndpointId endpointId, bool newTestInProgress) |
| { |
| bool active; |
| VerifyOrReturnValue(GetAttribute(endpointId, TestInProgress::Id, TestInProgress::Get, active), false); |
| VerifyOrReturnValue(active != newTestInProgress, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, TestInProgress::Id, TestInProgress::Set, newTestInProgress), false); |
| if (!newTestInProgress) |
| { |
| Events::SelfTestComplete::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetHardwareFaultAlert(EndpointId endpointId, bool newHardwareFaultAlert) |
| { |
| bool active; |
| VerifyOrReturnValue(GetAttribute(endpointId, HardwareFaultAlert::Id, HardwareFaultAlert::Get, active), false); |
| VerifyOrReturnValue(active != newHardwareFaultAlert, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, HardwareFaultAlert::Id, HardwareFaultAlert::Set, newHardwareFaultAlert), false); |
| if (newHardwareFaultAlert) |
| { |
| Events::HardwareFault::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetEndOfServiceAlert(EndpointId endpointId, EndOfServiceEnum newEndOfServiceAlert) |
| { |
| EndOfServiceEnum endOfServiceState; |
| VerifyOrReturnValue(GetAttribute(endpointId, EndOfServiceAlert::Id, EndOfServiceAlert::Get, endOfServiceState), false); |
| VerifyOrReturnValue(endOfServiceState != newEndOfServiceAlert, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, EndOfServiceAlert::Id, EndOfServiceAlert::Set, newEndOfServiceAlert), false); |
| if (newEndOfServiceAlert == EndOfServiceEnum::kExpired) |
| { |
| Events::EndOfService::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetInterconnectSmokeAlarm(EndpointId endpointId, AlarmStateEnum newInterconnectSmokeAlarm) |
| { |
| AlarmStateEnum alarmState; |
| VerifyOrReturnValue(GetAttribute(endpointId, InterconnectSmokeAlarm::Id, InterconnectSmokeAlarm::Get, alarmState), false); |
| VerifyOrReturnValue(alarmState != newInterconnectSmokeAlarm, true); |
| |
| VerifyOrReturnValue( |
| SetAttribute(endpointId, InterconnectSmokeAlarm::Id, InterconnectSmokeAlarm::Set, newInterconnectSmokeAlarm), false); |
| if (newInterconnectSmokeAlarm == AlarmStateEnum::kWarning || newInterconnectSmokeAlarm == AlarmStateEnum::kCritical) |
| { |
| Events::InterconnectSmokeAlarm::Type event{ newInterconnectSmokeAlarm }; |
| SendEvent(endpointId, event); |
| } |
| |
| if (newInterconnectSmokeAlarm == AlarmStateEnum::kCritical) |
| { |
| SetDeviceMuted(endpointId, MuteStateEnum::kNotMuted); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetInterconnectCOAlarm(EndpointId endpointId, AlarmStateEnum newInterconnectCOAlarm) |
| { |
| AlarmStateEnum alarmState; |
| VerifyOrReturnValue(GetAttribute(endpointId, InterconnectCOAlarm::Id, InterconnectCOAlarm::Get, alarmState), false); |
| VerifyOrReturnValue(alarmState != newInterconnectCOAlarm, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, InterconnectCOAlarm::Id, InterconnectCOAlarm::Set, newInterconnectCOAlarm), false); |
| if (newInterconnectCOAlarm == AlarmStateEnum::kWarning || newInterconnectCOAlarm == AlarmStateEnum::kCritical) |
| { |
| Events::InterconnectCOAlarm::Type event{ newInterconnectCOAlarm }; |
| SendEvent(endpointId, event); |
| } |
| |
| if (newInterconnectCOAlarm == AlarmStateEnum::kCritical) |
| { |
| SetDeviceMuted(endpointId, MuteStateEnum::kNotMuted); |
| } |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetContaminationState(EndpointId endpointId, ContaminationStateEnum newContaminationState) |
| { |
| ContaminationStateEnum contaminationState; |
| VerifyOrReturnValue(GetAttribute(endpointId, ContaminationState::Id, ContaminationState::Get, contaminationState), false); |
| VerifyOrReturnValue(contaminationState != newContaminationState, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, ContaminationState::Id, ContaminationState::Set, newContaminationState), false); |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::SetSmokeSensitivityLevel(EndpointId endpointId, SensitivityEnum newSmokeSensitivityLevel) |
| { |
| SensitivityEnum sensitivity; |
| VerifyOrReturnValue(GetAttribute(endpointId, SmokeSensitivityLevel::Id, SmokeSensitivityLevel::Get, sensitivity), false); |
| VerifyOrReturnValue(sensitivity != newSmokeSensitivityLevel, true); |
| |
| VerifyOrReturnValue(SetAttribute(endpointId, SmokeSensitivityLevel::Id, SmokeSensitivityLevel::Set, newSmokeSensitivityLevel), |
| false); |
| |
| return true; |
| } |
| |
| bool SmokeCoAlarmServer::GetExpressedState(chip ::EndpointId endpointId, ExpressedStateEnum & expressedState) |
| { |
| return GetAttribute(endpointId, ExpressedState::Id, ExpressedState::Get, expressedState); |
| } |
| |
| bool SmokeCoAlarmServer::GetSmokeState(EndpointId endpointId, AlarmStateEnum & smokeState) |
| { |
| return GetAttribute(endpointId, SmokeState::Id, SmokeState::Get, smokeState); |
| } |
| |
| bool SmokeCoAlarmServer::GetCOState(EndpointId endpointId, AlarmStateEnum & coState) |
| { |
| return GetAttribute(endpointId, COState::Id, COState::Get, coState); |
| } |
| |
| bool SmokeCoAlarmServer::GetBatteryAlert(EndpointId endpointId, AlarmStateEnum & batteryAlert) |
| { |
| return GetAttribute(endpointId, BatteryAlert::Id, BatteryAlert::Get, batteryAlert); |
| } |
| |
| bool SmokeCoAlarmServer::GetDeviceMuted(EndpointId endpointId, MuteStateEnum & deviceMuted) |
| { |
| return GetAttribute(endpointId, DeviceMuted::Id, DeviceMuted::Get, deviceMuted); |
| } |
| |
| bool SmokeCoAlarmServer::GetTestInProgress(EndpointId endpointId, bool & testInProgress) |
| { |
| return GetAttribute(endpointId, TestInProgress::Id, TestInProgress::Get, testInProgress); |
| } |
| |
| bool SmokeCoAlarmServer::GetHardwareFaultAlert(EndpointId endpointId, bool & hardwareFaultAlert) |
| { |
| return GetAttribute(endpointId, HardwareFaultAlert::Id, HardwareFaultAlert::Get, hardwareFaultAlert); |
| } |
| |
| bool SmokeCoAlarmServer::GetEndOfServiceAlert(EndpointId endpointId, EndOfServiceEnum & endOfServiceAlert) |
| { |
| return GetAttribute(endpointId, EndOfServiceAlert::Id, EndOfServiceAlert::Get, endOfServiceAlert); |
| } |
| |
| bool SmokeCoAlarmServer::GetInterconnectSmokeAlarm(EndpointId endpointId, AlarmStateEnum & interconnectSmokeAlarm) |
| { |
| return GetAttribute(endpointId, InterconnectSmokeAlarm::Id, InterconnectSmokeAlarm::Get, interconnectSmokeAlarm); |
| } |
| |
| bool SmokeCoAlarmServer::GetInterconnectCOAlarm(EndpointId endpointId, AlarmStateEnum & interconnectCOAlarm) |
| { |
| return GetAttribute(endpointId, InterconnectCOAlarm::Id, InterconnectCOAlarm::Get, interconnectCOAlarm); |
| } |
| |
| bool SmokeCoAlarmServer::GetContaminationState(EndpointId endpointId, ContaminationStateEnum & contaminationState) |
| { |
| return GetAttribute(endpointId, ContaminationState::Id, ContaminationState::Get, contaminationState); |
| } |
| |
| bool SmokeCoAlarmServer::GetSmokeSensitivityLevel(EndpointId endpointId, SensitivityEnum & smokeSensitivityLevel) |
| { |
| return GetAttribute(endpointId, SmokeSensitivityLevel::Id, SmokeSensitivityLevel::Get, smokeSensitivityLevel); |
| } |
| |
| bool SmokeCoAlarmServer::GetExpiryDate(EndpointId endpointId, uint32_t & expiryDate) |
| { |
| return GetAttribute(endpointId, ExpiryDate::Id, ExpiryDate::Get, expiryDate); |
| } |
| |
| chip::BitFlags<Feature> SmokeCoAlarmServer::GetFeatures(EndpointId endpointId) |
| { |
| chip::BitFlags<Feature> featureMap; |
| if (!GetAttribute(endpointId, FeatureMap::Id, FeatureMap::Get, *featureMap.RawStorage())) |
| { |
| ChipLogError(Zcl, "Unable to get the Smoke CO Alarm feature map: attribute read error"); |
| featureMap.ClearAll(); |
| } |
| return featureMap; |
| } |
| |
| /********************************************************** |
| * SmokeCoAlarmServer private methods |
| *********************************************************/ |
| |
| void SmokeCoAlarmServer::SetExpressedState(EndpointId endpointId, ExpressedStateEnum newExpressedState) |
| { |
| ExpressedStateEnum expressedState; |
| VerifyOrReturn(GetAttribute(endpointId, ExpressedState::Id, ExpressedState::Get, expressedState)); |
| VerifyOrReturn(expressedState != newExpressedState); |
| |
| VerifyOrReturn(SetAttribute(endpointId, ExpressedState::Id, ExpressedState::Set, newExpressedState)); |
| if (newExpressedState == ExpressedStateEnum::kNormal) |
| { |
| Events::AllClear::Type event{}; |
| SendEvent(endpointId, event); |
| } |
| } |
| |
| void SmokeCoAlarmServer::HandleRemoteSelfTestRequest(CommandHandler * commandObj, const ConcreteCommandPath & commandPath) |
| { |
| EndpointId endpointId = commandPath.mEndpointId; |
| |
| ExpressedStateEnum expressedState; |
| VerifyOrReturn(GetExpressedState(endpointId, expressedState), commandObj->AddStatus(commandPath, Status::Failure)); |
| |
| // If the value is busy then return busy |
| if (expressedState == ExpressedStateEnum::kSmokeAlarm || expressedState == ExpressedStateEnum::kCOAlarm || |
| expressedState == ExpressedStateEnum::kTesting || expressedState == ExpressedStateEnum::kInterconnectSmoke || |
| expressedState == ExpressedStateEnum::kInterconnectCO) |
| { |
| commandObj->AddStatus(commandPath, Status::Busy); |
| return; |
| } |
| |
| VerifyOrReturn(SetTestInProgress(endpointId, true), commandObj->AddStatus(commandPath, Status::Failure)); |
| SetExpressedState(endpointId, ExpressedStateEnum::kTesting); |
| |
| emberAfPluginSmokeCoAlarmSelfTestRequestCommand(endpointId); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| template <typename T> |
| void SmokeCoAlarmServer::SendEvent(EndpointId endpointId, T & event) |
| { |
| EventNumber eventNumber; |
| auto err = LogEvent(event, endpointId, eventNumber); |
| |
| if (CHIP_NO_ERROR != err) |
| { |
| ChipLogError(Zcl, "Failed to log event: err=%" CHIP_ERROR_FORMAT ", event_id=" ChipLogFormatMEI, err.Format(), |
| ChipLogValueMEI(event.GetEventId())); |
| } |
| } |
| |
| template <typename T> |
| bool SmokeCoAlarmServer::GetAttribute(EndpointId endpointId, AttributeId attributeId, |
| Status (*getFn)(EndpointId endpointId, T * value), T & value) const |
| { |
| Status status = getFn(endpointId, &value); |
| bool success = (Status::Success == status); |
| bool unsupportedStatus = (Status::UnsupportedAttribute == status); |
| |
| if (unsupportedStatus) |
| { |
| ChipLogProgress(Zcl, "Read unsupported SmokeCOAlarm attribute: attribute=" ChipLogFormatMEI, ChipLogValueMEI(attributeId)); |
| } |
| else if (!success) |
| { |
| ChipLogError(Zcl, "Failed to read SmokeCOAlarm attribute: attribute=" ChipLogFormatMEI ", status=0x%x", |
| ChipLogValueMEI(attributeId), to_underlying(status)); |
| } |
| return success; |
| } |
| |
| template <typename T> |
| bool SmokeCoAlarmServer::SetAttribute(EndpointId endpointId, AttributeId attributeId, |
| Status (*setFn)(EndpointId endpointId, T value), T value) |
| { |
| Status status = setFn(endpointId, value); |
| bool success = (Status::Success == status); |
| |
| if (!success) |
| { |
| ChipLogError(Zcl, "Failed to write SmokeCOAlarm attribute: attribute=" ChipLogFormatMEI ", status=0x%x", |
| ChipLogValueMEI(attributeId), to_underlying(status)); |
| } |
| return success; |
| } |
| |
| // ============================================================================= |
| // Cluster commands callbacks |
| // ============================================================================= |
| |
| bool emberAfSmokeCoAlarmClusterSelfTestRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, |
| const Commands::SelfTestRequest::DecodableType & commandData) |
| { |
| SmokeCoAlarmServer::Instance().HandleRemoteSelfTestRequest(commandObj, commandPath); |
| return true; |
| } |
| |
| void MatterSmokeCoAlarmPluginServerInitCallback() {} |