aiopapouch
This repository contains an asynchronous Python I/O library for Papouch s.r.o. devices.
The library provides major components: Devices, API Clients, and Hubs for managing multiple devices efficiently.
Requirements
- Python 3.14+: The library requires Python 3.14 or higher (PEP 758).
- aiohttp: Required for handling asynchronous HTTP communication with the network devices.
- pap_spinel: Required for serial communication.
Installation
pip install aiopapouch
Supported Devices
Currently, the library supports the following Ethernet devices communicating via WEB and TCP server mode:
- Quido ETH
- Papago
- Meteo
- 2TH
- 5HDI DO
- TH 2DI DO
- TH2E
- TME
- TME Multi / Radio
and these are devices that use serial communiction (RS485):
- Quido RS485
- THT2
- THCO2
- TQS 4
Devices
The library is designed using an object-oriented approach. Every device (or device family) is a subclass of either PapouchNetworkDevice or PapouchSerialDevice (which both inherit from the abstract PapouchDevice core). This clear separation of the transport layer provides shared contract methods such as get_fresh_data, get_supported_sensors, and properties like name and identifier.
Due to polymorphism, the factory functions create_network_device and create_serial_device return a generic PapouchNetworkDevice and PapouchSerialDevice respectively. This works in tandem with the is_device_supported function, which validates whether the hardware is supported by this library.
Note: The constructors are asynchronous (implementing the factory pattern). Creating any device instance utilizes the network/serial communication to download the initial configuration.
Note: The library was designed specifically for Home Assistant. Methods like
get_supported_sensorsreturn configurations required for entity creation. This remains the primary purpose of the library.
Note: Initial fresh fetch of data happens before the creation of the entities, making it a valid approach to generate configurations during/after the parsing of fresh data.
Hubs (Recommended)
When dealing with multiple devices, it is highly recommended to use Hubs. Hubs act as managers that group devices together, providing unified methods to concurrently fetch data (get_fresh_data) or verify device states (check_health).
The library provides 3 types of Hubs based on the transport layer:
NetworkHub: Used for IP-based devices (HTTP). It utilizes a single sharedaiohttp.ClientSessionto dynamically spawn and manage individual HTTP clients for each added IP address.SerialHub: Used for RS485-based devices. It takes a single sharedPapouchSerialClient(since all devices share the same serial bus or TCP gateway) and manages devices by their hardware address or serial number.NetworkSpinelHub: Used for standalone network devices that communicate via the Spinel protocol directly over a TCP socket. UnlikeSerialHub, this hub manages individual TCP connections for each registered IP address automatically.
Pythonic Features (Magic Methods & Context Managers)
All hubs are designed to behave like standard Python collections. You can easily get the device count using len(hub), check for existence with device in hub, or iterate directly over the hub using for device in hub:.
Furthermore, hubs handling persistent socket or serial connections (SerialHub and NetworkSpinelHub) support asynchronous context managers (async with), ensuring that all ports and connections are cleanly closed when the block is exited.
API Client & Usage
The library provides 2 major types of communication: Network and Serial (via RS485 or Spinel TCP). Although aiopapouch is primarily designed to serve as the underlying library for the official Home Assistant Papouch integration, it can also be used independently in standalone Python scripts.
1. Network Usage Example (HTTP)
import asyncio
import aiohttp
from aiopapouch import NetworkHub
from aiopapouch.exceptions import DeviceLogicError
async def main():
# Initialize the shared aiohttp client session
async with aiohttp.ClientSession() as session:
hub = NetworkHub(session)
try:
# Automatically create API clients and initialize devices by IP
await hub.create_and_add_device("192.168.1.100", password="admin")
await hub.create_and_add_device("192.168.1.101")
except DeviceLogicError as err:
print(f"Failed to add device: {err}")
return
# Check health of all devices concurrently
health_status = await hub.check_health()
print("Device health:", health_status)
# Parse fresh data concurrently from all devices
parsed_data = await hub.get_fresh_data()
print("Parsed telemetry data:", parsed_data)
# Utilize pythonic magic methods for iteration and length
print(f"Currently managing {len(hub)} devices.")
for device in hub:
print(f"Device: {device.conf.name} - IP: {device.api_client.ip_address}")
if __name__ == "__main__":
asyncio.run(main())
2. Network Spinel Usage Example (TCP)
For devices that use the Spinel protocol over an Ethernet connection, the NetworkSpinelHub manages individual TCP transports for you.
import asyncio
from aiopapouch.hub import NetworkSpinelHub
async def main():
# Context manager ensures all TCP ports are automatically closed on exit
async with NetworkSpinelHub() as hub:
# Add Spinel device by its IP and Port
await hub.create_and_add_device("192.168.3.40", 10001)
print("Health Status:", await hub.check_health())
print("Telemetry Data:", await hub.get_fresh_data())
# Showcasing pythonic iteration over the hub
for device in hub:
print(f"Serial Device Context: {device.conf.context}")
if __name__ == "__main__":
asyncio.run(main())
3. Serial RS485 Usage Example
For serial communication, you can use PapouchSerialClient that wraps the pap_spinel transport layer. The client can also resolve some data from the device without needing to know its exact type.
Note: Don't forget to give permissions to open/close the port if using direct USB/Serial connection (
/dev/ttyUSB0).
The SerialHub also features a Plug & Play discovery method (discover_and_add_single_device()). If you have exactly one new device physically connected to the bus, this method uses the broadcast address to automatically identify and add it. (Note: If multiple unknown devices are on the bus, this will raise an error due to data collision).
import asyncio
from aiopapouch import SerialHub
from aiopapouch.client import PapouchSerialClient
from pap_spinel import TcpTransport, SerialTransport
async def main():
# You can use either a TCP Gateway or Direct Serial connection:
transport = TcpTransport("192.168.3.33", 10001)
# transport = SerialTransport(port="/dev/ttyUSB0", baudrate=9600)
client = PapouchSerialClient(transport)
await client.open()
# The async context manager automatically calls client.close() when done
async with SerialHub(client) as hub:
# Option A: Plug & Play - Auto-detect a single connected device
# await hub.discover_and_add_single_device()
# Option B: Automatically assign free addresses by known serial numbers
await hub.create_device_by_serial_number("1395/0149")
await hub.create_device_by_serial_number("1255/5627")
# Option C: Add a device by a known address
# await hub.create_and_add_device(address=1)
# Check health and fetch data concurrently
print("Health Status:", await hub.check_health())
print("Telemetry Data:", await hub.get_fresh_data())
for device in hub:
print(f"Device: {device.conf.name} - Address: {device.conf.address}")
if __name__ == "__main__":
asyncio.run(main())
Exceptions
The library defines custom exceptions raised during execution, such as DeviceConnectionError, DeviceAuthError, DeviceParseError, and DeviceLogicError.
Device Control and Configuration
In addition to fetching telemetry data, the library allows controlling devices and modifying their settings. Because supported entities (switches, selects, numbers, buttons) vary by hardware model and configuration, PapouchDevice provides introspection methods (get_supported_switches, get_supported_selects, get_supported_buttons, get_supported_numbers) to discover available controls and their valid parameters before executing control methods.
For a complete list of available methods and properties, please refer to the docstrings in the source code of the PapouchDevice base class.
Discovery Methods
get_supported_switches(): Returns a list of available switch entities and theiritem_id.get_supported_selects(): Returns available select entities, includingcategory,item_id, and allowedoptions.get_supported_buttons(): Returns button commands (cmd) and placeholders.get_supported_numbers(): Returns configuration for counter operations. Rather than generic numbers, these entities represent specific actions like decreasing a counter or setting a counter to a specific value. It includes allowed min/max values, step size,category(e.g.,decrease_counter,set_counter), anditem_id.
Control Methods
turn_on_switch(item_id)/turn_off_switch(item_id): Controls digital outputs byitem_id.set_select_option(category, item_id, option): Changes a selection setting bycategory,item_id, andoptionstring.set_number_value(category, item_id, value): Executes a counter operation (such as decreasing or directly setting the counter) based on thecategory,item_id, and specifiedvalue.execute_button_command(cmd_type): Triggers a button action using thecmdidentifier.
Code Example (Control)
import asyncio
import aiohttp
from aiopapouch import PapouchHTTPClient, create_network_device
async def main():
async with aiohttp.ClientSession() as session:
client = PapouchHTTPClient("192.168.1.100", session)
device = await create_network_device(client)
if device is None:
return
# 1. Discover available controls and parameters
print("Switches:", device.get_supported_switches())
print("Selects:", device.get_supported_selects())
print("Buttons:", device.get_supported_buttons())
print("Numbers:", device.get_supported_numbers())
# 2. Execute actions using the explicitly discovered IDs and exact option strings
# Turn on the relay identified by item_id "1"
await device.turn_on_switch("1")
# Set the sensor type for item_id "1" in the "sensor_type" category
await device.set_select_option("sensor_type", "1", "temperature_ds")
# Execute the autodetect button command
await device.execute_button_command("set_sensor_1")
# Decrease the counter on input "1" by a specific value
await device.set_number_value("decrease_counter", "1", 10)
if __name__ == "__main__":
asyncio.run(main())
Release files for aiopapouch 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aiopapouch-0.4.1.tar.gz | 39.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aiopapouch-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 88.1 kB
Release files / aiopapouch-0.4.1.tar.gz
| Download URL | aiopapouch-0.4.1.tar.gz |
|---|---|
| Size | 39.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
823d220557927a8c731f86dfec15b317036c215dfd5cf8e2a1175329bbd9c584
|
|
BLAKE2b-256 checksum How to use checksums |
a0542b3f9883c628006ddab1aa1dd7986bb13d2bec802ec836a24b6cb4c91a40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / aiopapouch-0.4.1-py3-none-any.whl
| Download URL | aiopapouch-0.4.1-py3-none-any.whl |
|---|---|
| Size | 48.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b145240a035eca13509023315453c501d12141d4ca259f369646b43ee4c6e6de
|
|
BLAKE2b-256 checksum How to use checksums |
9be81e5fe2b8d3f00194f7363cacc05b12059eb28d60bd33bb84d2741eb18272
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log