Skip to main content

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 by AddBeaconTowerProvider<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 through IDeviceAdapter. There is no other adapter↔SDK coupling.
  • ITemplateClient — the SDK building block for templateId-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 JetStream BEACONTOWER_EVENTS (CloudEvents 1.0 structured mode), bus-api request/reply on subjects under bt.provider.{providerId}.*, post-commit internal broadcasts on bt.{providerId}.internal.* (empty payload), and the cross-tenant onboarding↔provider path now uses the canonical wire (NATS + MessagePack via the keyed btbus connection from BeaconTower.BusApi).
  • HTTP+JWT for human/admin paths only (/api/devices, /api/devices/.../twin, /api/diagnostics/*). The pre-1.38 HTTP+OAuth2 provider-api path is removed — see Canonical wire and templateId passthrough.

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.BusApi keyed INatsConnection (DI key "btbus").
  • Serialization: MessagePack ([MessagePackObject] records from BeaconTower.Bus.Contracts).
  • Subjects: built from BusApiSubjects — single source of truth.
  • Reply envelope: BusApiResult<TResponse> discriminates success vs. structured ErrorResponse so 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 CommandStatus outcome 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 endpointDevelopment uses DevBypassAuthHandler; production binds Microsoft's JwtBearer to AuthenticationOptions:MetadataAddress / AuthenticationOptions:Authority / AuthenticationOptions:Audience, matching the canonical pattern in BeaconTower.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 subjectbt.{providerId}.internal.{kind}.{deviceId} with empty payload. Recipients invalidate caches and re-read state. Used for registry-invalidate, state-changed, desired-changed, cert-revoked.

Next steps