Skip to content

Latest commit

 

History

History
139 lines (114 loc) · 8.01 KB

plugin-development.md

File metadata and controls

139 lines (114 loc) · 8.01 KB

Plugin development

A plugin is an independent Mix project with its own mix.lock and ordinary OTP supervision tree. The gateway owns infrastructure; plugins own protocols and equipment semantics. No custom adapter framework or separate web server is needed.

Selected plugins

plugins/selection.exs selects plugins/tailnet_subrouter, plugins/modbus and plugins/iotrouter_expansion. The experimental expansion driver opens only its assigned bus on explicit Start; see its README. Each project declares its dependencies normally. The Modbus plugin declares {:yamodbus, "== 0.1.0"}; its private library code is bundled with it. It also declares circuits_uart 1.7.0, supplied natively by firmware and never included in the pure-BEAM plugin archive. LiveView is a direct dependency only of plugins that provide UI. Keep shared library versions aligned with firmware. No contributed native libraries, executables or NIFs.

Build firmware from the repository root:

mix firmware x86_64
mix firmware ec100

These commands build assets and isolated plugin projects, validate the whole selection and generate the firmware inventory before Nerves assembly. They do not boot or deploy devices. Direct device-project builds require prepared plugin output; use the root commands to rebuild selection and sources together.

After a separately authorized firmware deployment, open /plugins. Start a plugin, open its page and configure its functionality explicitly. Adding or changing a plugin requires another firmware build. Changed package bytes require a new version; there are no same-version replacement or downgrade workarounds.

Test and package independent projects

cd plugins/modbus
mix deps.get
mix test
MIX_ENV=prod mix nerves_gate.package --output /tmp/modbus-plugin.ez

The Modbus host tests cover validation, SD configuration storage, admission and fail-closed Start without the platform; no socket or port opens. Its real platform and protocol flow runs in the QEMU plugins scenario. No equipment is contacted. Pure policy/component tests run on the host; device lifecycle and SDK ownership qualification use owned QEMU scenarios. Host success is not runtime/vendor-equipment qualification.

mix check includes SDK/firmware/root checks and both plugin test suites. It never starts QEMU. The plugins scenario covers firmware discovery without commissioned SD, explicit activation, UI cleanup and a real Modbus TCP server/client state-I/O roundtrip (desire, write, feedback, reconciliation, loss) on a disposable QEMU device. The user owns mix dev and device starts; do not launch/reset a bench implicitly.

Plugin contracts

Declare the SDK dependency and matching nerves_gate_sdk_requirement OTP environment entry. This checkout uses ~> 0.2.0-dev; use the local SDK path until that version is published. The runtime checks the declared requirement, not capability probes.

Use the OTP application callback and supervised workers normally. An optional nerves_gate_health_check MFA verifies startup, not external device readiness. An optional nerves_gate_ui module declares a verified LiveComponent. UI commands must call the plugin's supervised API; the hosting LiveView cannot own SDK claims. See runtime for dependency ownership and cleanup guarantees.

Read-only hardware observations can be queried through the SDK. Protocol clients should use normal libraries/TCP. Claim exclusive shared resources only where needed: RS485, CAN and IOTRouter expansion-bus workers open exclusively assigned resources, and local Ethernet addressing requires an exclusively assigned LAN plus a caller-owned network lease. Query assignments through NervesGateSDK.Hardware.resources/0. SDK 0.2.0-dev.5 adds read-only Hardware.candidates/0 for setup forms; it exposes owner/permission without granting access. Resource IDs and labels are board data: choose from candidates of a supported kind and link to /hardware/<id>?plugin=<app>, never a fixed port list. Save drafts before navigating to gateway-owned assignment review because checked unload closes plugin pages. There is no administrative assignment SDK API. Keep transport workers supervised by the plugin. There are no SDK bus transport proxies. Local-network addressing still uses platform leases; creating a TCP connection does not claim or reconfigure the Internet interface. See hardware.

State I/O foundation

The firmware NervesGate.IO domain and SDK NervesGateSDK.IO expose input registration, Readings, subscriptions and exclusive output Authority using shared IO.InputDefinition, IO.OutputDefinition, IO.Evidence and IO.Reading structs. Outputs are handled by the NervesGateSDK.Output runtime. SDK calls preserve managed calling-process ownership. The Modbus plugin is the reference integration; the gateway I/O page shows Readings, Desires and Status. These APIs require the local SDK ~> 0.2.0-dev.3, not the published 0.1.0 package.

For persistent settings, NervesGateSDK.Storage.save_config/1, restore_config/0 and delete_config/0 keep one opaque term per plugin version on commissioned SD (SDK ~> 0.2.0-dev.2). Validate the restored term yourself; a new version starts empty. See storage and state.

IO.register_input/1 and IO.publish/3 use plugin-local IDs and actual managed caller membership; only the input registrar publishes its Readings. Registrations must match saved definitions. Bindings and Readings are live only. Publish invalid/unknown quality with nil when there is no usable sample. Inputs are latest state, not reliable press/pulse events: expose occurrences that must not be missed as counters or sequence numbers.

For outputs, start NervesGateSDK.Output under your supervision with the output definitions and a callback module implementing apply/3 (see the SDK README). The runtime hides change hints, reading Desires and claim epochs: it always applies the newest Desire, discards outcomes from an ended claim or superseded Desire, and never calls apply/3 at startup. apply/3 receives nil once an applied Desire ends; perform your declared release behaviour, never an implicit default. Return :applied only when the operation appeared successful, :unknown when it may or may not have happened, otherwise {:failed, reason}.

Only repeatable state-setting operations belong here. Plugins serialize actual writes and own polling, reconnection, readback, retries and release behaviour. Ask the runtime to reapply after a reconnect or a disagreeing readback, rather than replaying old hints. Declare readback and physical feedback as evidence on the output definition and say whether it is a register/coil value or a physical condition; declare on_release so operators know what happens. Setting a Desire never fabricates evidence.

Clearing ownership or losing a binding causes no implicit physical operation and does not cancel work already in flight. Plugin shutdown/fallback and equipment watchdogs remain necessary where appropriate. This is not a safety system.

Disposable development installs

The workbench's plugin-project tools also support creating/testing/packaging independent OTP projects and installing development artifacts on commissioned QEMU storage. mix plugin.new, plugin.test, plugin.package and plugin.install refer to ordinary OTP development projects, not an app store. Build processes remain isolated from the workbench VM. Existing immutable output/version conflicts are errors.

Physical development installs require review and typed-address confirmation through Workbench.Jobs on the plugin's Develop → Plugins page. Transfer uses distribution: verify device identity, package hash, boot/storage identity and compatibility; confirm once, install stopped, then inspect state. They cannot replace firmware-owned plugins. Unknown outcomes are never automatically replayed. Do not use this workflow to bypass the firmware selection or update the physical gateway without authorization.