Provider SDK
The BeaconTower Provider SDK (BeaconTower.ProviderSdk) is a .NET 10 library for building device connectivity providers. A provider bridges a transport protocol (MQTT, AMQP, CoAP, HTTP polling, etc.) to the Beacon Tower platform and exposes each connected device as a provider client that platform assets can bind to.
The SDK ships everything that is protocol-agnostic. You implement one interface — IDeviceAdapter — for the protocol-specific parts: how devices connect, how messages arrive, and how commands are delivered.
What the SDK provides
- Device registry, twin storage, and credential management (AES-256-GCM at rest)
- Telemetry pipeline (channel-bounded ingest → batched JetStream publish)
- NATS bus API (request/reply for admin, commands, queries) with
schemaVersion: 1 - CloudEvents 1.0 catalogue (post-commit, dual-transport NATS + HMAC-signed webhook)
- Provisioning orchestration (transactional device + twin + adapter, idempotent on conflict)
- Diagnostics (per-device status, connection logs, flap detection,
/api/diagnostics/info) - Database migrations (DbUp), JetStream stream creation, NATS connection management
You focus on the protocol adapter. The SDK handles the rest.
Architecture
┌──────────────────────┐
│ Onboarding service │
│ (cross-tenant) │
└─────────┬────────────┘
│ NATS + MessagePack
│ (canonical wire, keyed btbus)
▼
Cloud admins ──HTTP+JWT──► ┌──────────────────────────────────┐
(devices-api, │ Provider rig host │
twin-management, │ (e.g. MqttProvider.Api) │
diagnostics) │ │
│ ┌────────────────────────────┐ │
│ │ BeaconTower.ProviderSdk │ │
│ │ │ │
│ │ bus-api ◀── NATS ──────┐ │ │
│ │ devices-api ─HTTP─┐ │ │ │
│ │ twin-management ─HTTP┤ │ │ │
│ │ diagnostics ─HTTP─┘ │ │ │
│ │ │ │ │
│ │ ITemplateClient ──NATS▶│ │ │
│ │ (→ core-management) │ │ │
│ │ │ │ │
│ │ TelemetryPipeline ─JS─►│ │ │
│ │ EventPublisher ──CE───►│ │ │
│ │ │ │ │
│ │ AdapterContext │ │ │
│ │ ▲ │ │ │
│ │ │ │ │ │
│ │ IDeviceAdapter │ │ │
│ └─────────┬───────────────┘ │ │
│ │ │ │
│ ┌─────────▼──────────────┐ │ │
│ │ Your protocol adapter │ │ │
│ │ (e.g. ProviderMqtt: │ │ │
│ │ MqttAdapter + │ │ │
│ │ topic-map registry + │ │ │
│ │ mqtt-auth) │ │ │
│ └─────────┬──────────────┘ │ │
└─────────────┼──────────────────┘
│
▼
Protocol broker / network
│
▼
Devices
Key seams
IDeviceAdapter— the only protocol-specific type the host implements. Everything else — auth, twin, credentials, telemetry pipeline, events — ships in the SDK and is wired byAddBeaconTowerProvider<TAdapter>.AdapterContext— the SDK's outbound surface to the adapter:ITelemetryClient,IPropertiesClient,IDiagnosticsClient. Adapters call into the SDK through this; the SDK calls out to the adapter throughIDeviceAdapter. There is no other adapter↔SDK coupling.ITemplateClient— the SDK building block fortemplateId-passthrough. Onboarding sends only the template ID on provision; the SDK resolves it against core-management over the canonical wire and caches the result for 60 s. See Template Resolution.- NATS is the data plane and the control plane — telemetry on JetStream
TELEMETRY, lifecycle events on JetStreamBEACONTOWER_EVENTS(CloudEvents 1.0 structured mode), bus-api request/reply on subjects underbt.provider.{providerId}.*, post-commit internal broadcasts onbt.{providerId}.internal.*(empty payload), and the cross-tenant onboarding↔provider path now uses the canonical wire (NATS + MessagePack via the keyedbtbusconnection fromBeaconTower.BusApi). - HTTP+JWT for human/admin paths only (
/api/devices,/api/devices/.../twin,/api/diagnostics/*). The pre-1.38 HTTP+OAuth2provider-apipath is removed — see Canonical wire andtemplateIdpassthrough.
Canonical wire and templateId passthrough
The provider control-plane RPC (provision / unprovision / get-credential / device-exists / set-state / revoke-certificate / get-device-data / set-desired / invoke-method / get-desired / get-reported) runs on the canonical BeaconTower wire:
- Transport: NATS, via the
BeaconTower.BusApikeyedINatsConnection(DI key"btbus"). - Serialization: MessagePack (
[MessagePackObject]records fromBeaconTower.Bus.Contracts). - Subjects: built from
BusApiSubjects— single source of truth. - Reply envelope:
BusApiResult<TResponse>discriminates success vs. structuredErrorResponseso error replies can never deserialize as malformed success bodies.
The pre-1.38 HTTP+OAuth2 provider-api path is removed. Onboarding ≥ 1.41 and
mqttprovider ≥ 1.38 must roll forward together; there is no soft-deprecation
window. Integrators that operate a provider against onboarding's NATS RPC
(in-house bridges to a cloud IoT hub, for example) update against
BeaconTower.Bus.Contracts ≥ 1.1.0 and wire AddBeaconTowerBusApi in
Program.cs — see Configuration › Bus API for the
options block.
templateId passthrough is the second half of the migration. Onboarding sends
only an opaque TemplateId field on the provision request; the SDK's
ITemplateClient resolves it against
core-management on demand, with a 60 s positive + negative cache and a 5 s
application timeout. The provider — closest to the device, already on the bus
— owns template caching now; onboarding does not. See
Template Resolution for the full wire shape, cache
semantics, and transport-fault mapping.
When to use the Provider SDK
Use the SDK when you need to connect a class of devices that speak a specific protocol to Beacon Tower. The SDK gives you:
- Device lifecycle — provisioning, credentials, state management
- Telemetry forwarding — high-throughput batched pipeline to NATS JetStream
- Twin management — desired/reported properties with versioning, JSON Merge Patch (RFC 7396) updates
- Command delivery — cloud-to-device method invocation with a closed
CommandStatusoutcome set - Platform integration — CloudEvents, HMAC-signed webhooks, onboarding service compatibility
You focus on the protocol adapter. The SDK handles the rest.
Pre-decided invariants
These are rig-wide invariants every adapter inherits — they are not re-litigated per spec:
- JSON casing — camelCase on every wire surface owned by the SDK (HTTP, NATS req/rep, CloudEvents
data). - JWT auth on every HTTP endpoint —
DevelopmentusesDevBypassAuthHandler; production binds Microsoft'sJwtBearertoAuthenticationOptions:MetadataAddress/AuthenticationOptions:Authority/AuthenticationOptions:Audience, matching the canonical pattern inBeaconTower.Core.API. - Post-commit publishing — CloudEvents and internal NATS broadcasts are emitted after the DB transaction commits, never inside it. A failed publish does not roll back the DB.
schemaVersion: 1— every bus-api request carries the field; the SDK rejects requests without it. Consumers must ignore unknown fields for forward compatibility.- Internal broadcast subject —
bt.{providerId}.internal.{kind}.{deviceId}with empty payload. Recipients invalidate caches and re-read state. Used forregistry-invalidate,state-changed,desired-changed,cert-revoked.
Next steps
- Getting Started — Build your first provider in 15 minutes
- Adapter Interface — The
IDeviceAdaptercontract - Template Resolution —
templateId-passthrough wire, 60 s cache, transport-fault mapping - Telemetry Pipeline — How messages flow from adapter to NATS
- Configuration — Options and environment variables
- Credentials — AES-256-GCM at rest, master-key rotation
- Error Handling — Failure modes and diagnosis
- Health Checks — Liveness and readiness wiring
- Testing — Unit + integration patterns for an adapter
- Operational Notes — Production gotchas (startup ordering, bcrypt, broker recovery)
- Reference — Complete API surface, HTTP endpoints, NATS subjects