Skip to main content

Async Python library for Tecnosystemi Pico HVAC/ventilation IoT devices

Project description

๐ŸŒŠ Open Pico Local API

Asynchronous Python library for Tecnosystemi Pico IoT devices

Python Version License Version Tests

Features โ€ข Installation โ€ข Quick Start โ€ข Auto-Discovery โ€ข Documentation โ€ข Examples โ€ข Scripts โ€ข Testing


โœจ Features

๐Ÿš€ Performance

  • Built with asyncio for non-blocking operations
  • Efficient UDP communication protocol
  • Shared transport manager for multiple devices
  • Automatic IDP synchronization and range allocation

๐Ÿ”„ Reliability

  • Auto-reconnect on connection failures
  • Configurable retry logic
  • Robust error handling
  • IDP sync recovery for resilient communication

๐ŸŽฏ Developer Friendly

  • Type-safe with Python enums
  • Async context manager support
  • Comprehensive logging
  • Multi-device orchestration support

๐ŸŽ›๏ธ Full Control

  • Complete device mode management
  • Fan speed control
  • Humidity & LED settings
  • Concurrent device operations

๐Ÿ“ฆ Installation

pip (recommended)

Install directly from a GitHub tag โ€” no need to copy source files:

pip install "open-pico-local-api @ git+https://github.com/VoidElle/open-pico-local-api.git@v2.4.1"

Home Assistant integration

Add to your integration's manifest.json and Home Assistant will install the library automatically when the integration loads:

"requirements": [
  "open-pico-local-api @ git+https://github.com/VoidElle/open-pico-local-api.git@v2.4.1"
]

Manual

  1. Clone this repository in your project
  2. Import PicoClient and other relevant classes in your files

๐Ÿš€ Quick Start

Single Device

import asyncio
from open_pico_local_api import PicoClient, DeviceModeEnum

async def main():
    # Initialize device with shared transport (default)
    device = PicoClient(
        ip="192.168.1.100",
        pin="1234",
        device_id="living_room",
        verbose=True
    )

    # Use context manager for automatic cleanup
    async with device:
        await device.turn_on()
        status = await device.get_status()
        print(f"โœ“ Device online: {status.operating.mode}")
        
        await device.change_operating_mode(DeviceModeEnum.CO2_RECOVERY)
        await device.change_fan_speed(75)
        print("โœ“ Configuration complete!")

if __name__ == "__main__":
    asyncio.run(main())

Multiple Devices

import asyncio
from open_pico_local_api import PicoClient

async def main():
    # Create multiple device clients
    living_room = PicoClient(
        ip="192.168.1.100",
        pin="1234",
        device_id="living_room",
        verbose=True
    )
    
    bedroom = PicoClient(
        ip="192.168.1.101",
        pin="1234",
        device_id="bedroom",
        verbose=True
    )

    # Control both devices simultaneously
    async with living_room, bedroom:
        # Concurrent operations
        await asyncio.gather(
            living_room.turn_on(),
            bedroom.turn_on()
        )
        
        # Get status from both devices
        status1, status2 = await asyncio.gather(
            living_room.get_status(),
            bedroom.get_status()
        )
        
        print(f"Living Room: {status1.sensors.temperature_celsius}ยฐC")
        print(f"Bedroom: {status2.sensors.temperature_celsius}ยฐC")

if __name__ == "__main__":
    asyncio.run(main())

๐Ÿ“š Table of Contents


โš™๏ธ Configuration

Constructor Parameters

Parameter Type Default Description
ip โญ str required ๐ŸŒ IP address of the Pico device
pin โญ str required ๐Ÿ” PIN code for authentication
device_id str None ๐Ÿท๏ธ Unique identifier (auto-generated if not provided)
device_port int 40070 ๐Ÿ“ก UDP port of the device
local_port int 40069 ๐Ÿ“ก Local UDP port
timeout float 5 โฑ๏ธ Command timeout (seconds)
retry_attempts int 3 ๐Ÿ”„ Number of retry attempts
retry_delay float 2.0 โณ Delay between retries (seconds)
verbose bool False ๐Ÿ“ข Enable verbose logging
use_shared_transport bool True ๐Ÿ”— Use shared transport for multi-device support

๐Ÿ”€ Multi-Device Support

How It Works

IDP Range Allocation

  • Each device is assigned a unique IDP (Identifier Packet) range (10,000 IDs per device)
  • The shared transport manager routes responses to the correct device based on IDP
  • Automatic IDP synchronization ensures reliable communication

Shared UDP Socket

  • All devices share a single UDP socket on the specified local port
  • Responses are distributed to device-specific queues
  • No port conflicts, even with multiple devices

Automatic Management

  • Transport manager is automatically initialized on first device connection
  • IDP ranges are allocated dynamically as devices register
  • Cleanup happens automatically when devices disconnect

Usage

Simply create multiple PicoClient instances with use_shared_transport=True (default):

device1 = PicoClient(ip="192.168.1.100", pin="1234", device_id="device1")
device2 = PicoClient(ip="192.168.1.101", pin="1234", device_id="device2")
device3 = PicoClient(ip="192.168.1.102", pin="1234", device_id="device3")

async with device1, device2, device3:
    # All devices can be controlled concurrently
    statuses = await asyncio.gather(
        device1.get_status(),
        device2.get_status(),
        device3.get_status()
    )

Device ID

The device_id parameter is recommended when using multiple devices:

  • Provides meaningful identification in logs
  • Used for internal routing and debugging
  • Auto-generated as {ip}:{port} if not provided
# Recommended: explicit device IDs
device1 = PicoClient(ip="192.168.1.100", pin="1234", device_id="living_room")
device2 = PicoClient(ip="192.168.1.101", pin="1234", device_id="bedroom")

# Also works: auto-generated IDs
device3 = PicoClient(ip="192.168.1.102", pin="1234")  # ID: "192.168.1.102:40070"

๐Ÿ” Auto-Discovery

PicoAutoDiscovery scans a subnet and returns the IPs of all Pico devices it finds. All traffic goes through SharedTransportManager (port 40069) โ€” Pico devices are hardcoded to reply only to that port.

Basic Usage

import asyncio
from open_pico_local_api import PicoAutoDiscovery

async def main():
    ips = await PicoAutoDiscovery.discover(pin="1234", subnet="192.168.1.0/24")
    print(ips)  # ["192.168.1.42", "192.168.1.55"]

asyncio.run(main())

Parameters

Parameter Type Default Description
pin โญ str required PIN sent with each probe
subnet โญ str required CIDR range to scan (e.g. "192.168.1.0/24")
device_port int 40070 UDP port Pico devices listen on
local_port int 40069 Local port for SharedTransportManager
scan_timeout float 2.0 Seconds to collect replies after probes are sent
max_concurrent int 50 Max simultaneous probes
verbose bool False Enable debug logging

Returns: sorted List[str] of discovered IP addresses.

โš ๏ธ Note: A single PIN is broadcast to every host in the subnet. Only devices that share that PIN will respond. Devices with a different PIN will be silently missed.

Discovery + Connect

async def auto_connect():
    ips = await PicoAutoDiscovery.discover(pin="1234", subnet="192.168.1.0/24")
    if not ips:
        print("No devices found")
        return

    clients = [PicoClient(ip=ip, pin="1234") for ip in ips]
    async with asyncio.TaskGroup() as tg:
        for client in clients:
            tg.create_task(client.connect())

    statuses = await asyncio.gather(*[c.get_status() for c in clients])
    for ip, status in zip(ips, statuses):
        print(f"{ip}: {status.operating.mode.name}")

๐Ÿ”Œ Connection Management

Connect to Device

Establishes UDP connection to the Pico device. With shared transport, this registers the device with the transport manager and allocates an IDP range.

await device.connect()

Raises: ConnectionError if connection fails

Disconnect from Device

Gracefully closes the connection and cleans up resources. Unregisters from shared transport and releases IDP range.

await device.disconnect()

Check Connection Status

Returns True if device is currently connected.

if device.connected:
    print("โœ“ Device is online")

๐ŸŽ›๏ธ Device Control

Get Device Status

Retrieve complete device state with sensor readings, operating parameters, and system information.

status = await device.get_status()

# Device Information
print(f"Name: {status.device_info.name}")
print(f"Firmware: {status.device_info.firmware_full}")
print(f"IP: {status.device_info.ip}")

# Operating Status
print(f"Mode: {status.operating.mode}")
print(f"Power: {'ON' if status.is_on else 'OFF'}")
print(f"Fan Speed: {status.operating.speed}%")
print(f"Night Mode: {status.operating.is_night_mode_active}")

# Sensor Readings
print(f"Temperature: {status.sensors.temperature_celsius}ยฐC")
print(f"Humidity: {status.sensors.humidity_percent}%")
print(f"CO2: {status.sensors.eco2} ppm")
print(f"TVOC: {status.sensors.tvoc} ppb")
print(f"Air Quality: {status.sensors.air_quality}")

# System Info
print(f"Uptime: {status.system.uptime_days:.1f} days")
print(f"Memory Free: {status.system.memory_free_kb:.1f} KB")
print(f"Health: {'Healthy' if status.is_healthy else 'Issues Detected'}")

# Feature Support
print(f"Supports Fan Control: {status.support_fan_speed_control}")

Returns: PicoDeviceModel with complete device state

Parameters:

  • retry (bool): Enable retry logic (default: True)

Power Control

Turn the device on or off.

# Turn on
await device.turn_on()

# Turn off
await device.turn_off()

Returns: CommandResponseModel with operation result

Operating Modes

Change the device operating mode.

from open_pico_local_api import DeviceModeEnum

await device.change_operating_mode(DeviceModeEnum.CO2_RECOVERY)

Available Modes:

  • HEAT_RECOVERY - Heat recovery mode
  • EXTRACTION - Extraction only
  • IMMISSION - Immission only
  • HUMIDITY_RECOVERY - Humidity-based recovery
  • HUMIDITY_EXTRACTION - Humidity-based extraction
  • COMFORT_SUMMER - Summer comfort mode
  • COMFORT_WINTER - Winter comfort mode
  • CO2_RECOVERY - CO2-based recovery
  • CO2_EXTRACTION - CO2-based extraction
  • HUMIDITY_CO2_RECOVERY - Combined humidity/CO2 recovery
  • HUMIDITY_CO2_EXTRACTION - Combined humidity/CO2 extraction
  • NATURAL_VENTILATION - Natural ventilation mode

Fan Speed

Adjust fan speed as a percentage (0-100).

# Set fan speed to 50%
await device.change_fan_speed(50)

# Force change regardless of mode
await device.change_fan_speed(75, force=True)

Parameters:

  • percentage (int): Speed from 0-100
  • retry (bool): Enable retry logic
  • force (bool): Skip mode validation (only supported in HEAT_RECOVERY, EXTRACTION, IMMISSION, COMFORT_SUMMER, COMFORT_WINTER)

Night Mode

Activates quiet operation for nighttime use.

# Enable night mode
await device.set_night_mode(True)

# Disable night mode
await device.set_night_mode(False)

# Force enable even if mode doesn't support it
await device.set_night_mode(True, force=True)

Parameters:

  • enable (bool): True to enable, False to disable
  • retry (bool): Enable retry logic
  • force (bool): Skip mode validation

โš ๏ธ Note: Only supported in modes that allow fan speed control

๐Ÿ”ฅ WARNING: Using force=True bypasses mode compatibility checks and may cause the device to behave unexpectedly or reset its state. Use with caution and only when you understand the implications.

LED Control

Controls device indicator lights.

# Turn off all LEDs
await device.set_led_status(False)

# Turn on LEDs
await device.set_led_status(True)

Humidity Control

Set target humidity level.

from open_pico_local_api import TargetHumidityEnum

await device.set_target_humidity(TargetHumidityEnum.FIFTY_PERCENT)

# Force set even if mode doesn't support it
await device.set_target_humidity(TargetHumidityEnum.FIFTY_PERCENT, force=True)

Parameters:

  • target_humidity (TargetHumidityEnum): Target humidity level
  • retry (bool): Enable retry logic
  • force (bool): Skip mode validation

Available Levels:

  • FORTY_PERCENT - Target 40% humidity
  • FIFTY_PERCENT - Target 50% humidity
  • SIXTY_PERCENT - Target 60% humidity

โš ๏ธ Note: Only supported in humidity-based modes: HUMIDITY_RECOVERY, HUMIDITY_EXTRACTION, CO2_RECOVERY, CO2_EXTRACTION

๐Ÿ”ฅ WARNING: Using force=True bypasses mode compatibility checks and may cause the device to behave unexpectedly or reset its state. Use with caution and only when you understand the implications.


๐Ÿ”ข IDP Management

The library uses IDP (Identifier Packet) for reliable communication between client and device.

What is IDP?

IDP is a sequential identifier used to match commands with responses. Each device maintains its own IDP counter that must stay synchronized with the client.

IDP Range Allocation

When using shared transport (multi-device mode):

  • Each device is assigned a unique IDP range (10,000 IDs)
  • Device 1: IDP 1-10,000
  • Device 2: IDP 10,001-20,000
  • Device 3: IDP 20,001-30,000
  • And so on...

Automatic IDP Synchronization

The library automatically handles IDP synchronization:

  1. Sends command with current IDP
  2. If no response, increments IDP and retries (up to 5 times)
  3. If still no response, resets IDP to range start
  4. Continues with full retry logic

Manual IDP Reset

If communication becomes stuck (e.g., device was restarted), manually reset the IDP counter:

# Reset IDP counter to start of allocated range
await device.reset_idp()

This is useful when:

  • Device was power cycled
  • Device firmware was updated
  • Communication became persistently unresponsive

IDP Logging

Enable verbose mode to see IDP synchronization in action:

device = PicoClient(ip="192.168.1.100", pin="1234", verbose=True)

# Logs will show:
# โ†’ [living_room] SENT: stato_sync (idp:1)
# โœ“ [living_room] ACK received (idp:1)
# โœ“ [living_room] Response received (idp:1)
# โš  [living_room] No response for IDP 5 - likely out of sync
# โœ“ [living_room] IDP synchronized after 2 increments

๐Ÿ—๏ธ Data Models

The library provides strongly-typed data models for all device information.

PicoDeviceModel

Main model containing complete device state with the following components:

status = await device.get_status()

# Access sub-models
status.device_info      # Device identification
status.sensors          # Sensor readings
status.operating        # Operating parameters
status.parameters       # Parameter arrays
status.system          # System information

DeviceInfoModel

Device identification and hardware information.

print(f"Name: {status.device_info.name}")
print(f"Firmware: {status.device_info.firmware_full}")
print(f"Model: {status.device_info.model}")
print(f"IP: {status.device_info.ip}")
print(f"Has Datamatrix: {status.device_info.has_datamatrix}")

SensorReadingsModel

Real-time environmental sensor data.

sensors = status.sensors

print(f"Temperature: {sensors.temperature_celsius}ยฐC")
print(f"Humidity: {sensors.humidity_percent}%")
print(f"CO2: {sensors.eco2} ppm")
print(f"TVOC: {sensors.tvoc} ppb")
print(f"Air Quality: {sensors.air_quality}")
print(f"Has Air Quality Sensors: {sensors.has_air_quality}")

OperatingParametersModel

Current operating state and settings.

op = status.operating

print(f"Mode: {op.mode}")
print(f"Is On: {op.is_on}")
print(f"Fan Speed: {op.speed}%")
print(f"Fan Running: {op.fan_running}")
print(f"Night Mode: {op.is_night_mode_active}")
print(f"LED On: {op.is_led_state_on()}")

SystemInfoModel

System diagnostics and health.

sys = status.system

print(f"Uptime: {sys.uptime_days:.1f} days")
print(f"Memory Free: {sys.memory_free_kb:.1f} KB")
print(f"Has RTC: {sys.has_rtc}")
print(f"Date/Time: {sys.date} {sys.time}")

ParameterArraysModel

Device parameter arrays and error tracking.

params = status.parameters

print(f"Has Errors: {params.has_errors}")
print(f"Active Errors: {params.active_errors}")
print(f"Realtime Params: {params.realtime}")

CommandResponseModel

Response from device control commands.

response = await device.turn_on()

print(f"Success: {response.success}")
print(f"Message: {response.message}")
print(f"IDP: {response.idp}")

๐Ÿšจ Exception Handling

The library provides custom exceptions for different scenarios:

Exception Description
PicoConnectionError Connection establishment or communication failures
PicoTimeoutError Operation exceeded timeout duration
NotSupportedError Feature not supported in current operating mode
PicoDeviceError General device-related errors (base class)

Example:

from open_pico_local_api import PicoConnectionError, NotSupportedError

async def safe_operation():
    device = PicoClient(ip="192.168.1.100", pin="1234")
    
    try:
        await device.connect()
        await device.change_fan_speed(75)
        
    except NotSupportedError as e:
        print(f"โš ๏ธ  Feature not available: {e}")
        
    except PicoConnectionError as e:
        print(f"โŒ Connection failed: {e}")
        
    finally:
        await device.disconnect()

๐Ÿ’ก Examples

Ready-to-run example scripts are available in the examples/ directory. See examples/README.md for full documentation and usage instructions.


๐ŸŽฏ Best Practices

โœ… DO

  • โœ”๏ธ Use async context managers for automatic cleanup
  • โœ”๏ธ Enable verbose mode during development
  • โœ”๏ธ Use explicit device_id when controlling multiple devices
  • โœ”๏ธ Handle exceptions appropriately
  • โœ”๏ธ Check mode compatibility before operations
  • โœ”๏ธ Verify device status after using force parameter
  • โœ”๏ธ Use asyncio.gather() for concurrent operations on multiple devices

โŒ DON'T

  • โœ–๏ธ Block the event loop with synchronous operations
  • โœ–๏ธ Ignore connection errors
  • โœ–๏ธ Use the same client instance across multiple event loops
  • โœ–๏ธ Forget to disconnect when not using context managers
  • โœ–๏ธ Use force=True without understanding the consequences
  • โœ–๏ธ Apply incompatible settings without checking device state afterwards
  • โœ–๏ธ Create multiple PicoClient instances for the same device

๐Ÿ“ฆ Library Structure

open-pico-local-api/
โ”œโ”€โ”€ scripts/
โ”‚   โ”œโ”€โ”€ bump_version.sh                # Bump version across all files
โ”‚   โ””โ”€โ”€ run_tests.sh                   # Run the full test suite
โ”œโ”€โ”€ examples/
โ”‚   โ”œโ”€โ”€ basic_control.py               # Connect, read status, control a single device
โ”‚   โ”œโ”€โ”€ multi_device.py                # Concurrent control of multiple devices
โ”‚   โ”œโ”€โ”€ auto_discovery.py              # Discover devices then read their status
โ”‚   โ”œโ”€โ”€ adaptive_climate.py            # Auto-select mode from sensor readings
โ”‚   โ”œโ”€โ”€ monitoring.py                  # Continuous polling with alerts
โ”‚   โ””โ”€โ”€ maintenance.py                 # Check and reset filter maintenance flag
โ”œโ”€โ”€ open_pico_local_api/
โ”‚   โ”œโ”€โ”€ __init__.py                    # Public API re-exports
โ”‚   โ”œโ”€โ”€ pico_client.py                 # Main client class
โ”‚   โ”œโ”€โ”€ pico_auto_discovery.py         # Subnet-based device discovery
โ”‚   โ”œโ”€โ”€ shared_transport_manager.py    # Shared UDP transport for multi-device
โ”‚   โ”œโ”€โ”€ enums/
โ”‚   โ”‚   โ”œโ”€โ”€ device_mode_enum.py        # Operating modes
โ”‚   โ”‚   โ”œโ”€โ”€ on_off_state_enum.py       # Power states
โ”‚   โ”‚   โ””โ”€โ”€ target_humidity_enum.py    # Humidity levels
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ”œโ”€โ”€ pico_device_model.py       # Complete device state
โ”‚   โ”‚   โ”œโ”€โ”€ command_response_model.py  # Command responses
โ”‚   โ”‚   โ”œโ”€โ”€ device_info_model.py       # Device identification
โ”‚   โ”‚   โ”œโ”€โ”€ sensor_readings_model.py   # Sensor data
โ”‚   โ”‚   โ”œโ”€โ”€ operating_parameters_model.py
โ”‚   โ”‚   โ”œโ”€โ”€ parameter_arrays_model.py
โ”‚   โ”‚   โ””โ”€โ”€ system_info_model.py       # System diagnostics
โ”‚   โ”œโ”€โ”€ utils/
โ”‚   โ”‚   โ”œโ”€โ”€ auto_reconnect.py          # Auto-reconnect decorator
โ”‚   โ”‚   โ”œโ”€โ”€ constants.py               # Mode constants
โ”‚   โ”‚   โ””โ”€โ”€ pico_protocol.py           # Base UDP protocol
โ”‚   โ””โ”€โ”€ exceptions/
โ”‚       โ”œโ”€โ”€ pico_device_error.py
โ”‚       โ”œโ”€โ”€ pico_connection_error.py
โ”‚       โ”œโ”€โ”€ pico_timeout_error.py
โ”‚       โ””โ”€โ”€ not_supported_error.py
โ””โ”€โ”€ tests/
    โ”œโ”€โ”€ test_exceptions.py
    โ”œโ”€โ”€ test_enums.py
    โ”œโ”€โ”€ test_models.py
    โ”œโ”€โ”€ test_shared_transport_manager.py
    โ”œโ”€โ”€ test_pico_auto_discovery.py
    โ”œโ”€โ”€ test_auto_reconnect.py
    โ””โ”€โ”€ test_pico_protocol.py

๐Ÿ› ๏ธ Scripts

Utility scripts live in the scripts/ directory and must be run from the repo root.

scripts/bump_version.sh

Keeps all version references in sync across pyproject.toml, README.md, and pico_client.py in one command.

./scripts/bump_version.sh patch     # 2.3.0 โ†’ 2.3.1
./scripts/bump_version.sh minor     # 2.3.0 โ†’ 2.4.0
./scripts/bump_version.sh major     # 2.3.0 โ†’ 3.0.0
./scripts/bump_version.sh 2.5.0     # set an explicit version
./scripts/bump_version.sh           # interactive menu

scripts/run_tests.sh

Runs the full unit test suite with verbose output.

./scripts/run_tests.sh

๐Ÿ“‹ Requirements

  • Python 3.11+
  • asyncio support
  • Local network access to Pico device(s)
  • No third-party dependencies โ€” stdlib only

๐Ÿงช Testing

The library ships with a full unit test suite (96 tests) covering all modules. No third-party packages needed.

Run locally

./scripts/run_tests.sh

Or directly:

python3 -W all -m unittest discover -s tests -v

CI

Tests run automatically on every push and pull request via GitHub Actions, across Python 3.11, 3.12, and 3.13.

Coverage

Module Tests
exceptions/ 10
enums/ 8
models/ 34
shared_transport_manager.py 20
pico_auto_discovery.py 12
utils/auto_reconnect.py 6
utils/pico_protocol.py 6
Total 96

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


๐Ÿ†˜ Support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

open_pico_local_api-2.4.1.tar.gz (37.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

open_pico_local_api-2.4.1-py3-none-any.whl (31.6 kB view details)

Uploaded Python 3

File details

Details for the file open_pico_local_api-2.4.1.tar.gz.

File metadata

  • Download URL: open_pico_local_api-2.4.1.tar.gz
  • Upload date:
  • Size: 37.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for open_pico_local_api-2.4.1.tar.gz
Algorithm Hash digest
SHA256 df988ebb5f5511b1fc2e9141ee54e282d529ed3befda148dadf6b85cb718fb0f
MD5 791a876cca5b58f1f8ce0bfa5d24d8e8
BLAKE2b-256 75769c705df7f17f224f4f8b955f363902bd62dc0db4312653deb7202aff578c

See more details on using hashes here.

Provenance

The following attestation bundles were made for open_pico_local_api-2.4.1.tar.gz:

Publisher: publish.yml on VoidElle/open-pico-local-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file open_pico_local_api-2.4.1-py3-none-any.whl.

File metadata

File hashes

Hashes for open_pico_local_api-2.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d00e888ef7af21a3b467cbf1413bde36717baa78f2a2b2406bb3f6e3b614c04a
MD5 df065975b428e11a81d4cabbdecdf0ee
BLAKE2b-256 48f22e3633a1431878dbeacd43885b8cbee5044a2a82ba2b499dd72a9998d497

See more details on using hashes here.

Provenance

The following attestation bundles were made for open_pico_local_api-2.4.1-py3-none-any.whl:

Publisher: publish.yml on VoidElle/open-pico-local-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page