Quick Start
This page walks you through installing msunpv and reading your first data from an MSunPV solar router in a few lines of Python.
Prerequisites
Python 3.9 or newer.
Your MSunPV device reachable on the local network (know its IP address or hostname).
The device’s HTTP interface enabled (factory default).
Installation
Install from PyPI with pip:
pip install msunpv
Or, to install the latest development version directly from GitHub:
pip install git+https://github.com/thanatos-vf-2000/msunpv.git
Basic usage — read status once
The simplest way to query the device is through MSunPVRead,
which manages the aiohttp session for you.
import asyncio
from msunpv import MSunPVRead, MSunPVConnectionException, MSunPVXMLDataException
DEVICE_IP = "192.168.1.123" # ← replace with your router's IP
async def main():
reader = MSunPVRead(DEVICE_IP)
await reader.start()
try:
await reader.refresh_data() # fetches status.xml (and index.xml on first call)
status = reader.DataMSunPVDataStatus
print(f"Grid power : {status.power_reso:+.1f} W")
print(f"PV power : {status.power_pv_positive:.1f} W")
print(f"Home load : {status.power_home:.1f} W")
print(f"Balloon temp: {status.temperature_balloon:.1f} °C")
print(f"Daily prod. : {status.daily_production:.3f} kWh")
except (MSunPVConnectionException, MSunPVXMLDataException) as exc:
print(f"Error: {exc}")
finally:
await reader.stop()
asyncio.run(main())
Expected output (values depend on your installation):
Grid power : -230.0 W
PV power : 280.0 W
Home load : 50.0 W
Balloon temp: 52.3 °C
Daily prod. : 1.243 kWh
Polling loop with wait_for
Use wait_for() to poll the device at a regular
interval without drift — it subtracts the time already spent reading:
import asyncio
from msunpv import MSunPVRead
DEVICE_IP = "192.168.1.123"
POLL_INTERVAL = 10 # seconds
async def main():
reader = MSunPVRead(DEVICE_IP)
await reader.start()
try:
for _ in range(6): # 6 iterations → ~1 minute
await reader.refresh_data()
power = reader.DataMSunPVDataStatus.power_reso
print(f"Grid: {power:+.1f} W")
await reader.wait_for(POLL_INTERVAL) # waits the remaining seconds
finally:
await reader.stop()
asyncio.run(main())
Reading the device index
The index (index.xml) contains sensor labels, unit suffixes, output names
and command descriptions. It is fetched automatically on the first call to
refresh_data(). Pass All=True to force a refresh:
await reader.refresh_data(All=True) # forces re-fetch of both status and index
index = reader.DataMSunPVDataIndex
print(f"Model : {index.modele}")
print(f"Version : {index.version}")
# Sensor type info for sensor 0 (usually grid power)
info = index.sensor_type_info(0)
print(f"Sensor 0: {info['name']} ({info['suffix']})")
# Command info for command slot 0
cmd = index.command_info(0)
print(f"Cmd 0: {cmd['cmdtxt']} — params: {cmd['param1']}, {cmd['param2']}")
Using the lower-level API (aiohttp session)
If you already manage your own aiohttp.ClientSession (e.g. inside
Home Assistant or another async framework), use MSunPVWebConnect
directly:
import asyncio
import aiohttp
from msunpv import MSunPVWebConnect
DEVICE_IP = "192.168.1.123"
async def main():
async with aiohttp.ClientSession() as session:
client = MSunPVWebConnect(session, DEVICE_IP)
status = await client.get_status()
print(f"PV power: {status.power_pv_positive:.1f} W")
index = await client.get_index()
print(f"Serial: {index.serial_number}")
asyncio.run(main())
Tip
refresh() is a convenience wrapper that
dispatches to get_status() or
get_index() based on the data_type
argument ("status.xml" or "index.xml").
Error handling
All library errors inherit from MSunPVException:
Exception |
When it is raised |
|---|---|
Network error, timeout, device unreachable, or empty IP |
|
Malformed XML or unexpected payload from the device |
from msunpv import MSunPVException, MSunPVConnectionException, MSunPVXMLDataException
try:
await reader.refresh_data()
except MSunPVConnectionException as exc:
print(f"Cannot reach device: {exc}")
except MSunPVXMLDataException as exc:
print(f"Bad data from device: {exc}")
except MSunPVException as exc:
print(f"Library error: {exc}")
Generic attribute access
Every data class exposes a get() helper that
returns None instead of raising AttributeError for unknown names —
useful for dynamic access:
value = status.get("power_reso") # returns the float, or None if missing
Next steps
Browse the msunpv package API reference for the full list of attributes.
See
example.pyin the repository root for a complete command-line demo.Check Changelog for recent changes.