Skip to content

TempSensor: new device class, loadpoint temperature override for heating devices - #33673

Draft
andig wants to merge 4 commits into
masterfrom
feat/thermometer
Draft

TempSensor: new device class, loadpoint temperature override for heating devices#33673
andig wants to merge 4 commits into
masterfrom
feat/thermometer

Conversation

@andig

@andig andig commented Sep 11, 2026

Copy link
Copy Markdown
Member

Ref #33175

Adds a tempsensor device class. A temp sensor implements api.Battery (Soc = temperature in °C) from a configured temp plugin, so an external sensor (Shelly, Home Assistant, MQTT, ...) can drive a heating loadpoint whose charger has no temperature input.

Backend

  • templates.TempSensor class, templates/definition/tempsensor/ (Home Assistant template), custom yaml type with temp: plugin
  • config.TempSensors() registry, tempsensors: yaml key, setup/delete/dump wiring, config device API handlers (/config/devices/tempsensor)
  • Loadpoint tempSensor: reference (static config, API, settings key). When set, the sensor's temperature replaces the charger's api.Battery reading in publishSocAndRange; the charger's SocLimiter (limit temperature) is still used. Loadpoint delete/disable propagate to the owned sensor like the charge meter.

UI

  • TempSensorModal (template + custom yaml)
  • Loadpoint modal: "Add external temperature sensor" for heating devices, mirrors the charge meter flow
  • Config view: device list, status polling, disable propagation

Not included: Playwright test, evcc-docs user-defined-devices#tempsensor section.

🤖 Generated with Claude Code

@github-actions github-actions Bot added enhancement New feature or request devices Specific device support heating Heating labels Sep 11, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="server/http_config_device_handler.go" line_range="702-704" />
<code_context>
+			// cleanup references
+			for _, dev := range h.Devices() {
+				lp := dev.Instance()
+				if lp != nil && lp.GetThermometerRef() == config.NameForID(id) {
+					lp.SetThermometerRef("")
+				}
+			}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Deleting a thermometer clears only the loadpoint's configured reference; `SetThermometerRef` does not clear the already-loaded `lp.thermometer` instance, so the running loadpoint continues reading the deleted thermometer until it is restarted or reconstructed.

**Triggers:** When a thermometer referenced by a running loadpoint is deleted.

**Suggested fix:** Clear the runtime thermometer pointer as part of reference removal, or reload/reconfigure the affected loadpoint after deleting the device.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and this adds a persisted thermometer reference and uses its readings to override the heating device's temperature and SOC/limit reporting, so an incorrect reading could cause bounded but incorrect runtime heating or charging behavior. Reverting removes the new behavior, while any incorrect configuration already saved would need to be corrected or cleared.

Blocking findings: server/http_config_device_handler.go:704


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread server/http_config_device_handler.go Outdated
Comment on lines +702 to +704
if lp != nil && lp.GetThermometerRef() == config.NameForID(id) {
lp.SetThermometerRef("")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Deleting a thermometer clears only the loadpoint's configured reference; SetThermometerRef does not clear the already-loaded lp.thermometer instance, so the running loadpoint continues reading the deleted thermometer until it is restarted or reconstructed.

Triggers: When a thermometer referenced by a running loadpoint is deleted.

Suggested fix: Clear the runtime thermometer pointer as part of reference removal, or reload/reconfigure the affected loadpoint after deleting the device.

@andig andig changed the title Thermometer: new device class, loadpoint temperature override for heating devices TempSensor: new device class, loadpoint temperature override for heating devices Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Code review

Found one issue worth a look before merge.

core/loadpoint.go — charger limit-temperature fallback gets clobbered by the vehicle branch

evcc/core/loadpoint.go

Lines 2151 to 2172 in 20dc4d5

return socR, limitR, socErr
}
// tempsensor overrides charger temperature
var socR *float64
var limitR *int64
if lp.tempsensor != nil {
socR, limitR, _ = socAndLimit("tempsensor", lp.tempsensor)
// keep charger limit temperature
if socLimiter, ok := api.Cap[api.SocLimiter](lp.charger); ok && limitR == nil {
if limit, err := socLimiter.GetLimitSoc(); err == nil {
limitR = &limit
lp.publish(keys.VehicleLimitSoc, float64(limit))
}
}
} else {
socR, limitR, _ = socAndLimit("charger", lp.charger)
}
if socR == nil && (lp.vehicleSocPollAllowed() || lp.chargerHasFeature(api.IntegratedDevice)) {
var socErr error
socR, limitR, socErr = socAndLimit("vehicle", lp.GetVehicle())

When lp.tempsensor is set, the new code deliberately preserves the charger's SocLimiter.GetLimitSoc() result in limitR when the temp sensor doesn't report one (lines 2160–2166, "keep charger limit temperature"). But if the temp sensor read itself fails, socR stays nil, and the very next block:

if socR == nil && (lp.vehicleSocPollAllowed() || lp.chargerHasFeature(api.IntegratedDevice)) {
    var socErr error
    socR, limitR, socErr = socAndLimit("vehicle", lp.GetVehicle())

unconditionally overwrites limitR. vehicleSocPollAllowed() returns true whenever lp.charging() is true — regardless of whether a vehicle is actually configured — which is the normal case for a heating loadpoint. With no vehicle, lp.GetVehicle() is nil, so socAndLimit("vehicle", nil) returns (nil, nil, nil), resetting limitR back to nil and undoing the charger-limit fallback that was just computed.

Net effect: on a heating loadpoint with a temp sensor configured, a single transient temp-sensor read error while charging silently discards the charger's limit temperature for that cycle (affects apiLimitSoc/remaining-duration math, since keys.VehicleLimitSoc was already published before the reset). This looks reachable in routine operation, not just a rare edge case.

@andig
andig marked this pull request as draft September 11, 2026 16:36
@andig

andig commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

@naltatis not sure if this is worth it?

andig and others added 4 commits September 11, 2026 18:37
…ting devices

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devices Specific device support enhancement New feature or request heating Heating

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant