The problem
Discovery worked. The workflow didn’t.
Kinesis is Tomahawk’s Android ground control system for uncrewed systems. Implementers developed against its SDK and, when a vehicle was discovered, the system automatically created a node that became visible and controllable in Kinesis. That was a good integration experience for the people building plugins, but it left too much work to the operator.
Configuring a ground radio through a web API, or worse, a command line, was impractical for an everyday user and even less suitable for a soldier downrange. We needed a guided way to configure the radio associated with a given vehicle platform, discover that vehicle, connect to it, and persist the settings for future use.
The workflow
Make the technical path repeatable.
Shortly after being promoted from junior engineer to L1, I was entrusted with substantial autonomy to shape this solution. After carrying a Redwood.js customer portal from infancy through production deployment in AWS GovCloud, I spent roughly six months actively contributing to Kinesis and building familiarity with its codebase before taking on the pairing work.
The first hurdle was not the screen itself. Each pairing wizard was a separate Activity defined in the vehicle’s plugin and launched by Kinesis through an explicit Intent, so I first had to establish how a plugin could advertise that a wizard existed and how Kinesis could launch it on demand. We started with one vehicle, where I owned the Android UI and architecture, partnered with an integrator who handled much of the C++ integration with the vehicle itself, and crossed the JNI boundary when the feature required it.
Once that foundation was working, I drew on established Android patterns, including Philipp Lackner’s educational content and public examples, and adapted those techniques to our workflows. The first wizard took about nine months; after the pattern was established, the next three took only a few weeks each.
The first pairing wizard turned those disconnected steps into one guided flow. A user could select the vehicle type, configure the radio, start discovery, review the vehicle that appeared, and complete the connection without leaving Kinesis or learning an implementation-specific command.
Radios were identified by IP address, which let Kinesis reuse the same physical radio across different vehicles. Each vehicle could have its own saved configuration—often a different channel. Enabling one of those saved configurations kicked off discovery: Kinesis located the radio, applied its settings, and attempted to discover the vehicle. Once the vehicle was found, the plugin created a node that Kinesis could display and control. When a connection needed to be retried, the operator could start the process on demand instead of reconstructing the setup from memory or repeating manual commands. In practice, that means setting up the kit ahead of time, then connecting to it quickly when it is needed.
The platform model
Let plugins describe what they support.
Kinesis launched vendor plugins at startup, where each plugin began its discovery loop and advertised the pairing Activities it supported. Some plugins exposed more than one vehicle, each with its own pairing procedure. Kinesis compiled those advertisements into a list of available wizards, which users could launch when needed.
Building that dynamic list was a learning experience of its own. At the time, I had not yet worked with Room, was still developing my fluency with Kotlin Flows, and had not established Hilt in the main application. I used a static registry with an observer-based pub/sub pattern to notify Kinesis when new pairing Activities were available. It solved the immediate problem, but it remains technical debt—an honest reminder that a working first step is not always the final architecture.
That model kept vendor-specific pairing logic within each plugin while giving the main application a consistent way to discover and present it. The result was an extensible integration point without forcing operators to understand how each vehicle worked internally.
What it taught me
Good abstractions remove operational friction.
The SDK solved the technical problem of discovering a vehicle. The wizard solved the product problem of helping a person complete the job. That distinction has stayed with me: an API can be correct and still leave the most important work to the user.
I learned a great deal while building the first wizard, and even more when I revisited the code later. I found public mutable properties, unnecessary suspend functions, long methods carrying too many responsibilities, and domain use cases that did little more than wrap a repository. Seeing those patterns clearly today is a reminder of how much my engineering judgment has grown. I’m grateful for that perspective: the distance between the first implementation and how I would build it today is one of the clearest measures of how far I’ve come.
I was also fortunate to have senior engineers I could lean on when I got stuck. They reinforced that the only silly question is the one you do not ask, and taught me that every engineer is still learning—regardless of title or years of experience. I carry that lesson forward whenever a junior engineer comes to me for help: make the question welcome, work through the problem together, and leave them more confident than when they arrived.
The pairing wizards also became my first production opportunity to establish Jetpack Compose and ViewModel patterns. They gave me a focused workflow in which to prove a more modern UI architecture before helping move the wider Kinesis application forward. More importantly, they taught me that good engineering is iterative: start with a real user problem, establish a repeatable pattern, and make each subsequent feature easier to build than the last.