Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Melbits POD — Unity Comms Library (PodLib)

The tablet side of a buttonless BLE smart toy.

Cross-platform Unity / C# client library that talks to the Melbits POD smart toy over Bluetooth Low Energy. Shipped inside the companion app on iOS and Android in 2020 by Melbot Studios, S.L.

This library is one of three components I built for the POD program: the firmware runs on the device itself, this comms library runs inside the Unity companion app on the player's tablet, and a separate factory desktop application was used by the contract manufacturer during assembly. All three speak the same custom Playground protocol over BLE.

Publication notice. This source is published as a portfolio archive with the express written authorization of Melbot Studios, S.L. All rights remain with Melbot Studios — see LICENSE for full terms. The commercial UnityBleBridge Unity plugin, on which the PodLib/uBLE/ adapter depends, has been removed before publication and must be obtained separately. FOTA production endpoints have been redacted.


What it does

The POD is a BLE peripheral with no screen and no buttons. The tablet running the companion app needs to:

  1. Find and pair with a specific POD belonging to this player — intuitively, with a child as the operator.
  2. Push and pull mission state to the POD over a custom encrypted protocol so the on-device activity engine can run the mission.
  3. Render real-time feedback for the player while the POD is off doing its thing (LightPort animations, connection status, firmware update progress).
  4. Stream sensor data back from the POD for live UI (motion, light, temperature).
  5. Update the firmware over the air when a new release is available.

PodLib is the entire device ↔ tablet contract on the C# side. It exposes a strongly-typed, event-driven API that the Unity game code consumes through a handful of interfaces (IPodManager, IPort, IDFUDevice), without ever touching the underlying BLE stack directly.


Target

Runtime Unity (game engine), targeting iOS and Android
Language C# / .NET Standard 2.0 (Unity's managed runtime)
BLE access Via the commercial UnityBleBridge plugin (removed from this publication)
JSON Newtonsoft.Json (Json.NET), via NuGet / UPM
Size ~8,500 LOC across 29 source files
Doc generation Sandcastle Help File Builder (XML doc comments → HTML reference)

Architecture

┌──────────────────────────────────────────────────────────────┐
│  Unity game code (companion app)                             │
│  Subscribes to events on IPodManager / IPort                 │
├──────────────────────────────────────────────────────────────┤
│  PodLib — public API surface                                 │
│  IPodManager · IPort · IDFUDevice · IDevice                  │
├──────────────────────────────────────────────────────────────┤
│  PodLib — runtime                                            │
│  PodManager (MonoBehaviour) · Port (per-slot FSM)            │
│  LinkManager · DFUAgent · FWDatabase                         │
│  Playground (protocol) · Command · CommandResponse           │
│  EventPacket · StreamElement · BroadcastPacket               │
│  FileXfer · XferQueue · AssetsPack · PodFile                 │
│  MLinkTX (LightPort event stream) · PGCT (AES-CTR)           │
├──────────────────────────────────────────────────────────────┤
│  PodLib/uBLE — adapter to the commercial BLE plugin          │
│  uBLEDevice · uBLEDeviceFinder                               │
├──────────────────────────────────────────────────────────────┤
│  UnityBleBridge (commercial plugin — not redistributed)      │
│  iOS Core Bluetooth · Android BLE · macOS / Windows          │
└──────────────────────────────────────────────────────────────┘

The shape that matters: everything that talks to the BLE plugin lives inside PodLib/uBLE/ — exactly two files. The rest of the library never imports UnityBleBridge types and would compile against any other BLE backend that satisfies the BLEDevice / DeviceFinder contracts. That's the value of an adapter layer: vendor swap is a two-file rewrite, not a library rewrite.

The same separation applies upward: Unity game code never touches Port, PodManager, or any concrete class. It talks through IPodManager and IPort, subscribes to typed events, and is free of plumbing.

See PodLib/architecture.txt for the original FSM design notes (states, per-slot operations, event lists).


Highlights

🪄 LightPort event stream — the tablet side of Magic Link

The POD pairs with the tablet by sitting on top of a circular optical pattern rendered on the screen — Magic Link (see the firmware repo for the device-side decoder and the rationale). This library is the tablet side of that protocol.

Important: this library does not render the LightPort itself. It cannot — rendering means drawing to a Canvas / RawImage / MeshRenderer of the game's choosing, with the visual style the game designer wants, layered into whatever UI flow the app provides. That's game code, not library code.

What this library does is drive the LightPort by emitting a stream of typed events that tell the game what to render, frame by frame:

public enum LightPortEventType
{
    LP_BEGIN,    // start rendering the circle
    LP_END,      // tear it down
    LP_WHITE,    // this frame must be white (high luminance)
    LP_BLACK,    // this frame must be black (low luminance)
    LP_LEVEL,    // legacy variable-level frame (with Level field 0..1)
    LP_ERROR     // optical handshake aborted / failed
}

Per port (each port = one POD slot):

public event Port.MLINKHandlerDelegate MLinkHandler;
// MLINKHandlerDelegate(MLinkTX.LightPortEvent eventType)

The game subscribes to MLinkHandler on each IPort and, inside its own Update() / animation loop, paints the circle according to the latest LP_WHITE / LP_BLACK / LP_LEVEL instruction. The library guarantees frame timing and pairing semantics; the game owns the visuals.

Implementation lives in PodLib/MLinkTX.cs (event source) and PodLib/Port.cs (per-slot integration with connection state).

🎰 Multi-port slot model

The app can be connected to up to 4 PODs simultaneously, each in its own IPort slot with an independent FSM:

IDLE → FINDING → ATMATCHING → CONNECTING → DISCCHARS →
       SUBSCRIBING → HANDSHAKE → CONNECTED → [DISCONNECTED|RECONNECTING]

Each port has its own:

  • MLinkHandler (LightPort events)
  • StreamHandler (sensor stream when streaming mode is on)
  • EventHandler (async events from the device's Playground protocol)
  • ConnectionHandler (state transitions with typed reasons)
  • Sticky flag — when set, the port auto-reconnects on unexpected disconnects without re-displaying the LightPort

See PodLib/IPort.cs and PodLib/Port.cs.

🛡️ Encrypted application protocol — Playground client

PodLib/Playground.cs and PodLib/Command.cs implement the C# side of the same custom application protocol the firmware exposes over BLE NUS. Every command is a typed C# class (with WaitForCommand async helpers); responses and async events come back through the CommandResponse / EventPacket parsers.

Application-layer crypto is in PodLib/PGCT.cs — AES-128-CTR keyed during the Magic Link handshake, mirroring the firmware's HAL/Crypt.c. The inline doxygen comments are intentionally kept aligned with the firmware-side documentation for cross-reference.

📦 File transfer + virtual file system client

The Playground protocol exposes the device's storage and many of its commands as a pseudo-FTP over BLE. This library wraps that into a proper file-transfer agent:

🚀 FOTA pipeline — DFUAgent + FWDatabase

End-to-end firmware update orchestration on the client side:

  • PodLib/FWDatabase.cs — polls the production update server, validates manifests against a JSON schema, downloads signed firmware images, verifies SHA-256 hashes (production endpoints redacted in this publication).
  • PodLib/DFUAgent.cs — orchestrates the end-to-end client-side update flow: drives the device into DFU mode, sequences the upload, reports progress to the UI, and handles failure modes (mid-transfer disconnect → resume, bad signature → roll back). The actual Nordic Secure DFU protocol transport (iOS Core Bluetooth / Android Nordic DFU Library) lives in a separate Melbot internal library (Melbot.Platform.AndroidiOS.NativeFeatures) and is invoked from here through a thin AndroidiOSMisc.* façade.

🎯 BLE advertising parser + candidate matching

PodLib/BroadcastPacket.cs decodes the POD's custom advertising manufacturer data — the same packet format the firmware emits with state flags (Magic Link detected, reconnection in progress, MLLess mode, etc.). PodLib/CandidateDevice.cs and PodLib/DeviceFinder.cs classify scan results so the right device gets connected to the right slot.

📜 Sandcastle-grade XML documentation

Every public member carries proper <summary>, <param>, <returns>, and <seealso cref="..."> cross-references. When the library shipped, those XML comments were compiled into a navigable HTML reference by Sandcastle Help File Builder via a tiny .shfbproj (omitted from publication — it was scaffolding pointing at the Unity project, regenerable in seconds). The doc content, which is what matters, is right there in the source.


What I'd do differently in 2026

This library shipped in 2020 inside Unity ~2019 LTS, on top of the patterns and tooling that were idiomatic at the time. With six years of distance, these are the changes I would deliberately make today.

Async model

  • Replace events + coroutines with async/await and IAsyncEnumerable<T>. Operations like AttachNew, WaitForCommand, Reconnect, and the file-transfer agents are all conceptually async, and the current callback + coroutine style (PodLib/WaitForCommand.cs) predates Unity's mature async support. Modern Unity can run Tasks on its main thread via UniTask or Awaitable, which would collapse a lot of glue code.
  • Adopt System.Threading.Channels for the stream/event pipelines.

Testability

  • Extract the protocol layer from MonoBehaviour. Today PodLib/PodManager.cs inherits from MonoBehaviour, which couples it to Unity's lifecycle and makes pure-C# unit tests painful. A pure POCO core plus a thin Unity bridge would unlock NUnit / xUnit testing without an editor instance.
  • Fake BLEDevice and DeviceFinder in tests. The adapter pattern already isolates UnityBleBridge — same trick can power test doubles. The fact that we never invested in this in 2020 is the single biggest gap.
  • Recorded BLE traffic replay. A handful of .bin captures from a real device session, replayed against the parser, would catch 90% of regressions in Playground / CommandResponse / EventPacket for free.

Architecture

  • Move from inheritance to composition in the per-slot FSM. Port.cs at 623 LOC is honest, but a state-pattern split with one file per state would unlock unit testing per state and remove the long switch in the message dispatch.
  • ADRs (Architecture Decision Records) for the load-bearing calls: why a custom file-server abstraction over the BLE NUS service rather than GATT-native characteristics; why AES-CTR (and not AEAD) at the application layer; why we paired multi-slot with per-port FSMs rather than a global session manager.

Security

  • Replace AES-CTR with an AEAD construction end-to-end. The current PGCT.cs provides confidentiality but no integrity — a man-in-the-middle on the link with known plaintext can flip ciphertext bits and the device cannot detect it. AES-GCM or ChaCha20-Poly1305 closes that. This is the same finding called out in the firmware repo's "What I'd do differently" section, and the fix has to happen on both sides at once.
  • Make Magic Link mutually authenticated. Today the optical channel transfers a nonce in one direction. A signed challenge back over BLE would close the asymmetry.

Distribution

  • Ship as a UPM (Unity Package Manager) package with a proper package.json, semver versioning, an .asmdef per assembly boundary (one for the protocol POCOs, one for the Unity-coupled runtime, one for the UnityBleBridge adapter), and explicit IL2CPP [Preserve] annotations on the JSON DTOs.
  • CI build matrix that compiles against the supported Unity versions on each PR, plus a dotnet test job for the POCO core.

Observability

  • A structured log sink that surfaces protocol-level events (connection state changes, command latencies, BLE retries, DFU progress) to the game's analytics layer. The current logging is ad-hoc Debug.Log calls, which is fine during development and useless in production.

None of this invalidates what shipped — the library was the BLE + protocol foundation of a product that passed certification and reached store shelves. But the engineering bar for "shipped in 2020" and "engineered correctly in 2026" are different bars, and a portfolio that pretends otherwise is not useful to anyone.


Companion repositories

This library is one piece of a larger system:

  • melbits-pod-firmware — The device-side firmware (C, nRF52810, BLE peripheral + custom application protocol + signed DFU bootloader + Magic Link decoder). Already published.

My role

I joined Melbot Studios as Tech Lead for the POD program and owned the full device ↔ tablet communication path, the Melbits Pod firmware and tooling, and construction of initial prototypes end-to-end.

On the tablet side that specifically meant:

  • API design — the IPodManager, IPort, IDFUDevice surface that the rest of the Unity app consumed.
  • Protocol implementation — the Playground client (commands, responses, events, streaming, file transfer, encryption).
  • Multi-slot runtime — concurrent connections to up to 4 PODs with independent FSMs, candidate matching, and per-slot UX states.
  • Magic Link LightPort orchestration — the typed event stream driving the game's optical rendering loop, paired with the firmware-side optical decoder I also wrote.
  • OTA / DFU client — manifest fetch, image download + verification, DFU drive, progress reporting, rollback on failure.
  • Adapter to the commercial BLE plugin — keeping the rest of the library vendor-agnostic.
  • Generated reference docs with Sandcastle from the XML comments.

The non-comms parts of the Unity companion app (game design, game logic, art, AR, sound design) were the work of the rest of the Melbot team — I owned the embedded firmware, embedded layer, the cross-platform comms, and the contract between them and everything else -- including providing support to the game programmers using the firmware, this library, and my other tools, reporting directly to the CEO.

Beyond the POD program, during my time at Melbot Studios I also programmed and optimized other titles the studio shipped — work that lives outside this repository.


A note on running this code

This repository is a portfolio archive, not a buildable package. The library has hard dependencies that are intentionally absent from this publication — the commercial UnityBleBridge Unity plugin, a sister Melbot native-features C library used by the DFU agent, the production FOTA service endpoints, and the surrounding Unity project that integrated all of it. They are listed in LICENSE for attribution, not as a build manifest.

The code is published for portfolio, preservation, and educational purposes, not compiled. If that's what you came for, start at PodLib/IPodManager.cs and PodLib/IPort.cs — the two public interfaces that define the entire contract — and follow the references from there.


License

This repository is published under a portfolio-only license. All rights remain with Melbot Studios, S.L. Source-available for reading and study; redistribution, commercial use, and derivative works are not permitted without prior written consent. See LICENSE for full terms and the list of third-party components.


Author

Miguel Angel Exposito — Tech Lead, embedded systems, BLE, cross-platform comms. Find me on LinkedIn · GitHub · Blog.

About

C# / Unity comms library for the Melbits POD smart toy — portfolio archive (Melbot Studios, 2020).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages