Metadata-Version: 2.4
Name: powerwalker-modbus
Version: 0.1.0
Summary: PowerWalker UPS Modbus TCP client
Author-email: Niko Järvinen <nbjarvinen@gmail.com>
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: click>=8.0
Requires-Dist: pymodbus>=3.6
Description-Content-Type: text/markdown

# powerwalker-modbus

Python client for PowerWalker UPS units over Modbus TCP.

Register map reverse-engineered from the PowerWalker WinPower monitoring software.

> **Note:** This has only been tested with the **PowerWalker VFI 1000 ICR IoT**.
> Other Phoenixtec/CyberPower/PowerWalker models using the same Modbus register
> layout may work but are untested.

## Installation

```bash
uv add powerwalker-modbus
```

Or install from a local clone:

```bash
git clone <repo-url>
cd powerwalker-modbus
uv sync
```

## CLI Usage

Read the full UPS status:

```bash
uv run powerwalker-modbus HOST
```

```
── Device Info ──
  Manufacturer: PHOENIXTEC
  Model: RT 1K
  UPS Firmware: 00.02.12754
  Serial Number: XXXX0000000000
  ...

── Output ──
  Frequency: 49.9 Hz
  Voltage: 230.2 V
  Current: 0.7 A
  Active Power: 130 W
  Apparent Power: 160 VA
  Load: 16 %

── Battery ──
  Capacity: 100 %
  Voltage: 40 V
  Runtime Remaining: 67m 45s
```

Show the Python access path for each value with `--paths`:

```bash
uv run powerwalker-modbus HOST --paths
```

```
── Output ──
  Frequency: 49.9 Hz  (status.output.frequency_hz)
  Voltage: 230.2 V  (status.output.voltage.l1)
  Current: 0.7 A  (status.output.current.l1)
  Active Power: 130 W  (status.output.active_power.l1)
  Apparent Power: 160 VA  (status.output.apparent_power.l1)
  Load: 16 %  (status.output.load_percent)

── Battery ──
  Capacity: 100 %  (status.battery.capacity_percent)
  Runtime Remaining: 67m 45s  (status.battery.runtime_remaining_sec)
```

Output as JSON:

```bash
uv run powerwalker-modbus HOST --json
```

```json
{
  "device_info": {
    "manufacturer": "PHOENIXTEC",
    "model": "RT 1K",
    ...
  },
  "output": {
    "frequency_hz": 49.9,
    "voltage": { "l1": 230.2, "l2": null, "l3": null },
    "active_power": { "l1": 130, "l2": null, "l3": null },
    "load_percent": 16
  },
  "battery": {
    "capacity_percent": 100,
    "voltage_v": 40.0,
    "runtime_remaining_sec": 4065
  },
  ...
}
```

Custom port:

```bash
uv run powerwalker-modbus HOST --port 5020
```

You can also run it as a Python module:

```bash
python -m powerwalker_modbus HOST
```

## Library Usage

### Read full status

```python
from powerwalker_modbus import PowerWalkerUPS

with PowerWalkerUPS("HOST") as ups:
    status = ups.get_status()

print(status.output.voltage.l1)        # 230.2
print(status.output.active_power.l1)   # 130
print(status.battery.capacity_percent) # 100
print(status.battery.runtime_remaining_sec)  # 4065
```

### Read individual sections

```python
from powerwalker_modbus import PowerWalkerUPS

with PowerWalkerUPS("HOST") as ups:
    info = ups.get_device_info()
    print(info.manufacturer)  # PHOENIXTEC
    print(info.model)         # RT 1K

    battery = ups.get_battery()
    print(f"{battery.voltage_v} V, {battery.capacity_percent}%")

    output = ups.get_output()
    print(f"{output.active_power.l1} W, {output.load_percent}%")

    topology = ups.get_topology()
    print(topology.abm_status_label)  # Resting
```

### Control output segments

```python
from powerwalker_modbus import PowerWalkerUPS

with PowerWalkerUPS("HOST") as ups:
    # Check which segments are on
    states = ups.get_segment_states()
    print(states)  # [True]

    # Get a handle to segment 0
    seg = ups.get_output_segment(0)
    print(seg.is_on())  # True

    seg.off()  # Turn off
    seg.on()   # Turn back on
```

### Available dataclasses

| Class | Key fields |
|---|---|
| `UpsStatus` | `device_info`, `topology`, `config`, `input`, `output`, `battery`, `bypass`, `segments` |
| `DeviceInfo` | `manufacturer`, `model`, `ups_firmware`, `serial_number`, `iot_firmware`, `device_guid` |
| `TopologyStatus` | `line_type`, `working_mode`, `ups_type`, `abm_status`, `abm_status_label` |
| `NominalConfig` | `frequency_hz`, `voltage_v`, `segment_count`, `auto_reboot`, `eco_mode`, ... |
| `InputStatus` | `ambient_temperature_c`, `frequency_hz`, `voltage` (PhaseReadings) |
| `OutputStatus` | `frequency_hz`, `voltage`, `current`, `active_power`, `apparent_power`, `load_percent` |
| `BatteryStatus` | `capacity_percent`, `voltage_v`, `voltage_negative_v`, `temperature_c`, `runtime_remaining_sec` |
| `BypassStatus` | `frequency_hz`, `voltage` (PhaseReadings) |
| `PhaseReadings` | `l1`, `l2`, `l3` (single-phase UPS only populates `l1`) |

## Development

```bash
uv sync
uv run pytest
```
