Where This Started

Why build a custom watch app?

The hardware from LilyGO was great. The official companion phone app from the manufacturer was far enough out of date that it wouldn't install or run on a modern phone, which left the thruster with nothing to drive it. So I went through their firmware, reverse-engineered the UDP packet structures they were using, and built my own tactile Wear OS smartwatch controller for it.

Network Discovery

Subnet Scanning & Multicast.

Once I understood the protocol, the first hurdle was finding the boat on the network. The LilyGO hardware creates an ad-hoc Wi-Fi network, and the smartwatch has to work out the target device's IP address on its own. A hardcoded IP is brittle, and a blind broadcast wastes battery and congests the network, so the watch probes the subnet one address at a time.

Discovery Layer

Safe-Payload Pinging

The app runs its own subnet scanner. It iterates from subnet.1 to subnet.254, firing a crafted "zero-throttle" UDP payload. A probe that lands on the thruster leaves the motors sitting still, so discovery can never spin them up.

Telemetry Acknowledgment

UDP Port 2390

The watch listens concurrently on UDP Port 2390. When the hardware receives the safe ping, it immediately returns a telemetry packet containing signal strength (RSSI) and battery voltage. The watch captures the sender's IP address and locks onto it, which completes the handshake.

Reverse-Engineering Hurdles

Physical Protocol Negotiation.

Writing software for undocumented hardware puts real silicon on the line. During testing, I found that different iterations of the LilyGo control boards interpreted the throttle bytes completely differently.

The LilyGo Classic (V1) Short: On older hardware revisions, sending a raw 0 integer made the PWM driver short the H-Bridge electrically, risking permanent damage to the silicon. The valid range was 1 to 128, with 1 as absolute zero throttle. The newer V2 hardware uses a standard 0 to 255 range.

So the ThrusterController.kt engine coerces every value on the way out. Before any UDP packet fires, the joystick's 0.0 to 1.0 float vector passes through a deterministic protocol filter. If V1 is selected, 0.0 clamps to 1, which guarantees the silicon will never receive a short-circuit command.

UI Engineering

Kinematic Jetpack Compose

Controlling differential thrust on a 1.4-inch circular smartwatch screen takes precision. The joystick has to be bounded by the same circle as the display, since a square bounding box leaves dead zones at the corners.

Atan2 Geometry

Radial Bounding

The custom Compose Canvas joystick tracks drag gestures using Pythagorean distance checks. If the user drags outside the permitted circle, the app uses atan2() to calculate the exact angle and clamps the thumbpad to the edge of the radius.

Tactile Feedback

Haptic Edge-Limits

When the drag vector crosses the maximum boundary radius, the Compose layer triggers the watch's internal Vibrator motor. That physical "click" confirms maximum throttle by feel, so an operator driving the thruster can keep their eyes off their wrist.