|
1 | 1 | # A Python Library for UDMI |
2 | 2 |
|
3 | 3 | This project is a high-level, extensible Python library designed to simplify the |
4 | | -development of applications for the **Universal Device Management Interface ( |
5 | | -UDMI)**. It provides a clean, Pythonic abstraction over the core UDMI |
| 4 | +development of applications for the **Universal Device Management Interface (UDMI)**. |
| 5 | +It provides a clean, Pythonic abstraction over the core UDMI |
6 | 6 | specification, lowering the barrier to entry for developers and device |
7 | 7 | manufacturers seeking to create UDMI-compliant IoT solutions. |
8 | 8 |
|
@@ -35,3 +35,193 @@ ${UDMI_ROOT}/clientlib/python/bin/build |
35 | 35 | You can find a few samples demonstrating how to connect a device using different |
36 | 36 | authentication methods as well as other features of the library in |
37 | 37 | `$UDMI/ROOT/clientlib/python/samples`. |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +## Comprehensive Feature List |
| 42 | + |
| 43 | +## 1\. Smart Connectivity & Authentication |
| 44 | + |
| 45 | +**Status:** Available |
| 46 | + |
| 47 | +* **Unified Device Factory:** A single entry point (`create_device`) that instantiates the client, dispatcher, and necessary managers based on the provided configuration. |
| 48 | +* **Auto-Auth Detection:** Logic to automatically select between Mutual TLS (mTLS), No Auth (Anonymous mode), Basic Auth (Username/Password), or RS256 JWT authentication without explicit code changes. |
| 49 | +* **JIT Credential Generation:** A built-in `CredentialManager` detects missing keys and generates RSA/ECC key pairs and self-signed certificates on the fly ("Zero-Config" mTLS/JWT). |
| 50 | +* **Connection Robustness:** Automatic reconnection handling using `paho-mqtt` with configurable exponential backoff parameters to survive network instability. |
| 51 | + |
| 52 | +**Sample Usage: [`simple_connect.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/connectivity/simple_connect.py)** |
| 53 | + |
| 54 | + |
| 55 | +```py |
| 56 | +from udmi.core.factory import create_device |
| 57 | +from udmi.schema import EndpointConfiguration |
| 58 | + |
| 59 | +# The library automatically detects auth type (or lack there) of from this |
| 60 | +# config object |
| 61 | +config = EndpointConfiguration.from_dict({ |
| 62 | + "client_id": "projects/my-project/locations/us-central1/registries/reg/devices/AHU-1", |
| 63 | + "hostname": "mqtt.bos.goog", |
| 64 | + "port": 8883, |
| 65 | + "auth_provider": {"jwt": {"audience": "my-project"}} |
| 66 | +}) |
| 67 | + |
| 68 | +# Automatic wiring of client, dispatcher, and managers. |
| 69 | +# If 'rsa_private.pem' is missing, the factory generates it automatically. |
| 70 | +device = create_device(config, key_file="rsa_private.pem") |
| 71 | +device.run() |
| 72 | +``` |
| 73 | + |
| 74 | +## 2\. Device State Management |
| 75 | + |
| 76 | +**Status:** Available |
| 77 | + |
| 78 | +* **Automated Config Loop:** Automatically handles incoming `config` messages, updates the `system.last_config` timestamp, and publishes the acknowledged `state`. |
| 79 | +* **Static State Injection:** The `SystemManager` accepts a typed `SystemState` object during initialization, allowing manufacturers to inject static identity fields (Serial Number, Firmware Version, Model) without complex subclassing. |
| 80 | +* **Config Sync Latch:** Logic ensures the device does not publish its initial state until a valid Config message is received or a timeout expires, preventing state thrashing on startup. |
| 81 | +* **Atomic Persistence:** Critical state data (such as `restart_count` and active endpoint configurations) is saved atomically to disk to prevent data corruption and ensure continuity after power loss. |
| 82 | + |
| 83 | +**Sample Usage: [`state_injection.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/system/state_injection.py)** |
| 84 | + |
| 85 | +```py |
| 86 | +from udmi.core.managers import SystemManager |
| 87 | +from udmi.schema import SystemState, StateSystemHardware |
| 88 | + |
| 89 | +# Define static device identity once |
| 90 | +static_info = SystemState( |
| 91 | + hardware=StateSystemHardware(make="Delta", model="Red5"), |
| 92 | + serial_no="SN-12345", |
| 93 | + software={"firmware": "v2.0"} |
| 94 | +) |
| 95 | + |
| 96 | +# Inject into the manager using the factory; the library handles the rest |
| 97 | +device = create_device(system_state=static_info, |
| 98 | + persistence_path=/path/to/persistence.json) |
| 99 | + |
| 100 | +``` |
| 101 | + |
| 102 | +## |
| 103 | + |
| 104 | +## 3\. Telemetry & Observability |
| 105 | + |
| 106 | +**Status:** Available |
| 107 | + |
| 108 | +* **Pointset Management:** A dedicated `PointsetManager` handles the `pointset` configuration block, manages sensor values, and runs a background loop to publish `PointsetEvents` at the configured sample rate. |
| 109 | +* **Unified Logging:** A `UDMIMqttLogHandler` integrates with Python's standard `logging` module. It captures application logs (INFO, WARN, ERROR), formats them as UDMI `SystemEvents`, and publishes them to the cloud for remote diagnostics. |
| 110 | +* **System Metrics:** The `SystemManager` automatically collects and reports system health metrics (e.g., RAM usage) in the `system` event stream. |
| 111 | + |
| 112 | +**Sample Usage:** |
| 113 | +[**`telemetry_basic.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/pointset/telemetry_basic.py)** |
| 114 | + |
| 115 | +```py |
| 116 | +# Update the PointsetManager (Internal Cache) |
| 117 | +pointset_manager.set_point_value("supply_temp", 22.5) |
| 118 | + |
| 119 | +# The background thread automatically publishes this at the configured 'sample_rate_sec'. |
| 120 | +``` |
| 121 | + |
| 122 | +[**`logging_integration.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/events/logging_integration.py)** |
| 123 | + |
| 124 | +```py |
| 125 | +import logging |
| 126 | +from udmi.core.logging.mqtt_handler import UDMIMqttLogHandler |
| 127 | + |
| 128 | +# Attach UDMI handler to standard Python logger |
| 129 | +mqtt_handler = UDMIMqttLogHandler(system_manager) |
| 130 | +logger = logging.getLogger("my_app") |
| 131 | +logger.addHandler(mqtt_handler) |
| 132 | + |
| 133 | +# This log appears in the cloud dashboard automatically as a SystemEvent |
| 134 | +logger.warning("High CPU usage detected!") |
| 135 | +``` |
| 136 | + |
| 137 | +## |
| 138 | + |
| 139 | +## 4\. Pointset Control (Writeback & Provisioning) |
| 140 | + |
| 141 | +**Status:** Available |
| 142 | + |
| 143 | +* **Writeback (Actuation):** Handles `set_value` commands from the cloud (e.g., changing a setpoint). The library parses the command and triggers a registered user callback to execute the hardware change. |
| 144 | +* **Dynamic Provisioning:** Seamlessly merges points defined in the initial firmware model with new points provisioned dynamically via the Cloud Configuration. |
| 145 | +* **Polling Support:** Supports a "pull" model via set\_poll\_callback for just-in-time data retrieval. |
| 146 | + |
| 147 | +**Sample Usage:** |
| 148 | +[**`point_writeback.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/pointset/point_writeback.py)**, |
| 149 | + |
| 150 | +```py |
| 151 | +def on_writeback(point_name: str, value: Any): |
| 152 | + print(f"Actuating {point_name} to {value}...") |
| 153 | + # Hardware logic here... |
| 154 | + |
| 155 | +# Register the handler |
| 156 | +pointset_manager.set_writeback_handler(on_writeback) |
| 157 | +``` |
| 158 | + |
| 159 | +[**`pointset_dynamic_configuration.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/pointset/pointset_dynamic_provisioning.py)**, |
| 160 | + |
| 161 | +[**`telemetry_poll_callback.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/pointset/telemetry_poll_callback.py)** |
| 162 | + |
| 163 | +```py |
| 164 | +pointset_manager.set_poll_callback(my_sensor_poll) |
| 165 | +``` |
| 166 | + |
| 167 | +## |
| 168 | + |
| 169 | +## 5\. Over-The-Air (OTA) Updates |
| 170 | + |
| 171 | +**Status:** Available |
| 172 | + |
| 173 | +* **Generic Blob Fetching:** Utilities to fetch data from HTTP(S) URLs or inline Data URIs (`data:base64...`). |
| 174 | +* **Automatic Verification:** The library enforces **SHA256 hash verification** on all downloaded blobs before passing them to the application logic. |
| 175 | +* **Two-Stage Workflow:** Supports a "Process" \-\> "State Flush" \-\> "Post-Process" pipeline to safely handle self-updates and restarts without leaving the cloud in an unknown state. |
| 176 | + |
| 177 | +**Sample Usage: [`ota_update_workflow.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/system/ota_update_workflow.py)** |
| 178 | + |
| 179 | +```py |
| 180 | +sys_manager.register_blob_handler( |
| 181 | + "ota_module_loader", |
| 182 | + process=process_firmware, # Writes file to disk |
| 183 | + post_process=restart_device # Restarts the app |
| 184 | +) |
| 185 | +``` |
| 186 | + |
| 187 | +## |
| 188 | + |
| 189 | +## 6\. Gateway, Proxy & Discovery |
| 190 | + |
| 191 | +**Status:** Available |
| 192 | + |
| 193 | +* **Proxy Lifecycle:** Handles attach and detach messages for sub-devices. |
| 194 | +* **Routed Configuration:** Config messages and Commands targeted at specific proxy IDs are automatically routed to registered handlers. |
| 195 | +* **Discovery Manager:** Implementation of the discovery block to support active scanning using registered `FamilyProvider` drivers and reporting `DiscoveryEvents`. |
| 196 | + |
| 197 | +**Sample Usage:** |
| 198 | +[**`gateway_proxy.py`**](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/gateway/gateway_proxy.py),** |
| 199 | +[**`discovery_scan.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/gateway/discovery_scan.py)** |
| 200 | + |
| 201 | +```py |
| 202 | +# Triggered by cloud 'discovery' command |
| 203 | +class MyBacnetProvider(FamilyProvider): |
| 204 | + def start_scan(self, config, publish): |
| 205 | + # Scan network... |
| 206 | + publish(device_id, DiscoveryEvents(...)) |
| 207 | +``` |
| 208 | + |
| 209 | +## |
| 210 | + |
| 211 | +## 7\. Others: Reliability, Localnet & Lifecycle Management |
| 212 | + |
| 213 | +**Status:** Available |
| 214 | + |
| 215 | +* **State Throttling:** "Dirty bit" logic triggers state updates immediately upon change, subject to a minimum time interval (STATE\_THROTTLE\_SEC) to prevent broker spamming. |
| 216 | +* **Endpoint Redirection:** |
| 217 | + * **Sample usage: [`endpoint_redirection.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/advanced/endpoint_redirection.py)** |
| 218 | +* **Key Rotation:** The `SystemManager` handles the `rotate_key` command and also has a public API `rotate_key`, triggering the `CredentialManager` to generate new keys, backup old ones, and invoke a callback for cloud registration. |
| 219 | + * **Sample usage: [`key_rotation.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/advanced/key_rotation.py)** |
| 220 | +* **Localnet Manager:** Handles the localnet configuration block to validate address families (e.g., mapping generic device IDs to physical BACnet addresses) using registered providers. |
| 221 | + * **Sample usage: [`localnet_routing.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/localnet/localnet_routing.py)** |
| 222 | +* **Lifecycle Commands:** Maps UDMI commands (reboot, shutdown) to registered callbacks or specific process exit codes. |
| 223 | + * **Sample usage: [`lifecycle_commands.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/system/lifecycle_commands.py)** |
| 224 | +* **Custom Persistence Backend:** |
| 225 | + * **Sample usage: [`custom_persistence_backend.py`](https://github.com/faucetsdn/udmi/tree/master/clientlib/python/samples/advanced/custom_persistence_backend.py)** |
| 226 | + |
| 227 | +## |
0 commit comments