Metadata-Version: 2.4
Name: hoymiles-wifi
Version: 0.7.0
Summary: Library to communicate with Hoymiles DTUs and HMS WiFi enabled microinverters via protobuf.
Author: suaveolent
License-Expression: MIT
Project-URL: Homepage, https://github.com/suaveolent/hoymiles-wifi
Project-URL: Issues, https://github.com/suaveolent/hoymiles-wifi/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: protobuf>=5.29.3
Requires-Dist: crcmod>=1.7
Requires-Dist: cryptography>=39.0.1
Dynamic: license-file

# hoymiles-wifi

This Python library enables local communication with Hoymiles DTUs, HMS-XXXXW microinverters, and hybrid inverters using protobuf messages.

For the Home Assistant integration have a look here:
https://github.com/suaveolent/ha-hoymiles-wifi

> [!NOTE]
> Disclaimer: This library is not affiliated with Hoymiles. It is an independent project developed to provide tools for interacting with Hoymiles DTUs and Hoymiles HMS-XXXXW series micro-inverters featuring integrated WiFi DTU. Any trademarks or product names mentioned are the property of their respective owners.

## Supported Devices

The library was successfully tested with:

- Hoymiles HMS-400W-1T
- Hoymiles HMS-800W-2T
- Hoymiles HMS-1000W-2T
- Hoymiles HMS-2000DW-4T
- Hoymiles DTU-WLite
- Hoymiles DTU-Pro (S)
- Hoymiles HAS-5.0LV-EUG1
- Hoymiles HYS-4.6LV-EUG1
- Hoymiles HYT-5.0HV-EUG1
- Hoymiles HAT-8.0HV-EUG1
- Solenso H-1000 (not tested for command, only to get data)
- Solenso DTU_SLS (not tested for command, only to get data)

## Installation

### Quick install (system-wide or existing environment)

```bash
pip install hoymiles-wifi
```

### Isolated install with a virtual environment (recommended)

A virtual environment keeps the library and its dependencies isolated from your
system Python and other projects. Works on Linux, macOS, and Windows.

**Linux / macOS**

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install hoymiles-wifi
```

**Windows (PowerShell)**

```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install hoymiles-wifi
```

**Windows (Command Prompt)**

```cmd
python -m venv .venv
.\.venv\Scripts\activate.bat
python -m pip install --upgrade pip
pip install hoymiles-wifi
```

After activation, the `hoymiles-wifi` command is available inside the venv.
To leave the environment, run `deactivate`.

**Editable install from a cloned source tree** (for development):

```bash
python3 -m venv .venv
source .venv/bin/activate        # Windows: .\.venv\Scripts\Activate.ps1
pip install -e .
```

## Usage

You can integrate the library into your own project, or simply use it in the command line.

## Warning

> [!CAUTION]
> Please refrain from using the persistent percentage power limit (`set-power-limit`) for zero feed-in, as frequent writes may damage the EEPROM. Use `set-power-limit-temporary` instead.

### Command line:

```
hoymiles-wifi --host HOST <command> [additional-arguments]
```

| Command                         | Device Class                           | Description                                                      |
| ------------------------------- | -------------------------------------- | ---------------------------------------------------------------- |
| get-real-data-new               | DTU and W-series                       | Retrieve real-time data                                          |
| get-real-data                   | DTU and W-series                       | Retrieve real-time data                                          |
| get-config                      | DTU and W-series                       | Retrieve configuration information                               |
| get-grid-profile                | DTU and W-series                       | Read the raw grid-configuration protobuf (`--inverter-serial` required) |
| get-grid-profile-decoded        | DTU and W-series                       | Read and decode microinverter grid-profile settings (`--inverter-serial` required) |
| network-info                    | DTU and W-series                       | Retrieve network information                                     |
| app-information-data            | DTU and W-series                       | Retrieve application information data                            |
| app-get-hist-power              | DTU and W-series                       | Retrieve historical power data                                   |
| set-power-limit                 | DTU and W-series                       | Set the power limit of the inverter (0-100%)                     |
| set-power-limit-temporary       | Supported HMS-W-2T only                 | Set a temporary limit in watts                       |
| set-wifi                        | DTU and W-series                       | Configure the WiFi network                                       |
| firmware-update                 | DTU and W-series                       | Update to latest firmware                                        |
| restart-dtu                     | DTU and W-series                       | Restart the DTU                                                  |
| turn-on-inverter                | DTU and W-series                       | Turn the inverter on                                             |
| turn-off-inverter               | DTU and W-series                       | Turn the inverter off                                            |
| get-information-data            | DTU and W-series                       | Retrieve information data                                        |
| get-version-info                | DTU and W-series                       | Retrieve version information                                     |
| heartbeat                       | DTU and W-series                       | Request a heartbeat message from the DTU                         |
| identify-dtu                    | DTU and W-series                       | Identify the DTU                                                 |
| identify-inverters              | DTU and W-series                       | Identify connected inverters                                     |
| identify-meters                 | DTU and W-series                       | Identify connected meters                                        |
| get-alarm-list                  | DTU and W-series                       | Get alarm list from the DTU                                      |
| enable-performance-data-mode    | DTU and W-series                       | _Experimental_: Enable higher update interval mode (30s or less) |
| is-encrypted                    | DTU and W-series                       | Check if DTU uses encryption and retrieve encryption data        |
| get-gateway-info                | HAT / HYT / HAS / HYS battery inverter | Get gateway information for hybrid-inverters                     |
| get-gateway-network-info        | HAT / HYT / HAS / HYS battery inverter | Get network information for hybrid-inverters                     |
| get-energy-storage-registry     | HAT / HYT / HAS / HYS battery inverter | Get information about the hybrid-inverter                        |
| get-energy-storage-data         | HAT / HYT / HAS / HYS battery inverter | Get live data of the hybrid-inverter                             |
| get-energy-storage-working-mode | HAT / HYT / HAS / HYS battery inverter | Read current working mode and mode-specific settings |
| set-energy-storage-working-mode | HAT / HYT / HAS / HYS battery inverter | Set the working mode of the hybrid-inverter                      |


### CLI Arguments

The following arguments are available when using the CLI:

| Argument                | Type | Description                                       |
| ----------------------- | ---- | ------------------------------------------------- |
| `--host`                | str  | IP address or hostname of the DTU (required)      |
| `--local_addr`          | str  | IP address of the interface to bind to (optional) |
| `--as-json`             | flag | Format output as JSON                             |
| `--disable-interactive` | flag | Disables interactive prompts                      |
| `--enc-rand`            | str  | Set inverter specific encryption data             |
| `--dtu-time-offset`     | int  | Clock offset in seconds (default: 0)              |
| `--timeout`             | int  | Set custom timeout in seconds                     |


The following arguments are only available when using the `--disable-interactive` flag:

| Argument                   | Type | Description                            |
| -------------------------- | ---- | -------------------------------------- |
| `--power-limit`            | int  | Power limit to set (0–100)             |
| `--inverter-serial-number` | int  | Inverter serial number                 |
| `--bms_working_mode`       | int  | BMS mode (1-8)                         |
| `--rev-soc`                | int  | Reserved SOC to set (0–100)            |
| `--max-power`              | int  | Max (dis)charging power to set (0–100) |
| `--peak-soc`               | int  | Peak SOC to set (0–100)                |
| `--peak-meter-power`       | int  | Peak meter power (in W) to set         |
| `--time-settings`          | str  | See Economic working mode              |
| `--time-periods`           | str  | See Time of Use working mode           |

### Encryption (enc_rand)

Some DTUs enforce encrypted communication. This is notably the case for devices running firmware v01.x, where encryption is mandatory. On older firmware (v00.x), data could be retrieved without encryption; firmware v01.x enables encryption, so `enc_rand` must be supplied.

If the DTU has been disconnected from the internet for some time, its clock may be out of sync. Encrypted requests containing the current time may then be rejected. Supply the clock difference as `dtu_time_offset` when needed.

> [!NOTE]
> Whether a DTU uses encryption is determined by a flag in the DTU's application information data. The `--enc-rand` argument is only required when the DTU reports as encrypted.

To obtain the `enc_rand` value, use the `is-encrypted` command:

```bash
hoymiles-wifi --host HOST is-encrypted
```

Example output (with `--as-json`):

```json
{
  "is_encrypted": true,
  "dtu_time_offset": -120,
  "enc_rand": "00112233445566778899aabbccddeeff"
}
```

Pass `enc_rand` via `--enc-rand` for encrypted commands. If the DTU clock is out of sync, also pass the measured offset via `--dtu-time-offset`. The offset is the DTU clock minus the host clock in seconds, so a negative value means the DTU is behind the host:

```bash
hoymiles-wifi --host HOST --enc-rand 00112233445566778899aabbccddeeff --dtu-time-offset -120 get-real-data-new
```

> [!NOTE]
> The `enc_rand` value is device-specific. If the DTU is re-paired or reset, re-run `is-encrypted` to fetch the new value.

In Python, `DTU.async_app_information_data()` returns the raw DTU timestamp,
encryption flag, and `enc_rand`. Calculate the offset from that response and
set it on the `DTU` instance:

```python
import asyncio
import time

from hoymiles_wifi.dtu import DTU
from hoymiles_wifi.hoymiles import is_encrypted_dtu

async def main():
    dtu = DTU("10.10.100.254")
    info = await dtu.async_app_information_data()
    if info is None:
        raise RuntimeError("Could not read DTU information")
    dtu.dtu_time_offset = info.timestamp - round(time.time())
    dtu.is_encrypted = bool(is_encrypted_dtu(info.dtu_info.dfs))
    dtu.enc_rand = info.dtu_info.enc_rand

    return await dtu.async_get_real_data_new()

response = asyncio.run(main())
print(response)
```

Calculate the offset immediately after receiving the response so it reflects
the current difference between the two clocks. Refresh it after a DTU restart
or clock synchronization. The offset stays in memory and is not automatically
learned by the library.

Negative integers are valid CLI arguments: `--dtu-time-offset -120` and
`--dtu-time-offset=-120` are equivalent. Positive values mean the DTU is ahead
of the host. The offset adjusts request timestamps, including power-limit and
hybrid configuration requests; transaction IDs and request pacing are unchanged.
Unencrypted app-info discovery continues to use host time.

### 🔧 BMS Working Modes & Required Parameters

For `set-energy-storage-working-mode` different CLI parameters must be provided depending on the selected BMS working mode. Below is an overview:

| Working Mode              | Required Parameters                                                         |
| ------------------------- | --------------------------------------------------------------------------- |
| `Self Consumption Mode=1` | `--inverter-serial-number`, `--rev-soc`                                     |
| `Economy Mode=2`          | `--inverter-serial-number`, `--rev-soc`, `--time-settings`                  |
| `Backup Mode=3`           | `--inverter-serial-number`, `--rev-soc`                                     |
| `Off-Grid Mode=4`         | `--inverter-serial-number`, `--rev-soc`                                     |
| `Force Charge Mode =5`    | `--inverter-serial-number`, `--rev-soc`, `--max-power`                      |
| `Force Dischage Mode=6`   | `--inverter-serial-number`, `--rev-soc`, `--max-power`                      |
| `Peak Shaving Mode=7`     | `--inverter-serial-number`, `--rev-soc`, `--peak-soc`, `--peak-meter-power` |
| `Time of Use Mode=8`      | `--inverter-serial-number`, `--rev-soc`, `--time-periods`                   |

---

#### `--time-settings` (Economic working mode)

```
START-END:WEEKDAYS=PEAK_START-PEAK_END-PEAK_IN-PEAK_OUT,OFF_START-OFF_END-OFF_IN-OFF_OUT,PARTIAL_START-PARTIAL_END-PARTIAL_IN-PARTIAL_OUT;WEEKDAYS=...||START-END:...||...
```

- `START`, `END`: Date in `DD.MM`
- For each date range, **exactly two time ranges** must be configured
- Each time range includes:
  - `WEEKDAYS`: Comma-separated days (`1=Mon`, ..., `7=Sun`)
  - 3 tariff blocks: `PEAK`, `OFF_PEAK`, `PARTIAL_PEAK`
- Each block format: `START-END-IN_PRICE-OUT_PRICE`
- Use `;` to separate **Time Range 1** and **Time Range 2**
- Use `||` to separate multiple date ranges

##### Example

```
01.01-31.03:1,2,3=06:00-10:00-0.20-0.10,00:00-06:00-0.10-0.05,10:00-18:00-0.15-0.08;4,5=07:00-11:00-0.22-0.11,00:00-07:00-0.08-0.04,11:00-17:00-0.14-0.07
```

| Field            | Value         | Description                |
| ---------------- | ------------- | -------------------------- |
| Start Date       | `01.01`       | Start of date range        |
| End Date         | `31.03`       | End of date range          |
| **Time Range 1** |               |                            |
| Weekdays         | `1,2,3`       | Monday, Tuesday, Wednesday |
| PEAK             | `06:00-10:00` | Buy: `0.20`, Sell: `0.10`  |
| OFF_PEAK         | `00:00-06:00` | Buy: `0.10`, Sell: `0.05`  |
| PARTIAL_PEAK     | `10:00-18:00` | Buy: `0.15`, Sell: `0.08`  |
| **Time Range 2** |               |                            |
| Weekdays         | `4,5`         | Thursday, Friday           |
| PEAK             | `07:00-11:00` | Buy: `0.22`, Sell: `0.11`  |
| OFF_PEAK         | `00:00-07:00` | Buy: `0.08`, Sell: `0.04`  |
| PARTIAL_PEAK     | `11:00-17:00` | Buy: `0.14`, Sell: `0.07`  |

#### `--time-periods` (Time of use mode)

```
CHARGE_FROM-CHARGE_TO-CHARGE_PWR-MAX_SOC|DISCHARGE_FROM-DISCHARGE_TO-DISCHARGE_PWR-MIN_SOC||...
```

- All values are required
- Use `||` to separate multiple time periods
- Power and SOC values must be `0–100`

##### Example

```
06:00-08:00-50-90|18:00-20:00-40-20
```

| Field             | Value         | Description               |
| ----------------- | ------------- | ------------------------- |
| Charge From–To    | `06:00-08:00` | Charging window           |
| Charge Power      | `50`          | % power used to charge    |
| Max SOC           | `90`          | Max state of charge (%)   |
| Discharge From–To | `18:00-20:00` | Discharging window        |
| Discharge Power   | `40`          | % power used to discharge |
| Min SOC           | `20`          | Min state of charge (%)   |

### Python code

```python
from hoymiles_wifi.dtu import DTU
...
dtu = DTU(<ip_address>)
response = await dtu.<command>

if response:
    print(f"DTU Response: {response}")
else:
    print("Unable to get response!")
```

#### Available functions

- `async_get_real_data_new()`: Retrieve real-time data
- `async_get_real_data()`: Retrieve real-time data
- `async_get_config()`: Retrieve configuration information
- `async_network_info()`: Retrieve network information
- `async_app_information_data()`: Retrieve application information data
- `async_app_get_hist_power()`: Retrieve historical power data
- `async_set_power_limit(power_limit)`: Set the persistent power limit of the inverter (0-100%)
- `async_set_power_limit_temporary(power_watts)`: Set a temporary watt limit on supported HMS-W-2T devices.
- `async_set_wifi(wifi_ssid, wifi_password)`: Configure the wifi network
- `async_firmware_update()`: Update to latest firmware
- `async_restart_dtu()`: Restart the DTU
- `async_turn_on_inverter()`: Turn the inverter on
- `async_turn_off_inverter()`: Turn the inverter off
- `async_get_information_data()`: Retrieve information data
- `async_heartbeat()`: Request a heartbeat message from the DTU
- `async_get_alarm_list()`: Get alarm list from the DTU
- `async_enable_performance_data_mode()`: _Experimental_: Enable higher update interval mode (30s or less)
- `async_get_gateway_info()`: Get gateway information for hybrid-inverters
- `async_get_gateway_network_info()` : Get network information for hybrid-inverters
- `async_get_energy_storage_registry()`: Get information about the hybrid-inverter
- `async_get_energy_storage_data()`: Get live data of the hybrid-inverter
- `async_set_energy_storage_working_mode()`: Set the working mode of the hybrid-inverter

## Note

Please be aware:

> [!NOTE]
> No DTU Implementation: This library retrieves information directly from the (internal) DTU of Hoymiles Wifi inverters.

## Caution

Use this library responsibly and be aware of potential risks. There are no guarantees provided, and any misuse or incorrect implementation may result in undesirable outcomes. Ensure that your inverter is not compromised during communication.

## Known Limitations

### Update Frequency:

> [!NOTE]
> The library may encounter limitations in fetching updates, restricting updates to approximately twice per minute.
> This issue can be identified when the data returned matches the response from the previous request.
> If you encounter this, you have three options:
>
> 1. Set the update interval to 35 seconds or longer between requests.
> 2. Use `async_enable_performance_data_mode()`
> 3. Open the S-Miles Installer App, connect to the DTU, and access the Toolkit (which sends the same data as option 2).
>
> Options 2 and 3 need to be repeated whenever the DTU restarts.

> [!IMPORTANT]
> Setting the update interval below approximately 32 seconds may disable Hoymiles cloud functionality. To ensure proper communication with Hoymiles servers, keep the update interval at or above this threshold.

> [!CAUTION]
> Setting the update interval to 1–2 seconds could also cause disruptions in the App's connection.

### Compatibility:

> [!IMPORTANT]
> While developed for the HMS-800W-2T inverter, compatibility with other inverters from the series is untested at the time of writing. Exercise caution and conduct thorough testing if using with different inverter models.

## Attribution

A special thank you for the inspiration and codebase to:

- [DennisOSRM](https://github.com/DennisOSRM): [hms-mqtt-publisher](https://github.com/DennisOSRM/hms-mqtt-publisher)
- [henkwiedig](https://github.com/henkwiedig): [Hoymiles-DTU-Proto](https://github.com/henkwiedig/Hoymiles-DTU-Proto)
