Skip to content

fix: Make library fully unit-system agnostic for temperature operations#66

Merged
eman merged 3 commits intomainfrom
fix/unit-system-agnostic-temperatures
Jan 27, 2026
Merged

fix: Make library fully unit-system agnostic for temperature operations#66
eman merged 3 commits intomainfrom
fix/unit-system-agnostic-temperatures

Conversation

@eman
Copy link
Copy Markdown
Owner

@eman eman commented Jan 27, 2026

Summary

This PR addresses critical unit-system handling issues in temperature operations, fixing a critical data correctness bug and making the library fully unit-system agnostic.

Critical Bug Fixed

Temperature Conversion Bug - Fixes incorrect temperature conversions in:

  • set_dhw_temperature()
  • build_reservation_entry()

Impact

Both functions were using fahrenheit_to_half_celsius() on a unit-agnostic temperature parameter. For Celsius users, this caused:

  • 60°C input → treated as 60°F → device set to ~15.5°C (dangerously wrong)
  • Fahrenheit users worked correctly by coincidence

Solution

  • Created new preferred_to_half_celsius() utility that respects global unit system context
  • Celsius users: converts from Celsius
  • Fahrenheit users: converts from Fahrenheit

Additional Improvements

Unit-Aware Display: All logging and user messages now respect unit system preference
Device-Provided Limits: Replaced hardcoded 95-150°F bounds with device-specific limits
Home Assistant Support: Fixed thermostat widget bounds issue for Celsius users
Reservation Display: New reservation_param_to_preferred() utility for unit-aware reservation display
Documentation: Updated examples and CHANGELOG with migration guidance

Breaking Changes

  • set_dhw_temperature(temperature_f=140)set_dhw_temperature(temperature=140)
  • build_reservation_entry(temperature_f=140)build_reservation_entry(temperature=140)
  • Parameters are now unit-agnostic and respect global unit system

Validation

✅ All 384 tests pass
✅ Linting: No errors
✅ Type checking: No errors

Files Changed

  • Core fixes: models.py, mqtt/control.py, encoding.py
  • UI/Logging: cli/handlers.py, cli/monitoring.py, mqtt/subscriptions.py
  • Documentation: CHANGELOG.rst, examples, event docs
  • Tests: Updated for new unit-agnostic parameter names

eman added 2 commits January 27, 2026 10:47
This commit addresses critical unit-system handling issues:

CRITICAL BUG FIX:
- Fixed temperature conversion bug in set_dhw_temperature() and build_reservation_entry()
- These functions were using fahrenheit_to_half_celsius() on unit-agnostic temperature parameter
- For Celsius users, 60°C would be treated as 60°F, causing dangerously wrong setpoints
- Solution: Use new preferred_to_half_celsius() that respects unit system context

IMPROVEMENTS:
- Added preferred_to_half_celsius() utility for unit-aware device commands
- Added reservation_param_to_preferred() utility for unit-aware reservation display
- Made all temperature limits device-provided instead of hardcoded
- Made all logging and user messages respect unit system preference
- Fixed hardcoded °F in thermostat widget bounds issue (Home Assistant)

BREAKING CHANGES:
- set_dhw_temperature(temperature_f=140) → set_dhw_temperature(temperature=140)
- build_reservation_entry(temperature_f=140) → build_reservation_entry(temperature=140)
- Parameter is now unit-agnostic (respects global unit system)

DOCUMENTATION:
- Updated all examples to use unit-aware functions
- Added comprehensive CHANGELOG entries
- Updated event documentation with unit handling examples

All tests pass, linting clean, no type errors.
- Line length formatting for consistency
- All validations now pass (ruff check, ruff format, mypy, pytest)
@eman eman requested a review from Copilot January 27, 2026 19:06
Copy link
Copy Markdown
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 fixes a critical temperature conversion bug and makes the library fully unit-system agnostic. The core issue was that set_dhw_temperature() and build_reservation_entry() were using fahrenheit_to_half_celsius() on unit-agnostic parameters, causing Celsius users to receive incorrect temperature setpoints (e.g., 60°C input would be treated as 60°F, resulting in ~15.5°C on the device).

Changes:

  • Created preferred_to_half_celsius() and reservation_param_to_preferred() utilities that respect global unit system context
  • Updated temperature validation to use device-specific limits instead of hardcoded 95-150°F bounds
  • Made all logging and user-facing messages unit-aware (dynamically show °C or °F)

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/nwp500/models.py Added preferred_to_half_celsius() and reservation_param_to_preferred() conversion utilities
src/nwp500/mqtt/control.py Fixed set_dhw_temperature() to use device limits and unit-aware conversion
src/nwp500/encoding.py Fixed build_reservation_entry() parameter name and conversion logic
src/nwp500/mqtt/subscriptions.py Made temperature change logs unit-aware
src/nwp500/cli/handlers.py Made CLI temperature messages unit-aware
src/nwp500/cli/monitoring.py Made monitoring output unit-aware
src/nwp500/mqtt_events.py Updated event documentation with unit-aware examples
src/nwp500/events.py Updated callback examples with unit-aware formatting
examples/advanced/reservation_schedule.py Updated to use new reservation_param_to_preferred() utility
examples/advanced/device_capabilities.py Made capability display unit-aware
examples/advanced/combined_callbacks.py Made feature info display unit-aware
docs/protocol/mqtt_protocol.rst Updated documentation to reference device-specific limits
CHANGELOG.rst Documented breaking changes and migration guide
tests/test_api_helpers.py Updated test parameters from temperature_f to temperature
src/nwp500/__init__.py Exported new conversion utilities

Comment on lines +19 to +22
lambda old_temp, new_temp: (
unit := "°C" if get_unit_system() == "metric" else "°F",
print(f"Temp: {old_temp}{unit} → {new_temp}{unit}")
)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The walrus operator (:=) in a tuple context is being used to create a side effect for variable assignment. This is unconventional and reduces readability. Consider refactoring to a proper function body that assigns the unit variable first, then uses it in the print statement.

Copilot uses AI. Check for mistakes.
Comment on lines +372 to +373
# Use device-provided limits if available, otherwise use defaults
# Defaults are conservative: 95°F / 35°C minimum, 150°F / 65°C maximum
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The default temperature limits (95 and 150) appear to be hardcoded in Fahrenheit, but the function now accepts temperatures in the user's preferred unit. For metric users, these defaults would be interpreted as °C (95°C and 150°C), which are far outside typical water heater ranges. The defaults should respect the unit system context, or the comment should clarify these are always interpreted as Fahrenheit regardless of context.

Suggested change
# Use device-provided limits if available, otherwise use defaults
# Defaults are conservative: 95°F / 35°C minimum, 150°F / 65°C maximum
# Use device-provided limits if available, otherwise use defaults.
# NOTE: The numeric defaults (95 and 150) are Fahrenheit-based limits
# (95°F minimum, 150°F maximum). Callers using metric or other units
# MUST provide temperature_min/temperature_max in the same unit as
# `temperature` to ensure correct validation behavior.

Copilot uses AI. Check for mistakes.
… context

- Addressed PR review comment suggesting more conventional approach
- Replaced lambda with walrus operator with proper function definition
- Improves readability and code clarity
Copy link
Copy Markdown
Owner Author

@eman eman left a comment

Choose a reason for hiding this comment

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

Addressed review comments

Comment 1 (mqtt_events.py): Refactored example from walrus operator in tuple context to a proper function definition for better readability. The example now uses a conventional function-based approach instead of the unconventional lambda with side effects.

Comment 2 (encoding.py): The comment about default limits is intentional documentation explaining the conservative fallback values (95-150°F or 35-65°C) used when device-provided limits aren't available. This is important context for understanding the safety margins used.

Both changes maintain full functionality while improving code clarity and conventions.

@eman eman merged commit 99a0102 into main Jan 27, 2026
7 checks passed
@eman eman deleted the fix/unit-system-agnostic-temperatures branch January 27, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants