blob: ea1f4375c1649de2257a06ae7541828d4e927a94 [file] [log] [blame]
/*
*
* Copyright (c) 2024 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.
*/
/**********************************************************
* Includes
*********************************************************/
#include <thermostat-delegate-impl.h>
#include <thermostat-manager.h>
#include <app/clusters/bindings/BindingManager.h>
#include <app/clusters/thermostat-server/thermostat-server.h>
#include <controller/ReadInteraction.h>
#include <platform/PlatformManager.h>
/**********************************************************
* Defines and Constants
*********************************************************/
using namespace chip;
using namespace chip::app;
using namespace chip::app::DataModel;
using namespace chip::Controller;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Thermostat;
using namespace chip::app::Clusters::Thermostat::Structs;
using namespace chip::app::Clusters::Thermostat::Attributes;
using namespace chip::app::Clusters::TemperatureMeasurement;
using namespace chip::app::Clusters::TemperatureMeasurement::Attributes;
using namespace Protocols::InteractionModel;
using namespace chip::DeviceLayer;
static constexpr EndpointId kThermostatEndpoint = 1;
static constexpr uint16_t kMaxIntervalCeilingSeconds = 3600;
static const char * SystemModeString(SystemModeEnum systemMode);
static const char * RunningModeString(ThermostatRunningModeEnum runningMode);
/**********************************************************
* Variable declarations
*********************************************************/
ThermostatManager ThermostatManager::sThermostatMgr;
namespace {
template <typename DecodableAttributeType>
static void OnAttributeChangeReported(const ConcreteDataAttributePath & path, const DecodableAttributeType & value);
template <>
void OnAttributeChangeReported<MeasuredValue::TypeInfo::DecodableType>(const ConcreteDataAttributePath & path,
const MeasuredValue::TypeInfo::DecodableType & value)
{
ClusterId clusterId = path.mClusterId;
if (clusterId != TemperatureMeasurement::Id)
{
ChipLogError(AppServer,
"Attribute change reported for TemperatureMeasurement cluster on incorrect cluster id " ChipLogFormatMEI,
ChipLogValueMEI(clusterId));
return;
}
AttributeId attributeId = path.mAttributeId;
if (attributeId != MeasuredValue::Id)
{
ChipLogError(AppServer,
"Attribute change reported for TemperatureMeasurement cluster for incorrect attribute" ChipLogFormatMEI,
ChipLogValueMEI(attributeId));
return;
}
if (!value.IsNull())
{
ChipLogDetail(AppServer, "Attribute change reported for TemperatureMeasurement cluster - MeasuredValue is %d",
value.Value());
}
}
static void OnError(const ConcreteDataAttributePath * path, ChipError err)
{
ChipLogError(AppServer,
"Subscribing to cluster Id " ChipLogFormatMEI " and attribute Id " ChipLogFormatMEI
" failed with error %" CHIP_ERROR_FORMAT,
ChipLogValueMEI(path->mClusterId), ChipLogValueMEI(path->mAttributeId), err.Format());
}
static void OnSubscriptionEstablished(const ReadClient & client, unsigned int value)
{
ChipLogDetail(AppServer, "OnSubscriptionEstablished with subscription Id: %d", value);
}
template <typename DecodableAttributeType>
void SubscribeToAttribute(ClusterId clusterId, AttributeId attributeId, const EmberBindingTableEntry & binding,
OperationalDeviceProxy * peer_device)
{
VerifyOrReturn(peer_device->GetSecureSession().HasValue(),
ChipLogError(AppServer, "SubscribeToAttribute failed. Secure session is null"));
SubscribeAttribute<DecodableAttributeType>(
peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, clusterId, attributeId,
&OnAttributeChangeReported<DecodableAttributeType>, &OnError, 0, kMaxIntervalCeilingSeconds, &OnSubscriptionEstablished,
nullptr, true /* fabricFiltered */, false /* keepExistingSubscription */);
}
static void ThermostatBoundDeviceChangedHandler(const EmberBindingTableEntry & binding, OperationalDeviceProxy * peer_device,
void * context)
{
VerifyOrReturn(binding.clusterId.has_value(), ChipLogError(AppServer, "Cluster Id is null"));
ClusterId clusterId = binding.clusterId.value();
switch (clusterId)
{
case TemperatureMeasurement::Id:
// Subscribe to the MeasuredValue attribute
SubscribeToAttribute<MeasuredValue::TypeInfo::DecodableType>(clusterId, MeasuredValue::Id, binding, peer_device);
break;
default:
ChipLogError(AppServer, "Unsupported Cluster Id");
break;
}
}
void NotifyBoundClusterChangedForAllClusters()
{
BindingManager::GetInstance().NotifyBoundClusterChanged(kThermostatEndpoint, TemperatureMeasurement::Id, nullptr);
}
static void OnPlatformChipDeviceEvent(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
if (event->Type == DeviceLayer::DeviceEventType::kBindingsChangedViaCluster)
{
NotifyBoundClusterChangedForAllClusters();
}
}
void InitBindingManager(intptr_t context)
{
auto & server = Server::GetInstance();
CHIP_ERROR error = BindingManager::GetInstance().Init(
{ &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() });
if (error != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to init binding manager");
}
BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(ThermostatBoundDeviceChangedHandler);
NotifyBoundClusterChangedForAllClusters();
}
} // anonymous namespace
CHIP_ERROR ThermostatManager::Init()
{
// Init binding manager
DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformChipDeviceEvent, reinterpret_cast<intptr_t>(this));
DeviceLayer::PlatformMgr().ScheduleWork(InitBindingManager);
mLocalTemperature = GetCurrentTemperature();
mSystemMode = GetSystemMode();
mRunningMode = GetRunningMode();
mOccupiedCoolingSetpoint = GetCurrentCoolingSetPoint();
mOccupiedHeatingSetpoint = GetCurrentHeatingSetPoint();
// TODO: Gotta expose this properly on attribute
mOccupiedSetback = 5; // 0.5 C
ChipLogError(AppServer,
"Initialized a thermostat with \n "
"mSystemMode: %u (%s) \n mRunningMode: %u (%s) \n mLocalTemperature: %d \n mOccupiedHeatingSetpoint: %d \n "
"mOccupiedCoolingSetpoint: %d"
"NumberOfPresets: %d",
to_underlying(mSystemMode), SystemModeString(mSystemMode), to_underlying(mRunningMode),
RunningModeString(mRunningMode), mLocalTemperature, mOccupiedHeatingSetpoint, mOccupiedCoolingSetpoint,
GetNumberOfPresets());
// TODO: Should this be called later?
EvalThermostatState();
return CHIP_NO_ERROR;
}
void ThermostatManager::AttributeChangeHandler(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t * value,
uint16_t size)
{
switch (endpointId)
{
case kThermostatEndpoint:
ThermostatEndpointAttributeChangeHandler(clusterId, attributeId, value, size);
break;
default:
ChipLogError(AppServer, "Attribute change reported for Thermostat on incorrect endpoint. Ignoring.");
break;
}
}
void ThermostatManager::ThermostatEndpointAttributeChangeHandler(ClusterId clusterId, AttributeId attributeId, uint8_t * value,
uint16_t size)
{
switch (clusterId)
{
case Thermostat::Id:
ThermostatClusterAttributeChangeHandler(attributeId, value, size);
break;
default:
ChipLogError(AppServer,
"Attribute change reported for Thermostat on incorrect cluster for the thermostat endpoint. Ignoring.");
break;
}
}
void ThermostatManager::ThermostatClusterAttributeChangeHandler(AttributeId attributeId, uint8_t * value, uint16_t size)
{
switch (attributeId)
{
case LocalTemperature::Id: {
memcpy(&mLocalTemperature, value, size);
ChipLogError(AppServer, "Local temperature changed to %d", mLocalTemperature);
EvalThermostatState();
}
break;
case OccupiedCoolingSetpoint::Id: {
memcpy(&mOccupiedCoolingSetpoint, value, size);
ChipLogError(AppServer, "Cooling temperature changed to %d", mOccupiedCoolingSetpoint);
EvalThermostatState();
}
break;
case OccupiedHeatingSetpoint::Id: {
memcpy(&mOccupiedHeatingSetpoint, value, size);
ChipLogError(AppServer, "Heating temperature changed to %d", mOccupiedHeatingSetpoint);
EvalThermostatState();
}
break;
case SystemMode::Id: {
mSystemMode = static_cast<SystemModeEnum>(*value);
ChipLogError(AppServer, "System mode changed to %u (%s)", *value, SystemModeString(mSystemMode));
EvalThermostatState();
}
break;
case ThermostatRunningMode::Id: {
mRunningMode = static_cast<ThermostatRunningModeEnum>(*value);
ChipLogError(AppServer, "Running mode changed to %u (%s)", *value, RunningModeString(mRunningMode));
}
break;
default: {
ChipLogError(AppServer, "Unhandled thermostat attribute %u", static_cast<uint>(attributeId));
return;
}
break;
}
}
SystemModeEnum ThermostatManager::GetSystemMode()
{
SystemModeEnum systemMode;
SystemMode::Get(kThermostatEndpoint, &systemMode);
return systemMode;
}
ThermostatRunningModeEnum ThermostatManager::GetRunningMode()
{
ThermostatRunningModeEnum runningMode;
ThermostatRunningMode::Get(kThermostatEndpoint, &runningMode);
return runningMode;
}
int16_t ThermostatManager::GetCurrentTemperature()
{
DataModel::Nullable<int16_t> currentTemperature;
currentTemperature.SetNull();
LocalTemperature::Get(kThermostatEndpoint, currentTemperature);
return currentTemperature.ValueOr(0);
}
int16_t ThermostatManager::GetCurrentHeatingSetPoint()
{
int16_t heatingSetpoint;
OccupiedHeatingSetpoint::Get(kThermostatEndpoint, &heatingSetpoint);
return heatingSetpoint;
}
int16_t ThermostatManager::GetCurrentCoolingSetPoint()
{
int16_t coolingSetpoint;
OccupiedCoolingSetpoint::Get(kThermostatEndpoint, &coolingSetpoint);
return coolingSetpoint;
}
uint8_t ThermostatManager::GetNumberOfPresets()
{
return ThermostatDelegate::GetInstance().GetNumberOfPresets();
}
CHIP_ERROR ThermostatManager::SetSystemMode(SystemModeEnum systemMode)
{
uint8_t systemModeValue = to_underlying(systemMode);
if (mSystemMode == systemMode)
{
ChipLogDetail(AppServer, "Already in system mode: %u (%s)", systemModeValue, SystemModeString(systemMode));
return CHIP_NO_ERROR;
}
ChipLogError(AppServer, "Setting system mode: %u (%s)", systemModeValue, SystemModeString(systemMode));
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(SystemMode::Set(kThermostatEndpoint, systemMode));
}
CHIP_ERROR ThermostatManager::SetRunningMode(ThermostatRunningModeEnum runningMode)
{
uint8_t runningModeValue = to_underlying(runningMode);
if (mRunningMode == runningMode)
{
ChipLogDetail(AppServer, "Already in running mode: %u (%s)", runningModeValue, RunningModeString(runningMode));
return CHIP_NO_ERROR;
}
ChipLogError(AppServer, "Setting running mode: %u (%s)", runningModeValue, RunningModeString(runningMode));
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(ThermostatRunningMode::Set(kThermostatEndpoint, runningMode));
}
CHIP_ERROR ThermostatManager::SetCurrentTemperature(int16_t temperature)
{
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(LocalTemperature::Set(kThermostatEndpoint, temperature));
}
CHIP_ERROR ThermostatManager::SetCurrentHeatingSetPoint(int16_t heatingSetpoint)
{
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(OccupiedHeatingSetpoint::Set(kThermostatEndpoint, heatingSetpoint));
}
CHIP_ERROR ThermostatManager::SetCurrentCoolingSetPoint(int16_t coolingSetpoint)
{
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(OccupiedCoolingSetpoint::Set(kThermostatEndpoint, coolingSetpoint));
}
void ThermostatManager::EvalThermostatState()
{
ChipLogError(AppServer,
"Eval Thermostat Running Mode \n "
"mSystemMode: %u (%s) \n mRunningMode: %u (%s) \n mLocalTemperature: %d \n mOccupiedHeatingSetpoint: %d \n "
"mOccupiedCoolingSetpoint: %d",
to_underlying(mSystemMode), SystemModeString(mSystemMode), to_underlying(mRunningMode),
RunningModeString(mRunningMode), mLocalTemperature, mOccupiedHeatingSetpoint, mOccupiedCoolingSetpoint);
switch (mSystemMode)
{
case SystemModeEnum::kOff: {
SetRunningMode(ThermostatRunningModeEnum::kOff);
break;
}
case SystemModeEnum::kHeat: {
UpdateRunningModeForHeating();
break;
}
case SystemModeEnum::kCool: {
UpdateRunningModeForCooling();
break;
}
case SystemModeEnum::kAuto: {
UpdateRunningModeForHeating();
UpdateRunningModeForCooling();
break;
}
default:
break;
}
}
void ThermostatManager::UpdateRunningModeForHeating()
{
const int16_t heatingOnThreshold = mOccupiedHeatingSetpoint - static_cast<int16_t>(mOccupiedSetback * 10);
const int16_t heatingOffThreshold = mOccupiedHeatingSetpoint + static_cast<int16_t>(mOccupiedSetback * 10);
if (mRunningMode == ThermostatRunningModeEnum::kHeat)
{
if (mLocalTemperature >= heatingOffThreshold)
{
ChipLogDetail(AppServer, "Eval Heat - Turning off");
SetRunningMode(ThermostatRunningModeEnum::kOff);
}
else
{
ChipLogDetail(AppServer, "Eval Heat - Keep Heating");
}
}
else
{
if (mLocalTemperature <= heatingOnThreshold)
{
ChipLogDetail(AppServer, "Eval Heat - Turn on");
SetRunningMode(ThermostatRunningModeEnum::kHeat);
}
else
{
ChipLogDetail(AppServer, "Eval Heat - Nothing to do");
}
}
}
void ThermostatManager::UpdateRunningModeForCooling()
{
const int16_t coolingOffThreshold = mOccupiedCoolingSetpoint - static_cast<int16_t>(mOccupiedSetback * 10);
const int16_t coolingOnThreshold = mOccupiedCoolingSetpoint + static_cast<int16_t>(mOccupiedSetback * 10);
if (mRunningMode == ThermostatRunningModeEnum::kCool)
{
if (mLocalTemperature <= coolingOffThreshold)
{
ChipLogDetail(AppServer, "Eval Cool - Turning off");
SetRunningMode(ThermostatRunningModeEnum::kOff);
}
else
{
ChipLogDetail(AppServer, "Eval Cool - Keep Cooling");
}
}
else
{
if (mLocalTemperature >= coolingOnThreshold)
{
ChipLogDetail(AppServer, "Eval Cool - Turn on");
SetRunningMode(ThermostatRunningModeEnum::kCool);
}
else
{
ChipLogDetail(AppServer, "Eval Cool - Nothing to do");
}
}
}
static const char * SystemModeString(SystemModeEnum systemMode)
{
switch (systemMode)
{
case SystemModeEnum::kOff:
return "Off";
case SystemModeEnum::kAuto:
return "Auto";
case SystemModeEnum::kCool:
return "Cool";
case SystemModeEnum::kHeat:
return "Heat";
default:
return "Unknown";
}
}
static const char * RunningModeString(ThermostatRunningModeEnum runningMode)
{
switch (runningMode)
{
case ThermostatRunningModeEnum::kOff:
return "Off";
case ThermostatRunningModeEnum::kCool:
return "Cool";
case ThermostatRunningModeEnum::kHeat:
return "Heat";
default:
return "Unknown";
}
}
void emberAfThermostatClusterInitCallback(EndpointId endpoint)
{
ChipLogProgress(Zcl, "Starting Thermostat Manager");
ThermostatManager().Init();
// Register the delegate for the Thermostat
auto & delegate = ThermostatDelegate::GetInstance();
// Set the default delegate for endpoint kThermostatEndpoint.
VerifyOrDie(endpoint == kThermostatEndpoint);
SetDefaultDelegate(endpoint, &delegate);
}