/** * * 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 #include #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::PumpConfigurationAndControl; using chip::Protocols::InteractionModel::Status; namespace chip { namespace app { namespace Clusters { namespace PumpConfigurationAndControl { // Enum for RemoteSensorType enum class RemoteSensorType : uint8_t { kNoSensor = 0x00, kPressureSensor = 0x01, kFlowSensor = 0x02, kTemperatureSensor = 0x03, }; static RemoteSensorType detectRemoteSensorConnected() { // TODO: Detect the sensor types attached to the pump control cluster // this could be pressure, flow or temperature sensors. return RemoteSensorType::kNoSensor; } static void setEffectiveModes(EndpointId endpoint) { ControlModeEnum controlMode; OperationModeEnum operationMode; BitMask pumpStatus; bool isControlModeAvailable = true; bool isPumpStatusAvailable = true; isControlModeAvailable = emberAfContainsAttribute(endpoint, PumpConfigurationAndControl::Id, Attributes::ControlMode::Id); isPumpStatusAvailable = emberAfContainsAttribute(endpoint, PumpConfigurationAndControl::Id, Attributes::PumpStatus::Id); // Get the current control- and operation modes Attributes::OperationMode::Get(endpoint, &operationMode); if (isControlModeAvailable) { Attributes::ControlMode::Get(endpoint, &controlMode); } else { // If controlMode attribute is not available, then use the default value // of the effectiveControlMode attribute as the effectiveControlMode // if this is not suitable, the application should override this value in // the post attribute change callback for the operation mode attribute const EmberAfAttributeMetadata * effectiveControlModeMetaData; effectiveControlModeMetaData = emberAfLocateAttributeMetadata(endpoint, PumpConfigurationAndControl::Id, Attributes::EffectiveControlMode::Id); controlMode = static_cast(effectiveControlModeMetaData->defaultValue.defaultValue); } if (isPumpStatusAvailable) { Attributes::PumpStatus::Get(endpoint, &pumpStatus); } switch (operationMode) { case OperationModeEnum::kNormal: { // The pump runs in the control mode as per the type of the remote sensor // If the remote sensor is a Flow sensor the mode would be ConstantFlow // If the remote sensor is a Pressure sensor the mode would be ConstantPressure (not ProportionalPressure) // If the remote sensor is a Temperature sensor the mode would be ConstantTemperature // If a remote sensor is detected and the OperationMode is kNormal, then the pump is operating in the // control mode indicated by the repective remote senor type RemoteSensorType sensorType = detectRemoteSensorConnected(); switch (sensorType) { case RemoteSensorType::kFlowSensor: Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantFlow); if (isPumpStatusAvailable) { pumpStatus.Set(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } break; case RemoteSensorType::kPressureSensor: Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantPressure); if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Set(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } break; case RemoteSensorType::kTemperatureSensor: Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantTemperature); if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Set(PumpStatusBitmap::kRemoteTemperature); } break; case RemoteSensorType::kNoSensor: // The pump is controlled by a setpoint, as defined by // the ControlMode attribute. (N.B. The setpoint is an internal variable which MAY be // controlled between 0% and 100%, e.g., by means of the Level Control cluster) // The ControlMode can be any of the following: // ConstantSpeed, ConstantPressure, ProportionalPressure, // ConstantFlow, ConstantTemperature or Automatic. The actual ControlMode // which would be the EffectiveControlMode is dependant on the actual // physical pump application running "on-top" of this cluster server. Attributes::EffectiveControlMode::Set(endpoint, controlMode); if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } break; } // Set the overall effective operation mode to Normal Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kNormal); } break; // The pump is controlled by the OperationMode attribute. // Maximum, Minimum or Local case OperationModeEnum::kMaximum: { #ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL uint8_t maxLevel; #endif Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kMaximum); Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantSpeed); #ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL LevelControl::Attributes::MaxLevel::Get(endpoint, &maxLevel); LevelControl::Attributes::CurrentLevel::Set(endpoint, maxLevel); #endif if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } } break; case OperationModeEnum::kMinimum: { #ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL uint8_t minLevel; #endif Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kMinimum); Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantSpeed); #ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL LevelControl::Attributes::MinLevel::Get(endpoint, &minLevel); if (minLevel == 0) { // Bump the minimum level to 1, since the value of 0 means stop minLevel = 1; } LevelControl::Attributes::CurrentLevel::Set(endpoint, minLevel); #endif if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } } break; case OperationModeEnum::kLocal: { // If the Application sets the OperatioMode to kLocal the application "owns" the EffectiveControlMode, which // it also does if the external entity sets the OperationMode to kLocal. So in any case the application // must set the EffectiveControlMode to something which applies to the current ControlMode in the application. // So to keeps things short: the application layer owns the EffetiveControlMode when OperationMode is kLocal. Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kLocal); // Set the current ControlMode for now. Perhaps the application will set the EffectiveControlMode to something else. Attributes::EffectiveControlMode::Set(endpoint, controlMode); // Clear out the remote sensors from the PumpStatus flags. if (isPumpStatusAvailable) { pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow); pumpStatus.Clear(PumpStatusBitmap::kRemotePressure); pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature); } } break; case OperationModeEnum::kUnknownEnumValue: { // Not expected; see check in MatterPumpConfigurationAndControlClusterServerPreAttributeChangedCallback. break; } } if (isPumpStatusAvailable) { Attributes::PumpStatus::Set(endpoint, pumpStatus); } } bool HasFeature(EndpointId endpoint, Feature feature) { bool hasFeature; uint32_t featureMap; hasFeature = (Attributes::FeatureMap::Get(endpoint, &featureMap) == Status::Success); return hasFeature ? ((featureMap & to_underlying(feature)) != 0) : false; } } // namespace PumpConfigurationAndControl } // namespace Clusters } // namespace app } // namespace chip // SDK Callbacks void emberAfPumpConfigurationAndControlClusterServerInitCallback(EndpointId endpoint) { ChipLogProgress(Zcl, "Initialize PCC Server Cluster [EP:%d]", endpoint); } chip::Protocols::InteractionModel::Status MatterPumpConfigurationAndControlClusterServerPreAttributeChangedCallback( const chip::app::ConcreteAttributePath & attributePath, EmberAfAttributeType attributeType, uint16_t size, uint8_t * value) { ChipLogProgress(Zcl, "PCC Server Cluster Attribute Pre-changed [EP:%d, ID:0x%x]", attributePath.mEndpointId, (unsigned int) attributePath.mAttributeId); Protocols::InteractionModel::Status status = Protocols::InteractionModel::Status::Success; switch (attributePath.mAttributeId) { case Attributes::ControlMode::Id: { ControlModeEnum controlMode; NumericAttributeTraits::StorageType tmp; memcpy(&tmp, value, size); controlMode = NumericAttributeTraits::StorageToWorking(tmp); switch (controlMode) { case ControlModeEnum::kConstantFlow: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantFlow)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kConstantPressure: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantPressure)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kConstantSpeed: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kConstantTemperature: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantTemperature)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kProportionalPressure: if (!HasFeature(attributePath.mEndpointId, Feature::kCompensatedPressure)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kAutomatic: if (!HasFeature(attributePath.mEndpointId, Feature::kAutomatic)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case ControlModeEnum::kUnknownEnumValue: status = Protocols::InteractionModel::Status::ConstraintError; break; } } break; case Attributes::OperationMode::Id: OperationModeEnum operationMode; NumericAttributeTraits::StorageType tmp; memcpy(&tmp, value, size); operationMode = NumericAttributeTraits::StorageToWorking(tmp); switch (operationMode) { case OperationModeEnum::kMinimum: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case OperationModeEnum::kMaximum: if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case OperationModeEnum::kLocal: if (!HasFeature(attributePath.mEndpointId, Feature::kLocalOperation)) { status = Protocols::InteractionModel::Status::ConstraintError; } break; case OperationModeEnum::kNormal: status = Protocols::InteractionModel::Status::Success; break; case OperationModeEnum::kUnknownEnumValue: status = Protocols::InteractionModel::Status::ConstraintError; break; } break; default: status = Protocols::InteractionModel::Status::Success; } return status; } void MatterPumpConfigurationAndControlClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath) { ChipLogProgress(Zcl, "PCC Server Cluster Attribute changed [EP:%d, ID:0x%x]", attributePath.mEndpointId, (unsigned int) attributePath.mAttributeId); switch (attributePath.mAttributeId) { case Attributes::ControlMode::Id: case Attributes::OperationMode::Id: setEffectiveModes(attributePath.mEndpointId); break; default: ChipLogProgress(Zcl, "PCC Server: unhandled attribute ID"); } } void MatterPumpConfigurationAndControlPluginServerInitCallback() {}