Skip to content

[web_server] Add climate preset, fan mode, and humidity support#14061

Merged
bdraco merged 1 commit intoesphome:devfrom
espforge:web-server-climate-preset
Feb 28, 2026
Merged

[web_server] Add climate preset, fan mode, and humidity support#14061
bdraco merged 1 commit intoesphome:devfrom
espforge:web-server-climate-preset

Conversation

@rwagoner
Copy link
Contributor

@rwagoner rwagoner commented Feb 18, 2026

What does this implement/fix?

The web server climate card had no way to view or change presets or fan modes, and current humidity was not displayed.

  • Add preset/custom_preset dropdown support to the climate REST API
  • Fix pre-existing bug where fan_modes trait list was gated on get_supported_custom_fan_modes() instead of get_supports_fan_modes()
  • Always list presets/custom_presets options in detail_all regardless of current state
  • Add current_humidity to climate state JSON when the device supports it

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Developer breaking change (an API change that could break external components)
  • Code quality improvements to existing code or addition of tests
  • Other

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).
    Tests in tests/components/web_server/ are compilation validation only

@github-actions
Copy link
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#14061
    components: [web_server]
    refresh: 1h

(Added by the PR bot)

@esphome esphome bot added component: web_server developer-breaking-change This PR includes changes that break APIs that are potentially used by external components. needs-docs needs-tests new-feature labels Feb 18, 2026
@codecov-commenter
Copy link

codecov-commenter commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.11%. Comparing base (7e11817) to head (ccf1f41).
⚠️ Report is 5 commits behind head on dev.

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #14061   +/-   ##
=======================================
  Coverage   74.11%   74.11%           
=======================================
  Files          55       55           
  Lines       11590    11590           
  Branches     1578     1578           
=======================================
  Hits         8590     8590           
  Misses       2598     2598           
  Partials      402      402           

☔ 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.

@bdraco bdraco marked this pull request as draft February 18, 2026 21:27
@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

Additionally, optional fields like preset and fan_mode were omitted from SSE state events when unset, causing the frontend's Object.assign() merge to leave stale values from a previous state.

That sounds like a bug in the frontend

@rwagoner
Copy link
Contributor Author

Additionally, optional fields like preset and fan_mode were omitted from SSE state events when unset, causing the frontend's Object.assign() merge to leave stale values from a previous state.

That sounds like a bug in the frontend

It's arguably more of a protocol design issue than a frontend bug. SSE state events are designed as partial updates merged via Object.assign(). The issue is that when an optional field like preset transitions from set → unset, omitting it from the payload gives the frontend no signal to clear the previous value. The frontend would need to track which fields are expected per entity type and explicitly null missing ones, which adds complexity and couples the frontend tightly to backend schema knowledge.

Always including supported fields (with empty string as the "unset" sentinel) keeps the contract simple: if a trait is supported, the field is always present in the payload, and the frontend can rely on the value it receives.

Also see the discord thread
https://discord.com/channels/429907082951524364/1473752675106422824

@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

That seems like a design mismatch. If we are expecting partial updates and we aren't comparing against the previous state, we end up sending everything anyways

@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

It looks like this puts us out of sync with the REST API

@rwagoner
Copy link
Contributor Author

That seems like a design mismatch. If we are expecting partial updates and we aren't comparing against the previous state, we end up sending everything anyways

You're right — other components (fan, cover, valve) omit optional fields when not supported/set. Climate is unique in having mutually exclusive optional pairs (fan_mode/custom_fan_mode, preset/custom_preset) which other entity types don't have. Happy to align the approach if there's a preferred pattern — should the frontend handle clearing stale values for missing keys instead?

@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

We probably need to do something with the frontend for climate to minimize the payload coming from the backend since we keep allocating larger and larger json payloads on the device to the point we will start to have a problem

@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

Also we are trying to fix the heap churn issue with the web_server with long connected SSE in #13625 so if we can keep the climate payload under 512 bytes it will never allocate once that PR is merged

@rwagoner rwagoner force-pushed the web-server-climate-preset branch from 66518d5 to 5f6754a Compare February 18, 2026 22:46
@esphome esphome bot added the small-pr PR < 30 lines label Feb 18, 2026
@bdraco
Copy link
Member

bdraco commented Feb 18, 2026

We are sending min_temp/max_temp/step every time as well and we don't need to. And for water heater its wrong...

bdraco added a commit that referenced this pull request Feb 18, 2026
Move min/max temperature and step traits into DETAIL_ALL block
so they are only sent once on initial connection instead of on
every state update. Rename keys from min_temperature/max_temperature
to min_temp/max_temp to match what the frontend expects.

Discovered while reviewing #14061.
@rwagoner rwagoner force-pushed the web-server-climate-preset branch from 5f6754a to 8d3f4c5 Compare February 19, 2026 03:03
@esphome esphome bot added bugfix and removed developer-breaking-change This PR includes changes that break APIs that are potentially used by external components. labels Feb 19, 2026
@bdraco
Copy link
Member

bdraco commented Feb 19, 2026

note that clang-format is still failing https://github.com/esphome/esphome/actions/runs/22166826625/job/64095845122?pr=14061

@rwagoner
Copy link
Contributor Author

Reworked based on feedback. The PR now only touches the backend minimally. I updated description with the changes and reasoning.

- Add preset/custom_preset to climate REST API (parse_string_param_ for
  preset in handle_climate_request)
- Fix fan_modes trait gate: use get_supports_fan_modes() instead of
  get_supported_custom_fan_modes().empty()
- Always list presets/custom_presets options in DETAIL_ALL regardless
  of current state
- Add current_humidity to climate state JSON when supported
@github-actions
Copy link
Contributor

Memory Impact Analysis

Components: web_server
Platform: esp8266-ard

Metric Target Branch This PR Change
RAM 32,804 bytes 32,804 bytes ➡️ +0 bytes (0.00%)
Flash 412,937 bytes 412,937 bytes ➡️ +0 bytes (0.00%)

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

@rwagoner
Copy link
Contributor Author

For reference the frontend PR: esphome/esphome-webserver#191

@bdraco
Copy link
Member

bdraco commented Feb 19, 2026

Current version LGTM

@bdraco bdraco marked this pull request as ready for review February 19, 2026 15:31
Copilot AI review requested due to automatic review settings February 19, 2026 15: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.

Pull request overview

This PR enhances the web_server REST/JSON interface for climate entities so the web UI (and other REST clients) can view and control additional climate capabilities, and receive current humidity when supported.

Changes:

  • Add REST set support for climate preset (string-based, covering standard + custom presets).
  • Fix fan_modes trait JSON gating to correctly depend on overall fan-mode support.
  • Always include presets/custom_presets option lists in DETAIL_ALL, and include current_humidity in state JSON when supported.

@bdraco bdraco self-requested a review February 27, 2026 06:51
Copy link
Member

@bdraco bdraco left a comment

Choose a reason for hiding this comment

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

Thanks @rwagoner

@bdraco bdraco merged commit d1b4813 into esphome:dev Feb 28, 2026
45 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Mar 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants