Every now and then, a project comes along that perfectly combines passion with a technical challenge. For me, that project was PocketDigi. As an amateur radio operator, I've always been fascinated by APRS (Automatic Packet Reporting System), yet often frustrated by the bulky, temporary setups required for mobile operations. The idea of a full-featured digipeater and iGate that could run entirely from my phone and a small TNC was irresistible.

Today, I want to take you behind the scenes of PocketDigi—from its core architecture to the intricacies of talking to hardware and parsing decades-old but rock-solid protocols—all built with Flutter.


The Blueprint: Keeping Concerns Separate

Before touching the UI, the first priority was establishing a clean, maintainable architecture. Without it, this kind of project can quickly turn into a tangle of UI code, Bluetooth streams, and network sockets. The goal was simple: the UI should be a "dumb" client, reacting to data provided by independent, robust services.

The result was a clear three-tier structure:

  • UI (ui/): A single stateful home screen that knows nothing about KISS, AX.25, or APRS-IS. Its only job is to display data from streams and send user commands (like “connect” or “change callsign”) to the services.

  • Services (services/): The brains of the operation, handling all the complex logic, I/O, and state management.

    • tnc_service.dart: Manages the Bluetooth connection, KISS protocol framing/de-framing, and the core digipeater logic.
    • aprs_is_service.dart: Handles TCP socket connections to APRS-IS, login and passcode generation, and bi-directional packet flow.
  • Parser (services/aprs_parser.dart): A stateless utility for translating raw AX.25 frames into structured AprsPacket objects and back.

This separation was essential. I could tackle packet decoding without worrying about buttons, and design a responsive UI confident that the underlying logic was solid.


Talking KISS to a TNC

The first major hurdle was communicating with a Bluetooth TNC. I used the flutter_bluetooth_serial package, but the real challenge was managing the data stream.

KISS is elegantly simple—it wraps raw AX.25 frames between special 0xC0 (FEND) bytes. But a Bluetooth stream is continuous. Frames can arrive fragmented, or multiple frames might come at once.

The solution: a buffer. All incoming data is stored in a List<int>, then processed to detect FEND boundaries. Complete frames are extracted and handled sequentially, ensuring no partial frames are parsed.


Decoding the Airwaves

With AX.25 frames flowing, the next step was interpretation. The APRS spec is decades old and packed with quirks—for example, callsigns occupy 7 bytes, with the six-character callsign left-shifted and the SSID stored in the upper nibble of the seventh byte.

Building the parser was painstaking but rewarding. Every address field was decoded into callsign, SSID, and the “H-bit,” indicating whether a digipeater had already serviced that hop. Encoding worked in reverse, constructing complete frames for transmission or beaconing.


Bridging to the Internet: iGate and GPS

Implementing iGate functionality added two challenges: APRS-IS connectivity and location awareness.

For APRS-IS, a dedicated service handles TCP connections and automated logins, including passcode hashing for the user’s callsign.

The trickiest part was filtering by location. Initial filters like r/30/30 were rejected. The solution: use Flutter’s geolocator and permission_handler packages to:

  1. Request location permission on startup.
  2. Capture the device’s GPS coordinates.
  3. Construct a valid APRS-IS filter (e.g., r/41.8519/-80.7930/50).

This ensured only relevant local traffic was gated to RF, making the iGate truly mobile-aware.


Forging Ahead

The end result is a portable, powerful APRS station. Its clean architecture makes adding features like messaging or maps straightforward. The core services are robust, independent of the UI, and reliable.

PocketDigi was a thrilling journey through Bluetooth communication, byte-level manipulation, and network protocols. It’s proof that with a versatile framework like Flutter, a single developer can create a sophisticated, multi-layered app that bridges hardware and the internet seamlessly.