Skip to content

Commit 3986859

Browse files
lboueclaude
andcommitted
Make Valve Configuration and Control duration/state attributes nullable
OpenDuration, DefaultOpenDuration, RemainingDuration, CurrentState and TargetState are all nullable per spec, and OpenDuration/DefaultOpenDuration have a min value of 1, so "no duration configured" must be represented as null rather than 0. Without ATTRIBUTE_MASK_NULLABLE, writes of null (e.g. clearing DefaultOpenDuration from Home Assistant) were rejected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent f315478 commit 3986859

2 files changed

Lines changed: 18 additions & 9 deletions

File tree

libraries/Matter/src/MatterWaterValve.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ class WaterValveCommandHandler : public app::CommandHandlerInterface {
7979

8080
// Valve Configuration and Control cluster attributes
8181
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(waterValveConfigurationAndControlAttrs)
82-
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::OpenDuration::Id, INT32U, 4, 0), /* Open Duration */
83-
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::DefaultOpenDuration::Id, INT32U, 4, ATTRIBUTE_MASK_WRITABLE), /* Default Open Duration */
84-
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::RemainingDuration::Id, INT32U, 4, 0), /* Remaining Duration */
85-
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::CurrentState::Id, ENUM8, 1, 0), /* Current State */
86-
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::TargetState::Id, ENUM8, 1, 0), /* Target State */
82+
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::OpenDuration::Id, INT32U, 4, ATTRIBUTE_MASK_NULLABLE), /* Open Duration */
83+
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::DefaultOpenDuration::Id, INT32U, 4, ATTRIBUTE_MASK_WRITABLE_NULLABLE), /* Default Open Duration */
84+
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::RemainingDuration::Id, INT32U, 4, ATTRIBUTE_MASK_NULLABLE), /* Remaining Duration */
85+
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::CurrentState::Id, ENUM8, 1, ATTRIBUTE_MASK_NULLABLE), /* Current State */
86+
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::TargetState::Id, ENUM8, 1, ATTRIBUTE_MASK_NULLABLE), /* Target State */
8787
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::ValveFault::Id, BITMAP16, 2, 0), /* Valve Fault */
8888
DECLARE_DYNAMIC_ATTRIBUTE(ValveConfigurationAndControl::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* FeatureMap */
8989
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); /* ClusterRevision auto added by LIST_END */

libraries/Matter/src/devices/DeviceWaterValve.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626

2727
#include "DeviceWaterValve.h"
2828

29+
// The Matter spec requires OpenDuration/DefaultOpenDuration/RemainingDuration to be at least 1s when
30+
// set, so "no duration configured" (internal 0) must be reported/accepted as null on the wire, using
31+
// the ZCL convention of an all-ones value to represent null for unsigned integer attributes.
32+
static constexpr uint32_t kNullDurationValue = 0xFFFFFFFFu;
33+
2934
DeviceWaterValve::DeviceWaterValve(const char* device_name) :
3035
Device(device_name),
3136
current_state(VALVE_STATE_CLOSED),
@@ -206,13 +211,16 @@ CHIP_ERROR DeviceWaterValve::HandleReadEmberAfAttribute(ClusterId clusterId,
206211
using namespace ::chip::app::Clusters::ValveConfigurationAndControl::Attributes;
207212
if ((attributeId == OpenDuration::Id) && (maxReadLength == 4)) {
208213
uint32_t openDuration = this->GetOpenDuration();
209-
memcpy(buffer, &openDuration, sizeof(openDuration));
214+
uint32_t wireOpenDuration = (openDuration == 0) ? kNullDurationValue : openDuration;
215+
memcpy(buffer, &wireOpenDuration, sizeof(wireOpenDuration));
210216
} else if ((attributeId == DefaultOpenDuration::Id) && (maxReadLength == 4)) {
211217
uint32_t defaultOpenDuration = this->GetDefaultOpenDuration();
212-
memcpy(buffer, &defaultOpenDuration, sizeof(defaultOpenDuration));
218+
uint32_t wireDefaultOpenDuration = (defaultOpenDuration == 0) ? kNullDurationValue : defaultOpenDuration;
219+
memcpy(buffer, &wireDefaultOpenDuration, sizeof(wireDefaultOpenDuration));
213220
} else if ((attributeId == RemainingDuration::Id) && (maxReadLength == 4)) {
214221
uint32_t remainingDuration = this->GetRemainingDuration();
215-
memcpy(buffer, &remainingDuration, sizeof(remainingDuration));
222+
uint32_t wireRemainingDuration = (remainingDuration == 0) ? kNullDurationValue : remainingDuration;
223+
memcpy(buffer, &wireRemainingDuration, sizeof(wireRemainingDuration));
216224
} else if ((attributeId == CurrentState::Id) && (maxReadLength == 1)) {
217225
uint8_t currentState = this->GetCurrentState();
218226
memcpy(buffer, &currentState, sizeof(currentState));
@@ -254,7 +262,8 @@ CHIP_ERROR DeviceWaterValve::HandleWriteEmberAfAttribute(ClusterId clusterId,
254262

255263
using namespace ::chip::app::Clusters::ValveConfigurationAndControl::Attributes;
256264
if (attributeId == DefaultOpenDuration::Id) {
257-
this->SetDefaultOpenDuration(*((uint32_t*)buffer));
265+
uint32_t wireDefaultOpenDuration = *((uint32_t*)buffer);
266+
this->SetDefaultOpenDuration((wireDefaultOpenDuration == kNullDurationValue) ? 0 : wireDefaultOpenDuration);
258267
} else {
259268
return CHIP_ERROR_INVALID_ARGUMENT;
260269
}

0 commit comments

Comments
 (0)