| /** |
| * |
| * 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 <app/util/af.h> |
| |
| #include <app/util/af-event.h> |
| #include <app/util/attribute-storage.h> |
| |
| #include <app-common/zap-generated/attribute-id.h> |
| #include <app-common/zap-generated/attribute-type.h> |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/callback.h> |
| #include <app-common/zap-generated/cluster-id.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app-common/zap-generated/command-id.h> |
| #include <app-common/zap-generated/enums.h> |
| #include <app-common/zap-generated/ids/Attributes.h> |
| #include <app/CommandHandler.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/ConcreteCommandPath.h> |
| #include <app/util/error-mapping.h> |
| #include <lib/core/CHIPEncoding.h> |
| |
| using namespace chip; |
| using namespace chip::app::Clusters::Thermostat; |
| using namespace chip::app::Clusters::Thermostat::Attributes; |
| |
| constexpr int16_t kDefaultAbsMinHeatSetpointLimit = 700; // 7C (44.5 F) is the default |
| constexpr int16_t kDefaultAbsMaxHeatSetpointLimit = 3000; // 30C (86 F) is the default |
| constexpr int16_t kDefaultMinHeatSetpointLimit = 700; // 7C (44.5 F) is the default |
| constexpr int16_t kDefaultMaxHeatSetpointLimit = 3000; // 30C (86 F) is the default |
| constexpr int16_t kDefaultAbsMinCoolSetpointLimit = 1600; // 16C (61 F) is the default |
| constexpr int16_t kDefaultAbsMaxCoolSetpointLimit = 3200; // 32C (90 F) is the default |
| constexpr int16_t kDefaultMinCoolSetpointLimit = 1600; // 16C (61 F) is the default |
| constexpr int16_t kDefaultMaxCoolSetpointLimit = 3200; // 32C (90 F) is the default |
| constexpr int16_t kDefaultHeatingSetpoint = 2000; |
| constexpr int16_t kDefaultCoolingSetpoint = 2600; |
| constexpr uint8_t kInvalidControlSequenceOfOperation = 0xff; |
| constexpr uint8_t kInvalidRequestedSystemMode = 0xff; |
| constexpr int8_t kDefaultDeadBand = 25; // 2.5C is the default |
| |
| // IMPORTANT NOTE: |
| // No Side effects are permitted in emberAfThermostatClusterServerPreAttributeChangedCallback |
| // If a setpoint changes is required as a result of setpoint limit change |
| // it does not happen here. It is the responsibility of the device to adjust the setpoint(s) |
| // as required in emberAfThermostatClusterServerPostAttributeChangedCallback |
| // limit change validation assures that there is at least 1 setpoint that will be valid |
| |
| #define FEATURE_MAP_HEAT 0x01 |
| #define FEATURE_MAP_COOL 0x02 |
| #define FEATURE_MAP_OCC 0x04 |
| #define FEATURE_MAP_SCH 0x08 |
| #define FEATURE_MAP_SB 0x10 |
| #define FEATURE_MAP_AUTO 0x20 |
| |
| #define FEATURE_MAP_OVERIDE FEATURE_MAP_HEAT | FEATURE_MAP_COOL | FEATURE_MAP_AUTO |
| |
| void emberAfThermostatClusterServerInitCallback() |
| { |
| // TODO |
| // Get from the "real thermostat" |
| // current mode |
| // current occupied heating setpoint |
| // current unoccupied heating setpoint |
| // current occupied cooling setpoint |
| // current unoccupied cooling setpoint |
| // and update the zcl cluster values |
| // This should be a callback defined function |
| // with weak binding so that real thermostat |
| // can get the values. |
| // or should this just be the responsibility of the thermostat application? |
| } |
| |
| using imcode = Protocols::InteractionModel::Status; |
| |
| Protocols::InteractionModel::Status |
| MatterThermostatClusterServerPreAttributeChangedCallback(const app::ConcreteAttributePath & attributePath, |
| EmberAfAttributeType attributeType, uint16_t size, uint8_t * value) |
| { |
| EndpointId endpoint = attributePath.mEndpointId; |
| int16_t requested; |
| |
| // Limits will be needed for all checks |
| // so we just get them all now |
| int16_t AbsMinHeatSetpointLimit; |
| int16_t AbsMaxHeatSetpointLimit; |
| int16_t MinHeatSetpointLimit; |
| int16_t MaxHeatSetpointLimit; |
| int16_t AbsMinCoolSetpointLimit; |
| int16_t AbsMaxCoolSetpointLimit; |
| int16_t MinCoolSetpointLimit; |
| int16_t MaxCoolSetpointLimit; |
| int8_t DeadBand = 0; |
| int16_t DeadBandTemp = 0; |
| uint32_t FeatureMap = 0; |
| bool AutoSupported = false; |
| bool HeatSupported = false; |
| bool CoolSupported = false; |
| bool OccupancySupported = false; |
| int16_t OccupiedCoolingSetpoint; |
| int16_t OccupiedHeatingSetpoint; |
| int16_t UnoccupiedCoolingSetpoint; |
| int16_t UnoccupiedHeatingSetpoint; |
| |
| // TODO re-enable reading reaturemap once https://github.com/project-chip/connectedhomeip/pull/9725 is merged |
| #ifndef FEATURE_MAP_OVERIDE |
| emberAfReadServerAttribute(endpoint, Thermostat::Id, chip::app::Clusters::Globals::Attributes::Ids::FeatureMap, |
| (uint8_t *) &FeatureMap, sizeof(FeatureMap)); |
| #else |
| FeatureMap = FEATURE_MAP_OVERIDE; |
| #endif |
| |
| if (FeatureMap & 1 << 5) // Bit 5 is Auto Mode supported |
| { |
| AutoSupported = true; |
| if (MinSetpointDeadBand::Get(endpoint, &DeadBand) != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| DeadBand = kDefaultDeadBand; |
| } |
| DeadBandTemp = static_cast<int16_t>(DeadBand * 10); |
| } |
| if (FeatureMap & 1 << 0) |
| HeatSupported = true; |
| |
| if (FeatureMap & 1 << 1) |
| CoolSupported = true; |
| |
| if (FeatureMap & 1 << 2) |
| OccupancySupported = true; |
| |
| if (AbsMinCoolSetpointLimit::Get(endpoint, &AbsMinCoolSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| AbsMinCoolSetpointLimit = kDefaultAbsMinCoolSetpointLimit; |
| |
| if (AbsMaxCoolSetpointLimit::Get(endpoint, &AbsMaxCoolSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| AbsMaxCoolSetpointLimit = kDefaultAbsMaxCoolSetpointLimit; |
| |
| if (MinCoolSetpointLimit::Get(endpoint, &MinCoolSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| MinCoolSetpointLimit = AbsMinCoolSetpointLimit; |
| |
| if (MaxCoolSetpointLimit::Get(endpoint, &MaxCoolSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| MaxCoolSetpointLimit = AbsMaxCoolSetpointLimit; |
| |
| if (AbsMinHeatSetpointLimit::Get(endpoint, &AbsMinHeatSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| AbsMinHeatSetpointLimit = kDefaultAbsMinHeatSetpointLimit; |
| |
| if (AbsMaxHeatSetpointLimit::Get(endpoint, &AbsMaxHeatSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| AbsMaxHeatSetpointLimit = kDefaultAbsMaxHeatSetpointLimit; |
| |
| if (MinHeatSetpointLimit::Get(endpoint, &MinHeatSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| MinHeatSetpointLimit = AbsMinHeatSetpointLimit; |
| |
| if (MaxHeatSetpointLimit::Get(endpoint, &MaxHeatSetpointLimit) != EMBER_ZCL_STATUS_SUCCESS) |
| MaxHeatSetpointLimit = AbsMaxHeatSetpointLimit; |
| |
| if (CoolSupported) |
| if (OccupiedCoolingSetpoint::Get(endpoint, &OccupiedCoolingSetpoint) != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: Can not read Occupied Cooling Setpoint"); |
| return imcode::Failure; |
| } |
| |
| if (HeatSupported) |
| if (OccupiedHeatingSetpoint::Get(endpoint, &OccupiedHeatingSetpoint) != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: Can not read Occupied Heating Setpoint"); |
| return imcode::Failure; |
| } |
| |
| if (CoolSupported && OccupancySupported) |
| if (UnoccupiedCoolingSetpoint::Get(endpoint, &UnoccupiedCoolingSetpoint) != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: Can not read Unoccupied Cooling Setpoint"); |
| return imcode::Failure; |
| } |
| |
| if (HeatSupported && OccupancySupported) |
| if (UnoccupiedHeatingSetpoint::Get(endpoint, &UnoccupiedHeatingSetpoint) != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: Can not read Unoccupied Heating Setpoint"); |
| return imcode::Failure; |
| } |
| |
| switch (attributePath.mAttributeId) |
| { |
| case OccupiedHeatingSetpoint::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!HeatSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit || |
| requested > MaxHeatSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested > OccupiedCoolingSetpoint - DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| |
| case OccupiedCoolingSetpoint::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!CoolSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit || |
| requested > MaxCoolSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested < OccupiedHeatingSetpoint + DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| |
| case UnoccupiedHeatingSetpoint::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!(HeatSupported && OccupancySupported)) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit || |
| requested > MaxHeatSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested > UnoccupiedCoolingSetpoint - DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| case UnoccupiedCoolingSetpoint::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!(CoolSupported && OccupancySupported)) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit || |
| requested > MaxCoolSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested < UnoccupiedHeatingSetpoint + DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| |
| case MinHeatSetpointLimit::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!HeatSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinHeatSetpointLimit || requested > MaxHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested > MinCoolSetpointLimit - DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| case MaxHeatSetpointLimit::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!HeatSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested > MaxCoolSetpointLimit - DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| case MinCoolSetpointLimit::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!CoolSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinCoolSetpointLimit || requested > MaxCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested < MinHeatSetpointLimit + DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| case MaxCoolSetpointLimit::Id: { |
| requested = static_cast<int16_t>(chip::Encoding::LittleEndian::Get16(value)); |
| if (!CoolSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit) |
| return imcode::InvalidValue; |
| else if (AutoSupported) |
| { |
| if (requested < MaxHeatSetpointLimit + DeadBandTemp) |
| return imcode::InvalidValue; |
| } |
| return imcode::Success; |
| } |
| case MinSetpointDeadBand::Id: { |
| requested = *value; |
| if (!AutoSupported) |
| return imcode::UnsupportedAttribute; |
| else if (requested < 0 || requested > 25) |
| return imcode::InvalidValue; |
| return imcode::Success; |
| } |
| |
| case ControlSequenceOfOperation::Id: { |
| uint8_t requestedCSO; |
| requestedCSO = *value; |
| if (requestedCSO > EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_COOLING_AND_HEATING_WITH_REHEAT) |
| return imcode::InvalidValue; |
| return imcode::Success; |
| } |
| |
| case SystemMode::Id: { |
| uint8_t ControlSequenceOfOperation = kInvalidControlSequenceOfOperation; |
| uint8_t RequestedSystemMode = kInvalidRequestedSystemMode; |
| ControlSequenceOfOperation::Get(endpoint, &ControlSequenceOfOperation); |
| RequestedSystemMode = *value; |
| if (ControlSequenceOfOperation > EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_COOLING_AND_HEATING_WITH_REHEAT || |
| RequestedSystemMode > EMBER_ZCL_THERMOSTAT_SYSTEM_MODE_FAN_ONLY) |
| { |
| return imcode::InvalidValue; |
| } |
| else |
| { |
| switch (ControlSequenceOfOperation) |
| { |
| case EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_COOLING_ONLY: |
| case EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_COOLING_WITH_REHEAT: |
| if (RequestedSystemMode == EMBER_ZCL_THERMOSTAT_SYSTEM_MODE_HEAT || |
| RequestedSystemMode == EMBER_ZCL_THERMOSTAT_SYSTEM_MODE_EMERGENCY_HEATING) |
| return imcode::InvalidValue; |
| else |
| return imcode::Success; |
| |
| case EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_HEATING_ONLY: |
| case EMBER_ZCL_THERMOSTAT_CONTROL_SEQUENCE_HEATING_WITH_REHEAT: |
| if (RequestedSystemMode == EMBER_ZCL_THERMOSTAT_SYSTEM_MODE_COOL || |
| RequestedSystemMode == EMBER_ZCL_THERMOSTAT_SYSTEM_MODE_PRECOOLING) |
| return imcode::InvalidValue; |
| else |
| return imcode::Success; |
| default: |
| return imcode::Success; |
| } |
| } |
| } |
| default: |
| return imcode::Success; |
| } |
| } |
| |
| bool emberAfThermostatClusterClearWeeklyScheduleCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::ClearWeeklySchedule::DecodableType & commandData) |
| { |
| // TODO |
| return false; |
| } |
| bool emberAfThermostatClusterGetRelayStatusLogCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::GetRelayStatusLog::DecodableType & commandData) |
| { |
| // TODO |
| return false; |
| } |
| |
| bool emberAfThermostatClusterGetWeeklyScheduleCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::GetWeeklySchedule::DecodableType & commandData) |
| { |
| // TODO |
| return false; |
| } |
| |
| bool emberAfThermostatClusterSetWeeklyScheduleCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::SetWeeklySchedule::DecodableType & commandData) |
| { |
| // TODO |
| return false; |
| } |
| |
| using namespace chip::app::Clusters::Thermostat::Attributes; |
| |
| int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoint) |
| { |
| // Optional Mfg supplied limits |
| int16_t AbsMinHeatSetpointLimit = kDefaultAbsMinHeatSetpointLimit; |
| int16_t AbsMaxHeatSetpointLimit = kDefaultAbsMaxHeatSetpointLimit; |
| |
| // Optional User supplied limits |
| int16_t MinHeatSetpointLimit = kDefaultMinHeatSetpointLimit; |
| int16_t MaxHeatSetpointLimit = kDefaultMaxHeatSetpointLimit; |
| |
| // Attempt to read the setpoint limits |
| // Absmin/max are manufacturer limits |
| // min/max are user imposed min/max |
| |
| // Note that the limits are initialized above per the spec limits |
| // if they are not present emberAfReadAttribute() will not update the value so the defaults are used |
| EmberAfStatus status; |
| |
| // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3724 |
| // behavior is not specified when Abs * values are not present and user values are present |
| // implemented behavior accepts the user values without regard to default Abs values. |
| |
| // Per global matter data model policy |
| // if a attribute is not present then it's default shall be used. |
| |
| status = AbsMinHeatSetpointLimit::Get(endpoint, &AbsMinHeatSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Warning: AbsMinHeatSetpointLimit missing using default"); |
| } |
| |
| status = AbsMaxHeatSetpointLimit::Get(endpoint, &AbsMaxHeatSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Warning: AbsMaxHeatSetpointLimit missing using default"); |
| } |
| status = MinHeatSetpointLimit::Get(endpoint, &MinHeatSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| MinHeatSetpointLimit = AbsMinHeatSetpointLimit; |
| } |
| |
| status = MaxHeatSetpointLimit::Get(endpoint, &MaxHeatSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| MaxHeatSetpointLimit = AbsMaxHeatSetpointLimit; |
| } |
| |
| // Make sure the user imposed limits are within the manufacturer imposed limits |
| |
| // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3725 |
| // Spec does not specify the behavior is the requested setpoint exceeds the limit allowed |
| // This implementation clamps at the limit. |
| |
| // resolution of 3725 is to clamp. |
| |
| if (MinHeatSetpointLimit < AbsMinHeatSetpointLimit) |
| MinHeatSetpointLimit = AbsMinHeatSetpointLimit; |
| |
| if (MaxHeatSetpointLimit > AbsMaxHeatSetpointLimit) |
| MaxHeatSetpointLimit = AbsMaxHeatSetpointLimit; |
| |
| if (HeatingSetpoint < MinHeatSetpointLimit) |
| HeatingSetpoint = MinHeatSetpointLimit; |
| |
| if (HeatingSetpoint > MaxHeatSetpointLimit) |
| HeatingSetpoint = MaxHeatSetpointLimit; |
| |
| return HeatingSetpoint; |
| } |
| |
| int16_t EnforceCoolingSetpointLimits(int16_t CoolingSetpoint, EndpointId endpoint) |
| { |
| // Optional Mfg supplied limits |
| int16_t AbsMinCoolSetpointLimit = kDefaultAbsMinCoolSetpointLimit; |
| int16_t AbsMaxCoolSetpointLimit = kDefaultAbsMaxCoolSetpointLimit; |
| |
| // Optional User supplied limits |
| int16_t MinCoolSetpointLimit = kDefaultMinCoolSetpointLimit; |
| int16_t MaxCoolSetpointLimit = kDefaultMaxCoolSetpointLimit; |
| |
| // Attempt to read the setpoint limits |
| // Absmin/max are manufacturer limits |
| // min/max are user imposed min/max |
| |
| // Note that the limits are initialized above per the spec limits |
| // if they are not present emberAfReadAttribute() will not update the value so the defaults are used |
| EmberAfStatus status; |
| |
| // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3724 |
| // behavior is not specified when Abs * values are not present and user values are present |
| // implemented behavior accepts the user values without regard to default Abs values. |
| |
| // Per global matter data model policy |
| // if a attribute is not present then it's default shall be used. |
| |
| status = AbsMinCoolSetpointLimit::Get(endpoint, &AbsMinCoolSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Warning: AbsMinCoolSetpointLimit missing using default"); |
| } |
| |
| status = AbsMaxCoolSetpointLimit::Get(endpoint, &AbsMaxCoolSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Warning: AbsMaxCoolSetpointLimit missing using default"); |
| } |
| |
| status = MinCoolSetpointLimit::Get(endpoint, &MinCoolSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| MinCoolSetpointLimit = AbsMinCoolSetpointLimit; |
| } |
| |
| status = MaxCoolSetpointLimit::Get(endpoint, &MaxCoolSetpointLimit); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| MaxCoolSetpointLimit = AbsMaxCoolSetpointLimit; |
| } |
| |
| // Make sure the user imposed limits are within the manufacture imposed limits |
| // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3725 |
| // Spec does not specify the behavior is the requested setpoint exceeds the limit allowed |
| // This implementation clamps at the limit. |
| |
| // resolution of 3725 is to clamp. |
| |
| if (MinCoolSetpointLimit < AbsMinCoolSetpointLimit) |
| MinCoolSetpointLimit = AbsMinCoolSetpointLimit; |
| |
| if (MaxCoolSetpointLimit > AbsMaxCoolSetpointLimit) |
| MaxCoolSetpointLimit = AbsMaxCoolSetpointLimit; |
| |
| if (CoolingSetpoint < MinCoolSetpointLimit) |
| CoolingSetpoint = MinCoolSetpointLimit; |
| |
| if (CoolingSetpoint > MaxCoolSetpointLimit) |
| CoolingSetpoint = MaxCoolSetpointLimit; |
| |
| return CoolingSetpoint; |
| } |
| bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * commandObj, |
| const app::ConcreteCommandPath & commandPath, |
| const Commands::SetpointRaiseLower::DecodableType & commandData) |
| { |
| auto & mode = commandData.mode; |
| auto & amount = commandData.amount; |
| |
| EndpointId aEndpointId = commandPath.mEndpointId; |
| |
| int16_t HeatingSetpoint = kDefaultHeatingSetpoint, CoolingSetpoint = kDefaultCoolingSetpoint; // Set to defaults to be safe |
| EmberAfStatus status = EMBER_ZCL_STATUS_FAILURE; |
| EmberAfStatus WriteCoolingSetpointStatus = EMBER_ZCL_STATUS_FAILURE; |
| EmberAfStatus WriteHeatingSetpointStatus = EMBER_ZCL_STATUS_FAILURE; |
| bool AutoSupported = false; |
| bool HeatSupported = false; |
| bool CoolSupported = false; |
| int16_t DeadBandTemp = 0; |
| int8_t DeadBand = 0; |
| uint32_t FeatureMap = 0; |
| |
| // TODO re-enable reading reaturemap once https://github.com/project-chip/connectedhomeip/pull/9725 is merged |
| #ifndef FEATURE_MAP_OVERIDE |
| emberAfReadServerAttribute(endpoint, Thermostat::Id, chip::app::Clusters::Globals::Attributes::Ids::FeatureMap, |
| (uint8_t *) &FeatureMap, sizeof(FeatureMap)); |
| #else |
| FeatureMap = FEATURE_MAP_OVERIDE; |
| #endif |
| |
| if (FeatureMap & 1 << 5) // Bit 5 is Auto Mode supported |
| { |
| AutoSupported = true; |
| if (MinSetpointDeadBand::Get(aEndpointId, &DeadBand) != EMBER_ZCL_STATUS_SUCCESS) |
| DeadBand = kDefaultDeadBand; |
| DeadBandTemp = static_cast<int16_t>(DeadBand * 10); |
| } |
| if (FeatureMap & 1 << 0) |
| HeatSupported = true; |
| |
| if (FeatureMap & 1 << 1) |
| CoolSupported = true; |
| |
| switch (mode) |
| { |
| case EMBER_ZCL_SETPOINT_ADJUST_MODE_HEAT_AND_COOL_SETPOINTS: |
| if (HeatSupported && CoolSupported) |
| { |
| int16_t DesiredCoolingSetpoint, CoolLimit, DesiredHeatingSetpoint, HeatLimit; |
| if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| DesiredCoolingSetpoint = static_cast<int16_t>(CoolingSetpoint + amount * 10); |
| CoolLimit = static_cast<int16_t>(DesiredCoolingSetpoint - |
| EnforceCoolingSetpointLimits(DesiredCoolingSetpoint, aEndpointId)); |
| { |
| if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| DesiredHeatingSetpoint = static_cast<int16_t>(HeatingSetpoint + amount * 10); |
| HeatLimit = static_cast<int16_t>(DesiredHeatingSetpoint - |
| EnforceHeatingSetpointLimits(DesiredHeatingSetpoint, aEndpointId)); |
| { |
| if (CoolLimit != 0 || HeatLimit != 0) |
| { |
| if (abs(CoolLimit) <= abs(HeatLimit)) |
| { |
| // We are limited by the Heating Limit |
| DesiredHeatingSetpoint = static_cast<int16_t>(DesiredHeatingSetpoint - HeatLimit); |
| DesiredCoolingSetpoint = static_cast<int16_t>(DesiredCoolingSetpoint - HeatLimit); |
| } |
| else |
| { |
| // We are limited by Cooling Limit |
| DesiredHeatingSetpoint = static_cast<int16_t>(DesiredHeatingSetpoint - CoolLimit); |
| DesiredCoolingSetpoint = static_cast<int16_t>(DesiredCoolingSetpoint - CoolLimit); |
| } |
| } |
| WriteCoolingSetpointStatus = OccupiedCoolingSetpoint::Set(aEndpointId, DesiredCoolingSetpoint); |
| if (WriteCoolingSetpointStatus != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); |
| } |
| WriteHeatingSetpointStatus = OccupiedHeatingSetpoint::Set(aEndpointId, DesiredHeatingSetpoint); |
| if (WriteHeatingSetpointStatus != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (CoolSupported && !HeatSupported) |
| { |
| if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| CoolingSetpoint = static_cast<int16_t>(CoolingSetpoint + amount * 10); |
| CoolingSetpoint = EnforceCoolingSetpointLimits(CoolingSetpoint, aEndpointId); |
| WriteCoolingSetpointStatus = OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint); |
| if (WriteCoolingSetpointStatus != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); |
| } |
| } |
| } |
| |
| if (HeatSupported && !CoolSupported) |
| { |
| if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| HeatingSetpoint = static_cast<int16_t>(HeatingSetpoint + amount * 10); |
| HeatingSetpoint = EnforceHeatingSetpointLimits(HeatingSetpoint, aEndpointId); |
| WriteHeatingSetpointStatus = OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint); |
| if (WriteHeatingSetpointStatus != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); |
| } |
| } |
| } |
| |
| if ((!HeatSupported || WriteHeatingSetpointStatus == EMBER_ZCL_STATUS_SUCCESS) && |
| (!CoolSupported || WriteCoolingSetpointStatus == EMBER_ZCL_STATUS_SUCCESS)) |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| break; |
| |
| case EMBER_ZCL_SETPOINT_ADJUST_MODE_COOL_SETPOINT: |
| if (CoolSupported) |
| { |
| if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| CoolingSetpoint = static_cast<int16_t>(CoolingSetpoint + amount * 10); |
| CoolingSetpoint = EnforceCoolingSetpointLimits(CoolingSetpoint, aEndpointId); |
| if (AutoSupported) |
| { |
| // Need to check if we can move the cooling setpoint while maintaining the dead band |
| if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| if (CoolingSetpoint - HeatingSetpoint < DeadBandTemp) |
| { |
| // Dead Band Violation |
| // Try to adjust it |
| HeatingSetpoint = static_cast<int16_t>(CoolingSetpoint - DeadBandTemp); |
| if (HeatingSetpoint == EnforceHeatingSetpointLimits(HeatingSetpoint, aEndpointId)) |
| { |
| // Desired cooling setpoint is enforcable |
| // Set the new cooling and heating setpoints |
| if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| } |
| else |
| ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); |
| } |
| else |
| { |
| ChipLogError(Zcl, "Error: Could Not adjust heating setpoint to maintain dead band!"); |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| } |
| else |
| status = OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint); |
| } |
| else |
| ChipLogError(Zcl, "Error: GetOccupiedHeatingSetpoint failed!"); |
| } |
| else |
| { |
| status = OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint); |
| } |
| } |
| else |
| ChipLogError(Zcl, "Error: GetOccupiedCoolingSetpoint failed!"); |
| } |
| else |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| break; |
| |
| case EMBER_ZCL_SETPOINT_ADJUST_MODE_HEAT_SETPOINT: |
| if (HeatSupported) |
| { |
| if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| HeatingSetpoint = static_cast<int16_t>(HeatingSetpoint + amount * 10); |
| HeatingSetpoint = EnforceHeatingSetpointLimits(HeatingSetpoint, aEndpointId); |
| if (AutoSupported) |
| { |
| // Need to check if we can move the cooling setpoint while maintaining the dead band |
| if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| if (CoolingSetpoint - HeatingSetpoint < DeadBandTemp) |
| { |
| // Dead Band Violation |
| // Try to adjust it |
| CoolingSetpoint = static_cast<int16_t>(HeatingSetpoint + DeadBandTemp); |
| if (CoolingSetpoint == EnforceCoolingSetpointLimits(CoolingSetpoint, aEndpointId)) |
| { |
| // Desired cooling setpoint is enforcable |
| // Set the new cooling and heating setpoints |
| if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == EMBER_ZCL_STATUS_SUCCESS) |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| } |
| else |
| ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); |
| } |
| else |
| { |
| ChipLogError(Zcl, "Error: Could Not adjust cooling setpoint to maintain dead band!"); |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| } |
| else |
| status = OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint); |
| } |
| else |
| ChipLogError(Zcl, "Error: GetOccupiedCoolingSetpoint failed!"); |
| } |
| else |
| { |
| status = OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint); |
| } |
| } |
| else |
| ChipLogError(Zcl, "Error: GetOccupiedHeatingSetpoint failed!"); |
| } |
| else |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| break; |
| |
| default: |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| break; |
| } |
| |
| emberAfSendImmediateDefaultResponse(status); |
| return true; |
| } |
| |
| void MatterThermostatPluginServerInitCallback() {} |