Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 9 additions & 24 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ Python library for Navien NWP500 Heat Pump Water Heater

A Python library for monitoring and controlling the Navien NWP500 Heat Pump Water Heater through the Navilink cloud service. This library provides comprehensive access to device status, temperature control, operation mode management, and real-time monitoring capabilities.

**Documentation:** https://nwp500-python.readthedocs.io/

**Source Code:** https://github.com/eman/nwp500-python

Features
========

* **Device Monitoring**: Access real-time status information including temperatures, power consumption, and tank charge level
* **Temperature Control**: Set target water temperature (100-140°F)
* **Temperature Control**: Set target water temperature (90-151°F)
* **Operation Mode Control**: Switch between Heat Pump, Energy Saver, High Demand, Electric, and Vacation modes
* **Reservation Management**: Schedule automatic temperature and mode changes
* **Time of Use (TOU)**: Configure energy pricing schedules for demand response
Expand Down Expand Up @@ -183,7 +187,7 @@ Operation Modes
- 5
- Suspends heating to save energy during extended absences.

**Important:** When you set a mode, you're configuring the ``dhwOperationSetting`` (what mode to use when heating). The device's current operational state is reported in ``operationMode`` (0=Standby, 32=Heat Pump active, 64=Energy Saver active, 96=High Demand active). See the `Device Status Fields documentation <docs/DEVICE_STATUS_FIELDS.rst>`_ for details on this distinction.
**Important:** When you set a mode, you're configuring the ``dhwOperationSetting`` (what mode to use when heating). The device's current operational state is reported in ``operationMode`` (0=Standby, 32=Heat Pump active, 64=Energy Saver active, 96=High Demand active).

MQTT Protocol
=============
Expand All @@ -210,25 +214,10 @@ The library supports low-level MQTT communication with Navien devices:
* Reservation information
* TOU settings

See the full `MQTT Protocol Documentation`_ for detailed message formats.

Documentation
=============

Comprehensive documentation is available in the ``docs/`` directory:

* `Device Status Fields`_ - Complete field reference with units and conversions
* `Device Feature Fields`_ - Device capabilities and firmware information reference
* `MQTT Messages`_ - MQTT protocol documentation
* `MQTT Client`_ - MQTT client usage guide
* `Authentication`_ - Authentication module documentation

.. _MQTT Protocol Documentation: docs/MQTT_MESSAGES.rst
.. _Device Status Fields: docs/DEVICE_STATUS_FIELDS.rst
.. _Device Feature Fields: docs/DEVICE_FEATURE_FIELDS.rst
.. _MQTT Messages: docs/MQTT_MESSAGES.rst
.. _MQTT Client: docs/MQTT_CLIENT.rst
.. _Authentication: docs/AUTHENTICATION.rst
For detailed information on device status fields, MQTT protocol, authentication, and more, see the complete documentation at https://nwp500-python.readthedocs.io/

Data Models
===========
Expand Down Expand Up @@ -258,7 +247,7 @@ Requirements

Development
===========
To set up a development environment, clone the repository and install the required dependencies:
To set up a development environment:

.. code-block:: bash

Expand Down Expand Up @@ -287,14 +276,10 @@ To ensure your local linting matches CI exactly:
# Auto-fix and format
tox -e format

For detailed linting setup instructions, see `LINTING_SETUP.md <LINTING_SETUP.md>`_.

For comprehensive development guide, see `DEVELOPMENT.md <DEVELOPMENT.md>`_.

License
=======

This project is licensed under the MIT License - see the `LICENSE.txt <LICENSE.txt>`_ file for details.
This project is licensed under the MIT License.

Author
======
Expand Down
5 changes: 3 additions & 2 deletions docs/guides/reservations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ Request the current reservation schedule from the device:

import asyncio
from typing import Any
from nwp500 import decode_week_bitfield

async def read_schedule():
async with NavienAuthClient(email, password) as auth:
Expand Down Expand Up @@ -327,7 +328,7 @@ Request the current reservation schedule from the device:
print(f"Number of entries: {len(entries)}")

for idx, entry in enumerate(entries, 1):
days = NavienAPIClient.decode_week_bitfield(
days = decode_week_bitfield(
entry.get("week", 0)
)
hour = entry.get("hour", 0)
Expand Down Expand Up @@ -633,7 +634,7 @@ Full working example with error handling and response monitoring:
print(f"Active entries: {len(entries)}\n")

for idx, entry in enumerate(entries, 1):
days = NavienAPIClient.decode_week_bitfield(
days = decode_week_bitfield(
entry["week"]
)
print(f"Entry {idx}: {entry['hour']:02d}:"
Expand Down
49 changes: 27 additions & 22 deletions docs/guides/time_of_use.rst
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,13 @@ Building TOU Periods
Helper Methods
~~~~~~~~~~~~~~

The ``NavienAPIClient`` class provides helper methods for building TOU period configurations:
The library provides helper functions for building TOU period configurations:

build_tou_period()
""""""""""""""""""

.. code-block:: python

@staticmethod
def build_tou_period(
season_months: Union[List[int], range],
week_days: List[str],
Expand Down Expand Up @@ -331,7 +330,6 @@ encode_price()

.. code-block:: python

@staticmethod
def encode_price(price: float, decimal_point: int = 5) -> int

Encodes a floating-point price into an integer for transmission.
Expand All @@ -340,16 +338,17 @@ Encodes a floating-point price into an integer for transmission.

.. code-block:: python

from nwp500 import encode_price

# Encode $0.45000 per kWh
encoded = NavienAPIClient.encode_price(0.45, decimal_point=5)
encoded = encode_price(0.45, decimal_point=5)
# Returns: 45000

decode_price()
""""""""""""""

.. code-block:: python

@staticmethod
def decode_price(encoded_price: int, decimal_point: int = 5) -> float

Decodes an integer price back to floating-point.
Expand All @@ -358,16 +357,17 @@ Decodes an integer price back to floating-point.

.. code-block:: python

from nwp500 import decode_price

# Decode price from device
price = NavienAPIClient.decode_price(45000, decimal_point=5)
price = decode_price(45000, decimal_point=5)
# Returns: 0.45

encode_week_bitfield()
""""""""""""""""""""""

.. code-block:: python

@staticmethod
def encode_week_bitfield(day_names: List[str]) -> int

Encodes a list of day names into a bitfield.
Expand All @@ -386,8 +386,10 @@ Encodes a list of day names into a bitfield.

.. code-block:: python

from nwp500 import encode_week_bitfield

# Weekdays only
bitfield = NavienAPIClient.encode_week_bitfield([
bitfield = encode_week_bitfield([
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
])
# Returns: 0b0111110 = 62
Expand All @@ -397,7 +399,6 @@ decode_week_bitfield()

.. code-block:: python

@staticmethod
def decode_week_bitfield(bitfield: int) -> List[str]

Decodes a bitfield back into a list of day names.
Expand All @@ -406,8 +407,10 @@ Decodes a bitfield back into a list of day names.

.. code-block:: python

from nwp500 import decode_week_bitfield

# Decode weekday bitfield
days = NavienAPIClient.decode_week_bitfield(62)
days = decode_week_bitfield(62)
# Returns: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

Usage Examples
Expand All @@ -421,7 +424,7 @@ Configure two rate periods - off-peak and peak pricing:
.. code-block:: python

import asyncio
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient, build_tou_period

async def configure_simple_tou():
async with NavienAuthClient("user@example.com", "password") as auth_client:
Expand All @@ -446,7 +449,7 @@ Configure two rate periods - off-peak and peak pricing:
controller_serial = feature.controllerSerialNumber

# Define off-peak period (midnight to 2 PM, weekdays)
off_peak = NavienAPIClient.build_tou_period(
off_peak = build_tou_period(
season_months=range(1, 13), # All months
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=0,
Expand All @@ -459,7 +462,7 @@ Configure two rate periods - off-peak and peak pricing:
)

# Define peak period (3 PM to 8 PM, weekdays)
peak = NavienAPIClient.build_tou_period(
peak = build_tou_period(
season_months=range(1, 13),
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=15,
Expand Down Expand Up @@ -502,7 +505,7 @@ Configure different rates for summer and winter:
# ... get controller_serial (same as Example 1) ...

# Summer off-peak (June-September, all day except 2-8 PM)
summer_off_peak = NavienAPIClient.build_tou_period(
summer_off_peak = build_tou_period(
season_months=[6, 7, 8, 9],
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=0,
Expand All @@ -515,7 +518,7 @@ Configure different rates for summer and winter:
)

# Summer peak (June-September, 2-8 PM)
summer_peak = NavienAPIClient.build_tou_period(
summer_peak = build_tou_period(
season_months=[6, 7, 8, 9],
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=14,
Expand All @@ -528,7 +531,7 @@ Configure different rates for summer and winter:
)

# Winter rates (October-May)
winter_off_peak = NavienAPIClient.build_tou_period(
winter_off_peak = build_tou_period(
season_months=[10, 11, 12, 1, 2, 3, 4, 5],
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=0,
Expand All @@ -540,7 +543,7 @@ Configure different rates for summer and winter:
decimal_point=5
)

winter_peak = NavienAPIClient.build_tou_period(
winter_peak = build_tou_period(
season_months=[10, 11, 12, 1, 2, 3, 4, 5],
week_days=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
start_hour=17,
Expand Down Expand Up @@ -571,6 +574,8 @@ Query the device for its current TOU configuration:

.. code-block:: python

from nwp500 import decode_week_bitfield, decode_price

async def check_tou_settings():
async with NavienAuthClient("user@example.com", "password") as auth_client:
api_client = NavienAPIClient(auth_client=auth_client)
Expand All @@ -593,12 +598,12 @@ Query the device for its current TOU configuration:
print(f"Number of periods: {len(periods)}")

for i, period in enumerate(periods, 1):
days = NavienAPIClient.decode_week_bitfield(period.get("week", 0))
price_min = NavienAPIClient.decode_price(
days = decode_week_bitfield(period.get("week", 0))
price_min = decode_price(
period.get("priceMin", 0),
period.get("decimalPoint", 0)
)
price_max = NavienAPIClient.decode_price(
price_max = decode_price(
period.get("priceMax", 0),
period.get("decimalPoint", 0)
)
Expand Down Expand Up @@ -657,7 +662,7 @@ data from the OpenEI API and configuring it on your device:

import asyncio
import aiohttp
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient, build_tou_period

OPENEI_API_URL = "https://api.openei.org/utility_rates"
OPENEI_API_KEY = "DEMO_KEY" # Get your own key at openei.org
Expand Down Expand Up @@ -734,7 +739,7 @@ data from the OpenEI API and configuring it on your device:
# Convert to TOU format
weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
return [
NavienAPIClient.build_tou_period(
build_tou_period(
season_months=range(1, 13),
week_days=weekdays,
start_hour=p["start_hour"],
Expand Down
18 changes: 0 additions & 18 deletions docs/python_api/constants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,24 +278,6 @@ Checking Command Types
if is_control_command(CommandCode.POWER_ON):
print("This is a control command")

Backward Compatibility
======================

Legacy constant names are supported for backward compatibility:

.. code-block:: python

# Old names (still work)
CMD_STATUS_REQUEST = CommandCode.STATUS_REQUEST
CMD_DEVICE_INFO_REQUEST = CommandCode.DEVICE_INFO_REQUEST
CMD_POWER_ON = CommandCode.POWER_ON
CMD_POWER_OFF = CommandCode.POWER_OFF
# ... etc

# Prefer new enum-based names
from nwp500.constants import CommandCode
CommandCode.STATUS_REQUEST

Firmware Version Constants
===========================

Expand Down
14 changes: 5 additions & 9 deletions examples/anti_legionella_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@
from typing import Any

from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient
from nwp500.constants import (
CMD_ANTI_LEGIONELLA_DISABLE,
CMD_ANTI_LEGIONELLA_ENABLE,
CMD_STATUS_REQUEST,
)
from nwp500.constants import CommandCode


def display_anti_legionella_status(status: dict[str, Any], label: str = "") -> None:
Expand Down Expand Up @@ -119,7 +115,7 @@ def on_status(topic: str, message: dict[str, Any]) -> None:
print("STEP 1: Getting initial Anti-Legionella status...")
print("=" * 70)
status_received.clear()
expected_command = CMD_STATUS_REQUEST
expected_command = CommandCode.STATUS_REQUEST
await mqtt_client.request_device_status(device)

try:
Expand All @@ -137,7 +133,7 @@ def on_status(topic: str, message: dict[str, Any]) -> None:
print("STEP 2: Enabling Anti-Legionella cycle every 7 days...")
print("=" * 70)
status_received.clear()
expected_command = CMD_ANTI_LEGIONELLA_ENABLE
expected_command = CommandCode.ANTI_LEGIONELLA_ENABLE
await mqtt_client.enable_anti_legionella(device, period_days=7)

try:
Expand All @@ -155,7 +151,7 @@ def on_status(topic: str, message: dict[str, Any]) -> None:
print("WARNING: This reduces protection against Legionella bacteria!")
print("=" * 70)
status_received.clear()
expected_command = CMD_ANTI_LEGIONELLA_DISABLE
expected_command = CommandCode.ANTI_LEGIONELLA_DISABLE
await mqtt_client.disable_anti_legionella(device)

try:
Expand All @@ -172,7 +168,7 @@ def on_status(topic: str, message: dict[str, Any]) -> None:
print("STEP 4: Re-enabling Anti-Legionella with 14-day cycle...")
print("=" * 70)
status_received.clear()
expected_command = CMD_ANTI_LEGIONELLA_ENABLE
expected_command = CommandCode.ANTI_LEGIONELLA_ENABLE
await mqtt_client.enable_anti_legionella(device, period_days=14)

try:
Expand Down
4 changes: 1 addition & 3 deletions examples/exception_handling_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,7 @@ async def example_validation_errors():

# Try to set invalid vacation days
try:
await mqtt.set_dhw_operation_setting(
device, mode_id=5, vacation_days=50
)
await mqtt.set_dhw_mode(device, mode_id=5, vacation_days=50)
except RangeValidationError as e:
print(f"✓ Caught RangeValidationError: {e}")
print(f" Field: {e.field}")
Expand Down
Loading