Architecture
LinkOStar provides hub connectivity, onboarding, and observability as a shared, supplier-agnostic platform. Suppliers keep their business logic (device models, alert rules) in their own BFFs and apps; LinkOStar exposes raw resources only.
Components
┌────────────────────────────────────────────────────────────────────────┐
│ LinkOStar (shared platform) │
│ │
│ ┌─────────────────┐ ┌────────────────┐ ┌─────────────────┐ │
│ │ Web Console │ │ Mobile App │ │ Backend API │ │
│ │ (Next.js) │ │ (Flutter) │ │ (Spring Boot) │ │
│ │ /tenant/** │ │ /app/** │ │ │ │
│ └────────┬────────┘ └────────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └─────────────────────┴────────── REST ────┤ │
│ │ │
│ ┌────────┴────────┐ │
│ │ MySQL + │ │
│ │ (later) MQTT │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────────┘ │
│ ┌─────────────────────┐ │ │
│ │ Hub-Agent (Pi) │◀── /v1/** ────┘ │
│ │ Go binary + │ │
│ │ systemd │ │
│ └────────┬────────────┘ │
│ │ BLE │
│ │ MQTT (optional) │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Bluetooth devices │ │
│ │ (beacons, sensors) │ │
│ └─────────────────────┘ │
└─────────────────────────────────┬──────────────────────────────────────┘
│ X-API-Key + X-Tenant-UUID
│ JWT (sdx-oauth)
▼
┌──────────────────────────────┐
│ Supplier BFF │
│ business aggregation │
└───────────┬──────────────────┘
│
▼
┌──────────────────────────────┐
│ Supplier UI (operator) │
│ / end-user app │
└──────────────────────────────┘Key flows
1. Hub claim (first boot)
- The supplier issues a HubClaimCode from the web console (or the installer auto-issues one).
- The installer flashes the OS image and a boot config (
linkostar-config.json: claim_code, supplier_uuid, backend_url) onto the SD card. - Pi boots → hub-agent finds the boot config →
POST /v1/hubs/claim. - The backend validates the claim code, creates or reuses a HubInstance, issues a HubAuthToken, and runs BundleClaimExpansion.
- Hub-agent stores
hub_uuid + accessToken + refreshTokeninto itsstate.json.
2. Telemetry (steady state)
- The hub-agent reconciler posts
/v1/hubs/{uuid}/telemetryevery ~60s. - The backend inserts into
hub_telemetry. - Supplier UI / web console reads via
GET /tenant/hub-monitoring/{uuid}/telemetry.
3. BLE setup (mobile)
- On boot, hub-agent advertises a GATT service (LinkOStar service UUID).
- The mobile app scans, matches, and connects.
- Wi-Fi scan + credential injection → hub gets WAN → first telemetry success.
- The ConnectivityMonitor flips healthy → BLE advertising turns off.
- If telemetry fails N times in a row, advertising automatically resumes so the operator can re-onboard.
4. Supplier step wizard
- The supplier defines ProvisioningSteps in the web builder.
- At claim time, BundleClaimExpansionService expands those steps into HubProvisioningRun rows.
- The end-user walks the wizard and posts each step result to
POST /tenant/hubs/{uuid}/provisioning/steps/{stepId}/submit— or, since V9, when the step'sconfigJson.external_submit_urlis set, the mobile app POSTs straight to that URL (LinkOStar is bypassed).
5. Supplier OAuth + permission sync (V10~V13)
[Flutter app] [LinkOStar backend] [Supplier OAuth]
| POST /app/supplier-link/authorize
|─────────────────────▶│ store state + PKCE
|◀────authorize URL────│
| open in external browser (RFC 8252)
|──────────────────────────────────────────────────────────▶
| │
| │◀──── GET /auth/supplier-link/callback ─────
| │ exchange code→token + userinfo + UPSERT
| │ tokens stored AES-GCM-encrypted
| │ │
|◀── deep link redirect ─────────────────────────────────
|
| right before external_submit_url
| GET /app/supplier-links/{id}/access-token
|─────────────────────▶│ refresh as needed + return token
|◀── access_token ─────│
[Permission sync (supplier as SSOT)]
/app/hubs · /app/devices ─▶ permission_cache hit?
│ no → call permission_query_url → fill cache
│ yes → filter by cached permissions
supplier change webhook ─────▶ /webhook/supplier-permission/{uuid}
HMAC verified → invalidate cache
[Registration push (LinkOStar → supplier)]
hub_instance INSERT afterCommit
│
▼
SupplierRegistrationEvent (outbox)
│
▼ Dispatcher (@Async)
HMAC + POST → supplier hub_registration_url
│
▼ retry with backoff on failureFull contract details and what the supplier registers with LinkOStar live on Supplier OAuth.
6. Activation outbound (V8)
[hub-agent / BLE etc.] [LinkOStar backend] [sdx-web]
device activates ─────▶│
│ DeviceInstance.activation_status = ACTIVE
│ DeviceActivationEvent INSERT (sync_status=PENDING)
│
│ afterCommit → ActivationSyncDispatcher (@Async)
│
│ POST /api/m2m/billing/activation ──────────▶│
│ credit_ledger debit
│◀───────── 201 / 200 (replay) / 409 ─────────│
│
│ event row sync_status updated
│ EntitlementService.invalidate()Responsibility split
| Responsibility | LinkOStar | Supplier |
|---|---|---|
| Hub discovery / control | ✅ BLE advertise, GATT, MQTT | — |
| End-user authentication | sdx-oauth integration only | BFF translates supplier session into LinkOStar credentials |
| Screen aggregation | — | ✅ BFF fan-out |
| Alerting / anomaly detection | — | ✅ poll telemetry + own policy |
| Device models / product metadata | generic ManagedApp | ✅ supplier business metadata |
| Pricing / contracts | — | sdx-billing or supplier-side |