Skip to content

[Fix] Apply preset setpoints when changing active preset#43587

Open
lboue wants to merge 9 commits intoproject-chip:masterfrom
lboue:fix/thermostat-apply-setpoints-on-preset-change
Open

[Fix] Apply preset setpoints when changing active preset#43587
lboue wants to merge 9 commits intoproject-chip:masterfrom
lboue:fix/thermostat-apply-setpoints-on-preset-change

Conversation

@lboue
Copy link
Contributor

@lboue lboue commented Mar 14, 2026

Summary

This PR fixes Thermostat SetActivePreset behavior so that changing the active preset correctly applies the preset’s occupied setpoints before updating ActivePresetHandle.

Technical details

  • Updated SetActivePreset to:
    • Resolve the target preset once,
    • Apply OccupiedCoolingSetpoint / OccupiedHeatingSetpoint from that preset (with existing limit enforcement),
    • Then set ActivePresetHandle.
  • Improved error handling:
    • Return InvalidCommand when the requested preset handle is not found.
    • Propagate underlying lookup/write failures as IM status values instead of collapsing them into a generic status.
    • Added explicit logging on failure paths for easier debugging.
  • Added protection against callback-driven re-entrancy while applying preset setpoints, preventing unintended transient clearing of ActivePresetHandle.

Impact

  • Preset activation now updates runtime setpoints consistently.
  • Error reporting is more accurate for clients and easier to diagnose.
  • Reduces risk of side effects from attribute-changed callback recursion.

Related issues

Testing

Tested with thermostat/linux example:
https://github.com/project-chip/connectedhomeip/tree/master/examples/thermostat/linux

I am not a member of the CSA, so I do not have permission to update the Python test file. I don't know what step number to assign to a new test.

  • Built and validated with targeted local thermostat/app test builds.
  • No new compile errors in touched code paths.
[1773449586.126] [181640:181640] [EM] >>> [E:1392r S:64189 M:46217965] (S) Msg RX from 1:000000000001B669 [269A] to 000000000000005E --- Type 0001:08 (IM:InvokeCommandRequest) (B:80)
[1773449586.126] [181640:181640] [EM] Handling via exchange: 1392r, Delegate: 0x58802974e7c8
[1773449586.126] [181640:181640] [DMG] InvokeRequestMessage =
[1773449586.126] [181640:181640] [DMG] {
[1773449586.126] [181640:181640] [DMG]  suppressResponse = false,
[1773449586.126] [181640:181640] [DMG]  timedRequest = false,
[1773449586.126] [181640:181640] [DMG]  InvokeRequests =
[1773449586.126] [181640:181640] [DMG]  [
[1773449586.126] [181640:181640] [DMG]          CommandDataIB =
[1773449586.126] [181640:181640] [DMG]          {
[1773449586.126] [181640:181640] [DMG]                  CommandPathIB =
[1773449586.127] [181640:181640] [DMG]                  {
[1773449586.127] [181640:181640] [DMG]                          ClusterId = 0x201,
[1773449586.127] [181640:181640] [DMG]                          CommandId = 0x6,
[1773449586.127] [181640:181640] [DMG]                          EndpointId = 0x1,
[1773449586.127] [181640:181640] [DMG]                  },
[1773449586.127] [181640:181640] [DMG]
[1773449586.127] [181640:181640] [DMG]                  CommandFields =
[1773449586.127] [181640:181640] [DMG]                  {
[1773449586.127] [181640:181640] [DMG]                          0x0 = [
[1773449586.127] [181640:181640] [DMG]                                          0x02,
[1773449586.127] [181640:181640] [DMG]                          ] (1 bytes)
[1773449586.127] [181640:181640] [DMG]                  },
[1773449586.127] [181640:181640] [DMG]          },
[1773449586.127] [181640:181640] [DMG]
[1773449586.127] [181640:181640] [DMG]  ],
[1773449586.127] [181640:181640] [DMG]
[1773449586.127] [181640:181640] [DMG]  InteractionModelRevision = 11
[1773449586.127] [181640:181640] [DMG] },
[1773449586.127] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=o r=i
[1773449586.127] [181640:181640] [DMG] AccessControl: allowed
[1773449586.127] [181640:181640] [DMG] Received command for Endpoint=1 Cluster=0x0000_0201 Command=0x0000_0006
[1773449586.128] [181640:181640] [DL] Wrote settings to /tmp/chip_kvs
[1773449586.128] [181640:181640] [DMG] Endpoint 1, Cluster 0x0000_0201 update version to e9c5b5f4
[1773449586.128] [181640:181640] [DMG] Cannot merge the new path into any existing path, create one.
[1773449586.128] [181640:181640] [ZCL] Setting active preset to null
[1773449586.128] [181640:181640] [-] Clear ActivePresetHandle
[1773449586.128] [181640:181640] [DMG] Endpoint 1, Cluster 0x0000_0201 update version to e9c5b5f5
[1773449586.128] [181640:181640] [DMG] Cannot merge the new path into any existing path, create one.
[1773449586.131] [181640:181640] [DL] Wrote settings to /tmp/chip_kvs
[1773449586.131] [181640:181640] [DMG] Endpoint 1, Cluster 0x0000_0201 update version to e9c5b5f6
[1773449586.131] [181640:181640] [DMG] Cannot merge the new path into any existing path, create one.
[1773449586.131] [181640:181640] [ZCL] Setting active preset to null
[1773449586.131] [181640:181640] [-] Clear ActivePresetHandle
[1773449586.131] [181640:181640] [DMG] Endpoint 1, Cluster 0x0000_0201 update version to e9c5b5f7
[1773449586.131] [181640:181640] [-] Set ActivePresetHandle to
[1773449586.131] [181640:181640] [-] 0x02,
[1773449586.131] [181640:181640] [DMG] Endpoint 1, Cluster 0x0000_0201 update version to e9c5b5f8
[1773449586.131] [181640:181640] [DMG] Command handler moving to [NewRespons]
[1773449586.131] [181640:181640] [DMG] Command handler moving to [ Preparing]
[1773449586.131] [181640:181640] [DMG] Command handler moving to [AddingComm]
[1773449586.131] [181640:181640] [DMG] Command handler moving to [AddedComma]
[1773449586.131] [181640:181640] [DMG] Decreasing reference count for CommandHandlerImpl, remaining 1
[1773449586.131] [181640:181640] [DMG] Decreasing reference count for CommandHandlerImpl, remaining 0
[1773449586.131] [181640:181640] [DMG] Command handler moving to [AwaitingDe]
[1773449586.131] [181640:181640] [EM] <<< [E:1392r S:64189 M:82471305 (Ack:46217965)] (S) Msg TX from 000000000000005E to 1:000000000001B669 [269A] [UDP:[2a01:e0a:102b:8280:58a7:3cb5:d1bf:5241]:37418] --- Type 0001:09 (IM:InvokeCommandResponse) (B:68)
[1773449586.132] [181640:181640] [EM] ??1 [E:1392r S:64189 M:82471305] (S) Msg Retransmission to 1:000000000001B669 scheduled for 376ms from now [State:Active II:500 AI:300 AT:4000]
[1773449586.132] [181640:181640] [DMG] Command response sender moving to [AllInvokeR]
[1773449586.132] [181640:181640] [DMG] Building Reports for ReadHandler with LastReportGeneration = 0x0000001C DirtyGeneration = 0x00000021
[1773449586.132] [181640:181640] [DMG] <RE:Run> Cluster 201, Attribute 11 is dirty
[1773449586.132] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.132] [181640:181640] [DMG] AccessControl: allowed
[1773449586.132] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.132] [181640:181640] [DMG] AccessControl: allowed
[1773449586.132] [181640:181640] [DMG] Reading attribute: Cluster=0x0000_0201 Endpoint=0x1 AttributeId=0x0000_0011 (expanded=1)
[1773449586.132] [181640:181640] [DMG] <RE:Run> Cluster 201, Attribute 12 is dirty
[1773449586.132] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.132] [181640:181640] [DMG] AccessControl: allowed
[1773449586.132] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.132] [181640:181640] [DMG] AccessControl: allowed
[1773449586.132] [181640:181640] [DMG] Reading attribute: Cluster=0x0000_0201 Endpoint=0x1 AttributeId=0x0000_0012 (expanded=1)
[1773449586.132] [181640:181640] [DMG] <RE:Run> Cluster 201, Attribute 4e is dirty
[1773449586.133] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.133] [181640:181640] [DMG] AccessControl: allowed
[1773449586.133] [181640:181640] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0201 e=1 p=v r=r
[1773449586.133] [181640:181640] [DMG] AccessControl: allowed
[1773449586.133] [181640:181640] [DMG] Reading attribute: Cluster=0x0000_0201 Endpoint=0x1 AttributeId=0x0000_004E (expanded=1)
[1773449586.133] [181640:181640] [DMG] Fetched 0 events
[1773449586.133] [181640:181640] [DMG] <RE> Sending report (payload has 98 bytes)...
[1773449586.133] [181640:181640] [EM] <<< [E:56886i S:64189 M:82471306] (S) Msg TX from 000000000000005E to 1:000000000001B669 [269A] [UDP:[2a01:e0a:102b:8280:58a7:3cb5:d1bf:5241]:37418] --- Type 0001:05 (IM:ReportData) (B:128)
[1773449586.133] [181640:181640] [EM] ??1 [E:56886i S:64189 M:82471306] (S) Msg Retransmission to 1:000000000001B669 scheduled for 395ms from now [State:Active II:500 AI:300 AT:4000]
[1773449586.133] [181640:181640] [DMG] IM RH moving to [AwaitingReportResponse]
[1773449586.133] [181640:181640] [DMG] <RE> ReportsInFlight = 1 with readHandler 0, RE has no more messages
[1773449586.133] [181640:181640] [DMG] All ReadHandler-s are clean, clear GlobalDirtySet
[1773449586.137] [181640:181640] [EM] >>> [E:1392r S:64189 M:46217966 (Ack:82471305)] (S) Msg RX from 1:000000000001B669 [269A] to 000000000000005E --- Type 0000:10 (SecureChannel:StandaloneAck) (B:50)
[1773449586.137] [181640:181640] [EM] Found matching exchange: 1392r, Delegate: (nil)
[1773449586.137] [181640:181640] [EM] Rxd Ack; Removing MessageCounter:82471305 from Retrans Table on exchange 1392r
[1773449586.144] [181640:181640] [EM] >>> [E:56886i S:64189 M:46217967 (Ack:82471306)] (S) Msg RX from 1:000000000001B669 [269A] to 000000000000005E --- Type 0001:01 (IM:StatusResponse) (B:42)
[1773449586.144] [181640:181640] [EM] Found matching exchange: 56886i, Delegate: 0x58804f0cfc98
[1773449586.144] [181640:181640] [EM] Rxd Ack; Removing MessageCounter:82471306 from Retrans Table on exchange 56886i
[1773449586.144] [181640:181640] [DMG] StatusResponseMessage =
[1773449586.144] [181640:181640] [DMG] {
[1773449586.144] [181640:181640] [DMG]  Status = 0x00 (SUCCESS),
[1773449586.144] [181640:181640] [DMG]  InteractionModelRevision = 13
[1773449586.144] [181640:181640] [DMG] }
[1773449586.144] [181640:181640] [IM] Received status response, status is 0x00 (SUCCESS)
[1773449586.144] [181640:181640] [DMG] <RE> OnReportConfirm: NumReports = 0
[1773449586.144] [181640:181640] [DMG] IM RH moving to [CanStartReporting]
[1773449586.145] [181640:181640] [EM] <<< [E:56886i S:64189 M:82471307 (Ack:46217967)] (S) Msg TX from 000000000000005E to 1:000000000001B669 [269A] [UDP:[2a01:e0a:102b:8280:58a7:3cb5:d1bf:5241]:37418] --- Type 0000:10 (SecureChannel:StandaloneAck) (B:34)
[1773449586.145] [181640:181640] [EM] Flushed pending ack for MessageCounter:46217967 on exchange 56886i

Readability checklist

The checklist below will help the reviewer finish PR review in time and keep the
code readable:

  • PR title is
    descriptive
  • Apply the
    “When in Rome…”
    rule (coding style)
  • PR size is short
  • Try to avoid "squashing" and "force-update" in commit history
  • CI time didn't increase

See: Pull Request Guidelines

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly implements the application of setpoints when a thermostat preset is activated. The changes in thermostat-server-presets.cpp ensure that when SetActivePreset is called with a valid preset handle, the corresponding heating and cooling setpoints are retrieved and applied to the occupied setpoint attributes. The addition of unit tests in TestThermostatServerPresets.cpp validates this new behavior effectively. I've made one suggestion to improve code efficiency by removing a redundant check and to adhere to repository guidelines for ignoring return values. Overall, this is a good fix.

@lboue lboue force-pushed the fix/thermostat-apply-setpoints-on-preset-change branch from 52c3aca to ff9378b Compare March 14, 2026 00:15
@lboue lboue changed the title [Fix] Initialize presets in TestThermostatDelegate and validate setpoint application in tests [Fix] Apply preset setpoints when changing active preset Mar 14, 2026
@lboue lboue marked this pull request as ready for review March 14, 2026 00:17
Copilot AI review requested due to automatic review settings March 14, 2026 00:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Thermostat SetActivePreset behavior so changing the active preset also applies the preset’s occupied heating/cooling setpoints (with limit enforcement) before updating ActivePresetHandle, and improves error/status handling for invalid handles and failed setpoint writes.

Changes:

  • Lookup the requested preset once (validation + data retrieval) and apply its occupied setpoints before updating ActivePresetHandle.
  • Return Status::InvalidCommand when the requested preset handle is not present.
  • Propagate (and log) the underlying status if writing OccupiedCoolingSetpoint / OccupiedHeatingSetpoint fails.

Copilot AI review requested due to automatic review settings March 14, 2026 00:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Thermostat preset activation behavior so that switching the active preset also applies any preset cooling/heating setpoints before updating ActivePresetHandle, and improves error handling for invalid preset handles and attribute write failures.

Changes:

  • Refactors preset lookup into a single pass (GetMatchingPresetInPresets) and uses it for both validation and setpoint retrieval.
  • Updates SetActivePreset to apply OccupiedCoolingSetpoint / OccupiedHeatingSetpoint (with enforced limits) before setting ActivePresetHandle, returning/logging the underlying write status on failure.
  • Returns Status::InvalidCommand when the requested preset handle is not found.

lboue and others added 2 commits March 14, 2026 01:29
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 14, 2026 00:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Thermostat presets server logic so switching the active preset also applies any preset-provided occupied heating/cooling setpoints before updating the ActivePresetHandle, improving behavioral consistency and reducing redundant preset list iteration.

Changes:

  • Refactors preset lookup into a single GetMatchingPresetInPresets(...) that returns CHIP_ERROR plus a found flag.
  • Updates ThermostatAttrAccess::SetActivePreset to validate the handle via lookup, apply optional preset setpoints, and return InvalidCommand when the handle is not found.
  • Updates AppendPendingPreset to use the new lookup API and to distinguish lookup errors from “not found”.

@github-actions
Copy link

PR #43587: Size comparison from 2403f0a to 16082d7

Full report (6 builds for cc32xx, nrfconnect, realtek, stm32)
platform target config section 2403f0a 16082d7 change % change
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 558650 559090 440 0.1
RAM 204504 204504 0 0.0
lock CC3235SF_LAUNCHXL FLASH 591814 591814 0 0.0
RAM 204744 204744 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 858144 858492 348 0.0
RAM 162019 162019 0 0.0
realtek light-switch-app rtl8777g FLASH 721176 721176 0 0.0
RAM 113456 113456 0 0.0
lighting-app rtl8777g FLASH 768912 768912 0 0.0
RAM 114696 114696 0 0.0
stm32 light STM32WB5MM-DK FLASH 479728 479728 0 0.0
RAM 141324 141324 0 0.0

…or setpoints

Replace emAfWriteAttributeExternal with ZCL_INT16S_ATTRIBUTE_TYPE by the
generated OccupiedCoolingSetpoint::Set / OccupiedHeatingSetpoint::Set
accessors, which use the correct attribute type (temperature) registered
in the metadata, fixing the INVALID_DATA_TYPE error on SetActivePreset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Thermostat Presets server logic so that switching ActivePresetHandle also applies the preset’s occupied heating/cooling setpoints, and improves error handling when the requested preset handle is invalid or when writes fail.

Changes:

  • Refactors preset lookup to a single GetMatchingPresetInPresets pass that returns CHIP_ERROR + found.
  • In ThermostatAttrAccess::SetActivePreset, applies preset occupied setpoints (with Enforce*SetpointLimits) before updating ActivePresetHandle, returning the underlying write status on failure.
  • Returns Status::InvalidCommand when the requested preset handle is not present.
Comments suppressed due to low confidence (1)

src/app/clusters/thermostat-server/thermostat-server-presets.cpp:357

  • SetActivePreset applies occupied setpoints before calling delegate->SetActivePresetHandle. If SetActivePresetHandle fails, the command returns an error but the occupied setpoints may already have been updated, leaving the thermostat in a partially-applied state (and possibly with ActivePresetHandle cleared to null by the attribute-changed callback). Consider either (a) snapshotting the previous setpoints and rolling them back on failure, or (b) performing the ActivePresetHandle update in a way that can’t fail after setpoints are applied (e.g., ensure persistence before writes, or apply changes atomically in the delegate).
    CHIP_ERROR err = delegate->SetActivePresetHandle(presetHandle);

    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(Zcl, "Failed to set ActivePresetHandle with error %" CHIP_ERROR_FORMAT, err.Format());
        return StatusIB(err).mStatus;
    }

Comment on lines +329 to +335
int16_t constrainedCoolingSetpoint = EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint);
Status status = OccupiedCoolingSetpoint::Set(endpoint, constrainedCoolingSetpoint);
if (status != Status::Success)
{
ChipLogError(Zcl, "Failed to set OccupiedCoolingSetpoint with status %u", to_underlying(status));
return status;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed the re-entrancy concern directly in SetActivePreset by adding an internal guard for preset-application scope.
When setpoint writes triggered during preset application cause a callback-driven SetActivePreset(nullptr), that nested clear is now ignored, so we avoid transient clears/extra writes while preserving normal explicit clear behavior outside this flow.

I also kept the existing error propagation behavior (StatusIB(...)) for lookup/write failures.

@codecov
Copy link

codecov bot commented Mar 14, 2026

Codecov Report

❌ Patch coverage is 0% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.11%. Comparing base (fa956ce) to head (44dab5c).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
...rs/thermostat-server/thermostat-server-presets.cpp 0.00% 37 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #43587      +/-   ##
==========================================
+ Coverage   54.07%   54.11%   +0.03%     
==========================================
  Files        1548     1550       +2     
  Lines      106709   106975     +266     
  Branches    13308    13322      +14     
==========================================
+ Hits        57704    57888     +184     
- Misses      49005    49087      +82     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link

github-actions bot commented Mar 14, 2026

PR #43587: Size comparison from 2403f0a to 1d2a162

Full report (34 builds for bl602, bl616, bl702, bl702l, cc13x4_26x4, cc32xx, efr32, esp32, nrfconnect, nxp, psoc6, qpg, realtek, stm32, telink)
platform target config section 2403f0a 1d2a162 change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1090298 1090298 0 0.0
RAM 144778 144778 0 0.0
bl616 lighting-app bl616+thread FLASH 1100804 1100804 0 0.0
RAM 104216 104216 0 0.0
bl616+wifi+shell FLASH 1587676 1587676 0 0.0
RAM 98080 98080 0 0.0
bl702 lighting-app bl702+eth FLASH 1053234 1053234 0 0.0
RAM 108381 108381 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 891244 891244 0 0.0
RAM 105756 105756 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 780076 780076 0 0.0
RAM 103332 103332 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 787104 787104 0 0.0
RAM 108516 108516 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 733332 733332 0 0.0
RAM 97324 97324 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 716752 716752 0 0.0
RAM 97484 97484 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 558650 559010 360 0.1
RAM 204504 204504 0 0.0
lock CC3235SF_LAUNCHXL FLASH 591814 591814 0 0.0
RAM 204744 204744 0 0.0
efr32 lock-app BRD4187C FLASH 971764 971764 0 0.0
RAM 125828 125828 0 0.0
BRD4338a FLASH 769948 769948 0 0.0
RAM 236552 236552 0 0.0
window-app BRD4187C FLASH 1075368 1075360 -8 -0.0
RAM 126440 126440 0 0.0
esp32 all-clusters-app c3devkit DRAM 98372 98372 0 0.0
FLASH 1597182 1597540 358 0.0
IRAM 93514 93514 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 858144 858396 252 0.0
RAM 162019 162019 0 0.0
nxp contact mcxw71+release FLASH 736456 736456 0 0.0
RAM 66944 66944 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1709172 1709476 304 0.0
RAM 213948 213948 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1607868 1607972 104 0.0
RAM 210820 210820 0 0.0
light cy8ckit_062s2_43012 FLASH 1471620 1471620 0 0.0
RAM 196996 196996 0 0.0
lock cy8ckit_062s2_43012 FLASH 1497756 1497756 0 0.0
RAM 224740 224740 0 0.0
qpg lighting-app qpg6200+debug FLASH 841228 841228 0 0.0
RAM 127788 127788 0 0.0
lock-app qpg6200+debug FLASH 779920 779920 0 0.0
RAM 118736 118736 0 0.0
realtek light-switch-app rtl8777g FLASH 721176 721176 0 0.0
RAM 113456 113456 0 0.0
lighting-app rtl8777g FLASH 768912 768912 0 0.0
RAM 114696 114696 0 0.0
stm32 light STM32WB5MM-DK FLASH 479728 479728 0 0.0
RAM 141324 141324 0 0.0
telink bridge-app tl7218x FLASH 729288 729288 0 0.0
RAM 95772 95772 0 0.0
light-app-ota-compress-lzma-shell-factory-data tl3218x FLASH 853768 853768 0 0.0
RAM 44188 44188 0 0.0
tl7218x FLASH 845178 845178 0 0.0
RAM 99576 99576 0 0.0
light-switch-app-ota-compress-lzma-factory-data tl7218x_retention FLASH 726100 726100 0 0.0
RAM 55752 55752 0 0.0
light-switch-app-ota-compress-lzma-shell-factory-data tlsr9528a FLASH 788668 788668 0 0.0
RAM 74928 74928 0 0.0
light-switch-app-ota-factory-data tl3218x_retention FLASH 726094 726094 0 0.0
RAM 33232 33232 0 0.0
lighting-app-ota-factory-data tlsr9118bdk40d FLASH 617176 617176 0 0.0
RAM 118244 118244 0 0.0
lighting-app-ota-rpc-factory-data-4mb tlsr9518adk80d FLASH 843948 843952 4 0.0
RAM 97284 97284 0 0.0

Copy link

@Sum1cares Sum1cares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome

@andy31415
Copy link
Contributor

@lboue could you update the summary on why adding an automated test for this is not possible? Are we unable to add a test even via python automation tests?

We are trying hard to encourage automated testing

@lboue
Copy link
Contributor Author

lboue commented Mar 15, 2026

@lboue could you update the summary on why adding an automated test for this is not possible? Are we unable to add a test even via python automation tests?

We are trying hard to encourage automated testing

I'd really like to add a test but I am not a member of the CSA, so I do not have permission to update the Python test file. I don't know what step number to assign to a new test.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 15, 2026 07:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Thermostat cluster server preset handling so that invoking SetActivePreset applies the preset’s occupied setpoints before updating ActivePresetHandle, and improves error reporting for invalid handles and failed setpoint writes.

Changes:

  • Refactors preset lookup to a single-pass GetMatchingPresetInPresets(...) API that returns CHIP_ERROR + found.
  • Updates SetActivePreset to apply preset heating/cooling setpoints (with limit enforcement) before setting ActivePresetHandle, and returns InvalidCommand when the handle is not found.
  • Propagates setpoint write failures back to the command status and logs the failure.
Comments suppressed due to low confidence (1)

src/app/clusters/thermostat-server/thermostat-server-presets.cpp:356

  • SetActivePreset writes occupied setpoints before calling delegate->SetActivePresetHandle(...). If either setpoint write partially succeeds (e.g. cooling set succeeds but heating set fails) or SetActivePresetHandle fails, the command returns an error but the endpoint may already have modified setpoints, leaving state inconsistent with ActivePresetHandle. Consider making this operation transactional (e.g. read and cache existing occupied setpoints, revert on failure) so a non-success return doesn’t leave partial state applied.
    CHIP_ERROR err = delegate->SetActivePresetHandle(presetHandle);

    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(Zcl, "Failed to set ActivePresetHandle with error %" CHIP_ERROR_FORMAT, err.Format());

@github-actions
Copy link

github-actions bot commented Mar 15, 2026

PR #43587: Size comparison from 2403f0a to eddb5a0

Full report (33 builds for bl602, bl616, bl702, bl702l, cc13x4_26x4, cc32xx, efr32, nrfconnect, nxp, psoc6, qpg, realtek, stm32, telink)
platform target config section 2403f0a eddb5a0 change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1090298 1090298 0 0.0
RAM 144778 144778 0 0.0
bl616 lighting-app bl616+thread FLASH 1100804 1100804 0 0.0
RAM 104216 104216 0 0.0
bl616+wifi+shell FLASH 1587676 1587676 0 0.0
RAM 98080 98080 0 0.0
bl702 lighting-app bl702+eth FLASH 1053234 1053234 0 0.0
RAM 108381 108381 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 891244 891244 0 0.0
RAM 105756 105756 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 780076 780076 0 0.0
RAM 103332 103332 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 787104 787104 0 0.0
RAM 108516 108516 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 733332 733332 0 0.0
RAM 97324 97324 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 716752 716752 0 0.0
RAM 97484 97484 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 558650 559026 376 0.1
RAM 204504 204504 0 0.0
lock CC3235SF_LAUNCHXL FLASH 591814 591814 0 0.0
RAM 204744 204744 0 0.0
efr32 lock-app BRD4187C FLASH 971764 971764 0 0.0
RAM 125828 125828 0 0.0
BRD4338a FLASH 769948 769948 0 0.0
RAM 236552 236552 0 0.0
window-app BRD4187C FLASH 1075368 1075360 -8 -0.0
RAM 126440 126440 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 858144 858404 260 0.0
RAM 162019 162019 0 0.0
nxp contact mcxw71+release FLASH 736456 736456 0 0.0
RAM 66944 66944 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1709172 1709476 304 0.0
RAM 213948 213948 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1607868 1607972 104 0.0
RAM 210820 210820 0 0.0
light cy8ckit_062s2_43012 FLASH 1471620 1471620 0 0.0
RAM 196996 196996 0 0.0
lock cy8ckit_062s2_43012 FLASH 1497756 1497756 0 0.0
RAM 224740 224740 0 0.0
qpg lighting-app qpg6200+debug FLASH 841228 841228 0 0.0
RAM 127788 127788 0 0.0
lock-app qpg6200+debug FLASH 779920 779920 0 0.0
RAM 118736 118736 0 0.0
realtek light-switch-app rtl8777g FLASH 721176 721176 0 0.0
RAM 113456 113456 0 0.0
lighting-app rtl8777g FLASH 768912 768912 0 0.0
RAM 114696 114696 0 0.0
stm32 light STM32WB5MM-DK FLASH 479728 479728 0 0.0
RAM 141324 141324 0 0.0
telink bridge-app tl7218x FLASH 729288 729310 22 0.0
RAM 95772 95772 0 0.0
light-app-ota-compress-lzma-shell-factory-data tl3218x FLASH 853768 853790 22 0.0
RAM 44188 44188 0 0.0
tl7218x FLASH 845178 845200 22 0.0
RAM 99576 99576 0 0.0
light-switch-app-ota-compress-lzma-factory-data tl7218x_retention FLASH 726100 726122 22 0.0
RAM 55752 55752 0 0.0
light-switch-app-ota-compress-lzma-shell-factory-data tlsr9528a FLASH 788668 788690 22 0.0
RAM 74928 74928 0 0.0
light-switch-app-ota-factory-data tl3218x_retention FLASH 726094 726116 22 0.0
RAM 33232 33232 0 0.0
lighting-app-ota-factory-data tlsr9118bdk40d FLASH 617176 617198 22 0.0
RAM 118244 118244 0 0.0
lighting-app-ota-rpc-factory-data-4mb tlsr9518adk80d FLASH 843948 843974 26 0.0
RAM 97284 97284 0 0.0

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 15, 2026 08:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Thermostat “SetActivePreset” command handling so that selecting an active preset applies that preset’s occupied heating/cooling setpoints (with limit enforcement) and improves error handling when the preset handle is invalid or attribute writes fail.

Changes:

  • Refactors preset lookup to a single-pass helper (GetMatchingPresetInPresets) that returns CHIP_ERROR plus a found flag.
  • Updates ThermostatAttrAccess::SetActivePreset to apply preset setpoints (with Enforce*SetpointLimits) before updating ActivePresetHandle, and to return InvalidCommand when the handle doesn’t exist.
  • Propagates and logs failures when writing OccupiedCoolingSetpoint / OccupiedHeatingSetpoint.
Comments suppressed due to low confidence (1)

src/app/clusters/thermostat-server/thermostat-server-presets.cpp:356

  • SetActivePreset now writes occupied setpoints before calling delegate->SetActivePresetHandle(...). If SetActivePresetHandle fails, the method returns an error status even though it has already modified one or both setpoints, leaving the device in a partially-updated state (command failed but state changed). Consider making this operation atomic from the client’s perspective (e.g., capture and restore previous setpoints on failure, or defer setpoint writes until after ActivePresetHandle is successfully updated via a mechanism that can’t fail mid-way).
    CHIP_ERROR err = delegate->SetActivePresetHandle(presetHandle);

    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(Zcl, "Failed to set ActivePresetHandle with error %" CHIP_ERROR_FORMAT, err.Format());

@github-actions
Copy link

github-actions bot commented Mar 15, 2026

PR #43587: Size comparison from 2403f0a to cc54de0

Full report (21 builds for bl602, bl616, bl702, bl702l, cc13x4_26x4, cc32xx, nrfconnect, psoc6, qpg, realtek, stm32)
platform target config section 2403f0a cc54de0 change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1090298 1090298 0 0.0
RAM 144778 144778 0 0.0
bl616 lighting-app bl616+thread FLASH 1100804 1100804 0 0.0
RAM 104216 104216 0 0.0
bl616+wifi+shell FLASH 1587676 1587676 0 0.0
RAM 98080 98080 0 0.0
bl702 lighting-app bl702+eth FLASH 1053234 1053234 0 0.0
RAM 108381 108381 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 891244 891244 0 0.0
RAM 105756 105756 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 780076 780076 0 0.0
RAM 103332 103332 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 787104 787104 0 0.0
RAM 108516 108516 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 733332 733332 0 0.0
RAM 97324 97324 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 716752 716752 0 0.0
RAM 97484 97484 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 558650 559010 360 0.1
RAM 204504 204504 0 0.0
lock CC3235SF_LAUNCHXL FLASH 591814 591814 0 0.0
RAM 204744 204744 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 858144 858388 244 0.0
RAM 162019 162019 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1709172 1709476 304 0.0
RAM 213948 213948 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1607868 1607972 104 0.0
RAM 210820 210820 0 0.0
light cy8ckit_062s2_43012 FLASH 1471620 1471620 0 0.0
RAM 196996 196996 0 0.0
lock cy8ckit_062s2_43012 FLASH 1497756 1497756 0 0.0
RAM 224740 224740 0 0.0
qpg lighting-app qpg6200+debug FLASH 841228 841228 0 0.0
RAM 127788 127788 0 0.0
lock-app qpg6200+debug FLASH 779920 779920 0 0.0
RAM 118736 118736 0 0.0
realtek light-switch-app rtl8777g FLASH 721176 721176 0 0.0
RAM 113456 113456 0 0.0
lighting-app rtl8777g FLASH 768912 768912 0 0.0
RAM 114696 114696 0 0.0
stm32 light STM32WB5MM-DK FLASH 479728 479728 0 0.0
RAM 141324 141324 0 0.0

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Thermostat preset activation so that selecting an active preset applies the preset’s occupied heating/cooling setpoints (when present) and improves error handling for invalid preset handles and write failures.

Changes:

  • Refactors preset lookup into a single-pass GetMatchingPresetInPresets(...) that returns CHIP_ERROR plus a found out-param.
  • Updates SetActivePreset to apply preset occupied setpoints (with limit enforcement) before writing ActivePresetHandle, and to propagate underlying failures.
  • Updates pending-preset validation to use the new lookup helper.

Comment on lines +328 to +350
Optional<int16_t> coolingSetpointValue = matchingPreset.GetCoolingSetpoint();
if (coolingSetpointValue.HasValue())
{
int16_t constrainedCoolingSetpoint = EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint);
Status status = OccupiedCoolingSetpoint::Set(endpoint, constrainedCoolingSetpoint);
if (status != Status::Success)
{
ChipLogError(Zcl, "Failed to set OccupiedCoolingSetpoint with status %u", to_underlying(status));
return status;
}
}

Optional<int16_t> heatingSetpointValue = matchingPreset.GetHeatingSetpoint();
if (heatingSetpointValue.HasValue())
{
int16_t constrainedHeatingSetpoint = EnforceHeatingSetpointLimits(heatingSetpointValue.Value(), endpoint);
Status status = OccupiedHeatingSetpoint::Set(endpoint, constrainedHeatingSetpoint);
if (status != Status::Success)
{
ChipLogError(Zcl, "Failed to set OccupiedHeatingSetpoint with status %u", to_underlying(status));
return status;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-entrancy path is now guarded in SetActivePreset: while applying a non-null preset, we increment a scoped presetApplicationDepth, and any callback-driven SetActivePreset(..., nullopt) during that window returns early.
So setpoint writes can still trigger the attribute-changed callback, but they no longer clear ActivePresetHandle mid-application (including deadband-adjustment cascades), avoiding transient clears and extra churn/version bumps.

@github-actions
Copy link

github-actions bot commented Mar 15, 2026

PR #43587: Size comparison from 2403f0a to 44dab5c

Full report (34 builds for bl602, bl616, bl702, bl702l, cc13x4_26x4, cc32xx, efr32, esp32, nrfconnect, nxp, psoc6, qpg, realtek, stm32, telink)
platform target config section 2403f0a 44dab5c change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1090298 1090298 0 0.0
RAM 144778 144778 0 0.0
bl616 lighting-app bl616+thread FLASH 1100804 1100804 0 0.0
RAM 104216 104216 0 0.0
bl616+wifi+shell FLASH 1587676 1587676 0 0.0
RAM 98080 98080 0 0.0
bl702 lighting-app bl702+eth FLASH 1053234 1053234 0 0.0
RAM 108381 108381 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 891244 891244 0 0.0
RAM 105756 105756 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 780076 780076 0 0.0
RAM 103332 103332 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 787104 787104 0 0.0
RAM 108516 108516 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 733332 733332 0 0.0
RAM 97324 97324 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 716752 716752 0 0.0
RAM 97484 97484 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 558650 559146 496 0.1
RAM 204504 204504 0 0.0
lock CC3235SF_LAUNCHXL FLASH 591814 591814 0 0.0
RAM 204744 204744 0 0.0
efr32 lock-app BRD4187C FLASH 971764 971764 0 0.0
RAM 125828 125828 0 0.0
BRD4338a FLASH 769948 769948 0 0.0
RAM 236552 236552 0 0.0
window-app BRD4187C FLASH 1075368 1075360 -8 -0.0
RAM 126440 126440 0 0.0
esp32 all-clusters-app c3devkit DRAM 98372 98372 0 0.0
FLASH 1597182 1597602 420 0.0
IRAM 93514 93514 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 858144 858460 316 0.0
RAM 162019 162019 0 0.0
nxp contact mcxw71+release FLASH 736456 736456 0 0.0
RAM 66944 66944 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1709172 1709548 376 0.0
RAM 213948 213948 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1607868 1608044 176 0.0
RAM 210820 210820 0 0.0
light cy8ckit_062s2_43012 FLASH 1471620 1471620 0 0.0
RAM 196996 196996 0 0.0
lock cy8ckit_062s2_43012 FLASH 1497756 1497756 0 0.0
RAM 224740 224740 0 0.0
qpg lighting-app qpg6200+debug FLASH 841228 841228 0 0.0
RAM 127788 127788 0 0.0
lock-app qpg6200+debug FLASH 779920 779920 0 0.0
RAM 118736 118736 0 0.0
realtek light-switch-app rtl8777g FLASH 721176 721176 0 0.0
RAM 113456 113456 0 0.0
lighting-app rtl8777g FLASH 768912 768912 0 0.0
RAM 114696 114696 0 0.0
stm32 light STM32WB5MM-DK FLASH 479728 479728 0 0.0
RAM 141324 141324 0 0.0
telink bridge-app tl7218x FLASH 729288 729310 22 0.0
RAM 95772 95772 0 0.0
light-app-ota-compress-lzma-shell-factory-data tl3218x FLASH 853768 853790 22 0.0
RAM 44188 44188 0 0.0
tl7218x FLASH 845178 845200 22 0.0
RAM 99576 99576 0 0.0
light-switch-app-ota-compress-lzma-factory-data tl7218x_retention FLASH 726100 726122 22 0.0
RAM 55752 55752 0 0.0
light-switch-app-ota-compress-lzma-shell-factory-data tlsr9528a FLASH 788668 788690 22 0.0
RAM 74928 74928 0 0.0
light-switch-app-ota-factory-data tl3218x_retention FLASH 726094 726116 22 0.0
RAM 33232 33232 0 0.0
lighting-app-ota-factory-data tlsr9118bdk40d FLASH 617176 617198 22 0.0
RAM 118244 118244 0 0.0
lighting-app-ota-rpc-factory-data-4mb tlsr9518adk80d FLASH 843948 843974 26 0.0
RAM 97284 97284 0 0.0

@lboue lboue requested a review from Copilot March 15, 2026 10:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants