| /** |
| * |
| * 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. |
| * |
| */ |
| #include "dishwasher-alarm-server.h" |
| |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/cluster-enums.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app/EventLogging.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/util/attribute-storage.h> |
| #include <app/util/config.h> |
| #include <lib/support/BitFlags.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::DishwasherAlarm; |
| using namespace chip::app::Clusters::DishwasherAlarm::Attributes; |
| using namespace chip::DeviceLayer; |
| using chip::Protocols::InteractionModel::Status; |
| |
| static constexpr size_t kDishwasherAlarmDelegateTableSize = |
| MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; |
| |
| static_assert(kDishwasherAlarmDelegateTableSize <= kEmberInvalidEndpointIndex, "Dishwasher Alarm Delegate table size error"); |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace DishwasherAlarm { |
| |
| Delegate * gDelegateTable[kDishwasherAlarmDelegateTableSize] = { nullptr }; |
| |
| Delegate * GetDelegate(EndpointId endpoint) |
| { |
| uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DishwasherAlarm::Id, |
| MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT); |
| return (ep >= kDishwasherAlarmDelegateTableSize ? nullptr : gDelegateTable[ep]); |
| } |
| |
| void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) |
| { |
| uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DishwasherAlarm::Id, |
| MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT); |
| // if endpoint is found |
| if (ep < kDishwasherAlarmDelegateTableSize) |
| { |
| gDelegateTable[ep] = delegate; |
| } |
| } |
| |
| } // namespace DishwasherAlarm |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |
| |
| DishwasherAlarmServer DishwasherAlarmServer::instance; |
| |
| DishwasherAlarmServer & DishwasherAlarmServer::Instance() |
| { |
| return instance; |
| } |
| |
| Status DishwasherAlarmServer::GetMaskValue(EndpointId endpoint, BitMask<AlarmMap> * mask) |
| { |
| Status status = Attributes::Mask::Get(endpoint, mask); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: reading mask, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::GetLatchValue(EndpointId endpoint, BitMask<AlarmMap> * latch) |
| { |
| if (!HasResetFeature(endpoint)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm feature: Unsupport Latch attribute"); |
| return Status::UnsupportedAttribute; |
| } |
| |
| Status status = Attributes::Latch::Get(endpoint, latch); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: reading latch, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::GetStateValue(EndpointId endpoint, BitMask<AlarmMap> * state) |
| { |
| Status status = Attributes::State::Get(endpoint, state); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: get state, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::GetSupportedValue(EndpointId endpoint, BitMask<AlarmMap> * supported) |
| { |
| Status status = Attributes::Supported::Get(endpoint, supported); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: reading supported, err:0x%x", to_underlying(status)); |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::SetSupportedValue(EndpointId endpoint, const BitMask<AlarmMap> supported) |
| { |
| Status status = Status::Success; |
| |
| if ((status = Attributes::Supported::Set(endpoint, supported)) != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: writing supported, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| |
| // Whenever there is change in Supported attribute, Latch should change accordingly (if possible). |
| BitMask<AlarmMap> latch; |
| if (GetLatchValue(endpoint, &latch) == Status::Success && !supported.HasAll(latch)) |
| { |
| latch = latch & supported; |
| status = SetLatchValue(endpoint, latch); |
| } |
| |
| // Whenever there is change in Supported attribute, Mask, State should change accordingly. |
| BitMask<AlarmMap> mask; |
| if ((status = GetMaskValue(endpoint, &mask)) != Status::Success) |
| { |
| return status; |
| } |
| |
| if (!supported.HasAll(mask)) |
| { |
| mask = supported & mask; |
| status = SetMaskValue(endpoint, mask); |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::SetMaskValue(EndpointId endpoint, const BitMask<AlarmMap> mask) |
| { |
| BitMask<AlarmMap> supported; |
| if (Status::Success != GetSupportedValue(endpoint, &supported) || !supported.HasAll(mask)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: Mask is not supported"); |
| return Status::Failure; |
| } |
| |
| Status status = Status::Success; |
| if ((status = Attributes::Mask::Set(endpoint, mask)) != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: writing mask, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| |
| // Whenever there is change in Mask, State should change accordingly. |
| BitMask<AlarmMap> state; |
| status = GetStateValue(endpoint, &state); |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| if (!mask.HasAll(state)) |
| { |
| state = mask & state; |
| status = SetStateValue(endpoint, state, true); |
| } |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::SetLatchValue(EndpointId endpoint, const BitMask<AlarmMap> latch) |
| { |
| if (!HasResetFeature(endpoint)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm feature: Unsupport Latch attribute"); |
| return Status::UnsupportedAttribute; |
| } |
| |
| BitMask<AlarmMap> supported; |
| if (Status::Success != GetSupportedValue(endpoint, &supported) || !supported.HasAll(latch)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: Latch is not supported"); |
| return Status::Failure; |
| } |
| |
| Status status = Attributes::Latch::Set(endpoint, latch); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: writing latch, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::SetStateValue(EndpointId endpoint, const BitMask<AlarmMap> newState, bool ignoreLatchState) |
| { |
| BitMask<AlarmMap> supported; |
| BitMask<AlarmMap> finalNewState; |
| finalNewState.Set(newState); |
| |
| if (Status::Success != GetSupportedValue(endpoint, &supported) || !supported.HasAll(finalNewState)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: Alarm is not supported"); |
| return Status::Failure; |
| } |
| |
| BitMask<AlarmMap> mask; |
| if (Status::Success != GetMaskValue(endpoint, &mask) || !mask.HasAll(finalNewState)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: Alarm is suppressed"); |
| return Status::Failure; |
| } |
| |
| Status status = Status::Success; |
| BitMask<AlarmMap> currentState; |
| status = Attributes::State::Get(endpoint, ¤tState); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: reading state, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| |
| BitMask<AlarmMap> latch; |
| if (!ignoreLatchState && (GetLatchValue(endpoint, &latch) == Status::Success)) |
| { |
| // Restore bits that have their Latch bit set. |
| auto bitsToKeep = latch & currentState; |
| finalNewState.Set(bitsToKeep); |
| } |
| |
| // Store the new value of the State attribute. |
| status = Attributes::State::Set(endpoint, finalNewState); |
| if (status != Status::Success) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: writing state, err:0x%x", to_underlying(status)); |
| return status; |
| } |
| |
| // Generate Notify event. |
| BitMask<AlarmMap> becameActive; |
| becameActive.Set(finalNewState).Clear(currentState); |
| BitMask<AlarmMap> becameInactive; |
| becameInactive.Set(currentState).Clear(finalNewState); |
| |
| SendNotifyEvent(endpoint, becameActive, becameInactive, finalNewState, mask); |
| return status; |
| } |
| |
| Status DishwasherAlarmServer::ResetLatchedAlarms(EndpointId endpoint, const BitMask<AlarmMap> alarms) |
| { |
| BitMask<AlarmMap> supported; |
| if (Status::Success != GetSupportedValue(endpoint, &supported) || !supported.HasAll(alarms)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm: ERR: Alarm is not supported"); |
| return Status::Failure; |
| } |
| |
| BitMask<AlarmMap> state; |
| if (GetStateValue(endpoint, &state) != Status::Success) |
| { |
| return Status::Failure; |
| } |
| |
| state.Clear(alarms); |
| return SetStateValue(endpoint, state, true); |
| } |
| |
| bool DishwasherAlarmServer::HasResetFeature(EndpointId endpoint) |
| { |
| uint32_t featureMap = 0; |
| if (Attributes::FeatureMap::Get(endpoint, &featureMap) != Status::Success) |
| { |
| return false; |
| } |
| |
| if (featureMap & to_underlying(Feature::kReset)) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| void DishwasherAlarmServer::SendNotifyEvent(EndpointId endpointId, BitMask<AlarmMap> becameActive, BitMask<AlarmMap> becameInactive, |
| BitMask<AlarmMap> newState, BitMask<AlarmMap> mask) |
| { |
| Events::Notify::Type event{ .active = becameActive, .inactive = becameInactive, .state = newState, .mask = mask }; |
| EventNumber eventNumber; |
| CHIP_ERROR error = LogEvent(event, endpointId, eventNumber); |
| if (CHIP_NO_ERROR != error) |
| { |
| ChipLogError(Zcl, "[Notify] Unable to send notify event: %s [endpointId=%d]", error.AsString(), endpointId); |
| } |
| } |
| |
| static Status ModifyEnabledHandler(const app::ConcreteCommandPath & commandPath, const BitMask<AlarmMap> mask) |
| { |
| EndpointId endpoint = commandPath.mEndpointId; |
| BitMask<AlarmMap> supported; |
| |
| if (DishwasherAlarmServer::Instance().GetSupportedValue(endpoint, &supported) != Status::Success) |
| { |
| return Status::Failure; |
| } |
| |
| // receives this command with a Mask that includes bits that are set for alarms which are not supported |
| if (!supported.HasAll(mask)) |
| { |
| return Status::InvalidCommand; |
| } |
| |
| // A server that is unable to enable a currently suppressed alarm, |
| // or is unable to suppress a currently enabled alarm SHALL respond |
| // with a status code of FAILURE |
| Delegate * delegate = DishwasherAlarm::GetDelegate(endpoint); |
| if (delegate && !(delegate->ModifyEnabledAlarmsCallback(mask))) |
| { |
| ChipLogProgress(Zcl, "Unable to modify enabled alarms"); |
| return Status::Failure; |
| } |
| // The cluster will do this update if delegate.ModifyEnabledAlarmsCallback() returns true. |
| if (DishwasherAlarmServer::Instance().SetMaskValue(endpoint, mask) != Status::Success) |
| { |
| return Status::Failure; |
| } |
| return Status::Success; |
| } |
| |
| static Status ResetHandler(const app::ConcreteCommandPath & commandPath, const BitMask<AlarmMap> alarms) |
| { |
| EndpointId endpoint = commandPath.mEndpointId; |
| |
| if (!DishwasherAlarmServer::Instance().HasResetFeature(endpoint)) |
| { |
| ChipLogProgress(Zcl, "Dishwasher Alarm feature: Unsupport Reset Command"); |
| return Status::UnsupportedCommand; |
| } |
| |
| // A server that is unable to reset alarms SHALL respond with a status code of FAILURE |
| Delegate * delegate = DishwasherAlarm::GetDelegate(endpoint); |
| if (delegate && !(delegate->ResetAlarmsCallback(alarms))) |
| { |
| ChipLogProgress(Zcl, "Unable to reset alarms"); |
| return Status::Failure; |
| } |
| |
| // The cluster will do this update if delegate.ResetAlarmsCallback() returns true. |
| if (DishwasherAlarmServer::Instance().ResetLatchedAlarms(endpoint, alarms) != Status::Success) |
| { |
| ChipLogProgress(Zcl, "reset alarms fail"); |
| return Status::Failure; |
| } |
| return Status::Success; |
| } |
| |
| bool emberAfDishwasherAlarmClusterResetCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, |
| const Commands::Reset::DecodableType & commandData) |
| { |
| auto & alarms = commandData.alarms; |
| |
| Status status = ResetHandler(commandPath, alarms); |
| commandObj->AddStatus(commandPath, status); |
| |
| return true; |
| } |
| |
| bool emberAfDishwasherAlarmClusterModifyEnabledAlarmsCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::ModifyEnabledAlarms::DecodableType & commandData) |
| { |
| auto & mask = commandData.mask; |
| Status status = ModifyEnabledHandler(commandPath, mask); |
| commandObj->AddStatus(commandPath, status); |
| |
| return true; |
| } |