131 lines
3.9 KiB
Markdown
131 lines
3.9 KiB
Markdown
# Writing a connector
|
|
|
|
A connector adds a device or service integration to the node palette. It is
|
|
a Python package with a `ConnectorNode` subclass and an entry point.
|
|
This example works without hardware or another repository.
|
|
|
|
## Create the package
|
|
|
|
Make this directory beside your project:
|
|
|
|
```text
|
|
fluksio-connector-random/
|
|
pyproject.toml
|
|
src/
|
|
fluksio_connector_random/
|
|
__init__.py
|
|
```
|
|
|
|
Put this in `pyproject.toml`:
|
|
|
|
```toml
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[project]
|
|
name = "fluksio-connector-random"
|
|
version = "0.1.0"
|
|
requires-python = ">=3.12"
|
|
dependencies = ["fluksio>=0.2,<0.3"]
|
|
|
|
[project.entry-points."fluksio.node_types"]
|
|
random_sensor = "fluksio_connector_random:RandomSensor"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/fluksio_connector_random"]
|
|
```
|
|
|
|
Put this in `src/fluksio_connector_random/__init__.py`:
|
|
|
|
```python
|
|
import random
|
|
|
|
from pydantic import Field
|
|
|
|
from fluksio.flow.connector import CONTRACT_VERSION, ConnectorNode
|
|
|
|
|
|
class RandomSensor(ConnectorNode):
|
|
contract = CONTRACT_VERSION
|
|
title = "Random sensor"
|
|
description = "Emits a random reading. No sensor required."
|
|
|
|
class Params(ConnectorNode.Params):
|
|
ceiling: float = Field(default=1.0, gt=0)
|
|
|
|
async def poll(self):
|
|
return {"reading": random.random() * self.config.ceiling}
|
|
```
|
|
|
|
The entry point name, `random_sensor`, becomes the stored node type.
|
|
Choose a unique name; the engine skips names that are already registered.
|
|
|
|
## Install and try it
|
|
|
|
Use the **engine's Python environment**, the one where you installed Fluksio:
|
|
|
|
```sh
|
|
pip install -e /path/to/fluksio-connector-random
|
|
```
|
|
|
|
Restart the engine from its original project directory so it keeps using
|
|
the same instance data:
|
|
|
|
```sh
|
|
fluksio serve
|
|
```
|
|
|
|
Restart an already running engine after installing. In the browser:
|
|
|
|
1. Add a **Random sensor** node to a live flow.
|
|
2. Set `poll_interval` to `5` seconds.
|
|
3. Add an output port named `reading`, type `float`.
|
|
4. Publish the flow and watch the value change.
|
|
|
|
The palette shows the package name and version. Code edits need an engine
|
|
restart; metadata changes also need another editable install.
|
|
|
|
For Docker, install the package in the backend image and rebuild it.
|
|
The Modules screen installs packages for **node code**, so it does not
|
|
install connectors into the engine.
|
|
|
|
## Connect a real device
|
|
|
|
Replace `poll()` with your device read. Return a dictionary keyed by declared
|
|
output port, or `None` when there is nothing new. The base class polls at
|
|
`poll_interval`, publishes changed values, and reports failures.
|
|
|
|
Keep blocking I/O off the event loop with `asyncio.to_thread`. For pushed
|
|
updates, manage the connection in `start()` and `stop()`, leave
|
|
`poll_interval=0`, and call `self.inject({...})` when data arrives.
|
|
|
|
Mark credential parameters with
|
|
`Field(default=None, json_schema_extra={"x-secret": True})`. The editor
|
|
then stores a secret reference rather than the credential in the flow.
|
|
|
|
To send commands, implement `write(self, **ports)`: it receives values keyed
|
|
by input port. Set `idempotent = False` for commands that must not be replayed
|
|
after a crash.
|
|
|
|
## Media and persistent state
|
|
|
|
Use `self.save_artifact(data, name, media_type=...)` for bytes such as camera
|
|
frames. Return the reference through an `image`, `audio`, or `video` port.
|
|
For disposable live frames, pass `volatile=True`.
|
|
|
|
Use `self.store` for short strings that must survive a restart, such as a
|
|
pairing token. Include the device address in the key: all nodes from the
|
|
same top-level package share this store.
|
|
|
|
## Before sharing
|
|
|
|
- Declare `contract = CONTRACT_VERSION` on the class itself.
|
|
- Return only declared output ports, with values matching their types.
|
|
- Mark credentials as secrets.
|
|
- Keep `stop()` safe after a failed start and when called twice.
|
|
- Report connection health if you manage your own subscription.
|
|
|
|
See [the connector contract](../reference/connector-contract.md) for lifecycle,
|
|
storage limits, and certificate pinning.
|