1 The boat network
The boat's Wi-Fi, from the plotter or a gateway, often has no internet, so the phone keeps mobile data as its default route.
Four kinds of traffic, kept apart:
- InstrumentsPlotter or gateway to phone, on the boat Wi-Fi. Received only.
- Crew LAN linePhone to phone, on the boat Wi-Fi. One TCP connection per pair of devices, carrying encrypted lines.
- Crew relayPhone to phone through a server on the internet. One WebSocket per device, live messages only.
- CallsWebRTC audio between crew devices. Setup travels over the crew link; the audio goes direct if it can.
The web app cannot open TCP or UDP sockets: it reads no instruments and its crew link uses the relay only.
2 Reading the instruments
The sockets live in the app's foreground service, so they stay open with the screen off and can be bound to one network.
2.1 Protocols and ports
| Source | Transport | Default port | Role of the app |
|---|---|---|---|
| NMEA 0183 | TCP, the app is the client | 10110 | Receive |
| NMEA 0183 | UDP datagrams, unicast or broadcast | 10110 | Receive |
| NMEA 2000, as text | TCP, the app is the client | 1456 | Receive |
| Signal K | HTTP, then WebSocket | 3000 | Receive |
| NMEA 0183 over BLE UART | GATT notifications | n/a | Receive |
Nothing here transmits, except Signal K's subscription request and the one Bluetooth write that turns notifications on.
2.2 NMEA 0183 over TCP
Most Wi-Fi gateways accept one client, so a change of target closes the live socket at once.
| Setting | Value | Notes |
|---|---|---|
| Connect timeout | 1200 ms | |
| Read timeout | 3000 ms | |
| Stale | 20 s | No data |
| Longest line | 1024 bytes | Bounds a misbehaving gateway. |
| TCP keepalive | on | SO_KEEPALIVE |
| Reconnect backoff | 1, 2, 4, 8, 16, 30 s | Then every 30 s. Resets on a good connection. A Wi-Fi change skips the wait. |
| Network | Wi-Fi | The socket is bound to the Wi-Fi network when the host is an IPv4 address on that subnet. |
Without that binding, a socket on the default (mobile data) route never reaches 192.168.1.x. A host given as a name, or an address elsewhere, keeps normal routing.
UDP binds 0.0.0.0 on the configured port (default 10110) with address reuse and broadcast on, so one socket takes a gateway's broadcast and a unicast stream. Same 3 s read timeout and 20 s staleness as TCP.
2.3 NMEA 2000 as text
The app reads no CAN bus itself: a gateway sends CAN frames as text lines over TCP, in four framings detected line by line: Yacht Devices RAW, Actisense ASCII, $PCDIN and $MXPGN.
Yacht Devices RAW time dir CAN id data 19:58:53.469 R 09F8017F 50 C3 B8 13 47 D8 2B C6 Actisense ASCII A+time src dst+pgn data A173321.107 23FF7 1F513 012F3070002F30709F
An Actisense USB gateway plugged into the device goes through the same decoder.
Fast-packet messages are reassembled. These 16 PGNs are decoded; any other is ignored.
| Group | PGNs |
|---|---|
| Heading, attitude | 127250 heading, 127257 attitude (pitch, roll) |
| Speed, depth | 128259 speed through water, 128267 water depth |
| Position | 129025 position rapid update, 129029 GNSS position data |
| AIS | 129038 and 129039 position reports (Class A, B), 129794 Class A static, 129809 and 129810 Class B static (parts A, B) |
| Wind | 130306 |
| Environment | 130310 and 130311 environmental parameters, 130312 temperature, 130316 temperature extended range |
2.4 Signal K
The app asks GET http://host:3000/signalk (1.5 s timeout) for the stream's address, and otherwise uses:
ws://host:3000/signalk/v1/stream?subscribe=self
subscribe=self: the boat's own data, not every vessel the server knows. Reconnects wait 1, 2, 4, 8, 16, then 30 s.
2.5 What is decoded
A sentence with a wrong checksum is dropped; a source that has never sent a checksum is accepted without one.
| Group | Sentences |
|---|---|
| Depth | DBT DPT DBS DBK |
| Heading | HDG HDT HDM |
| Wind | MWV MWD VWR VWT |
| Speed, water temperature, transducers | VHW MTW XDR |
| Position and GNSS quality | GGA RMC GLL GNS GSA GST PGRME |
| AIS | VDM and VDO, message types 1, 2, 3, 5, 18, 19, 21 and 24 |
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A $SDDBT,12.4,f,3.8,M,2.1,F*39 $WIMWV,214.8,R,12.4,N,A*1B $IIHDT,274.07,T*14 !AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C
Multi-sentence AIS messages are reassembled.
2.6 Choosing between sources
Per reading:
- A reading is current for 5 s; an older one is ignored.
- For depth, the shallowest current reading wins. A current "no bottom" report beats a numeric one only if it is at least as new.
- For wind angle,
MWVis preferred over the other wind sentences. - The source in use stays unless a better-ranked one appears, so the display does not flicker between equals.
- The device's own instruments beat readings relayed by crew. A crew reading is used only when this device has no current one, and two crew sources are not switched on a gap under 1.5 s.
3 Finding the plotter
Android only. The app looks for hosts that answer on a known port and checks that they send NMEA, so nobody types an IP address.
3.1 The subnet
The Wi-Fi network with an IPv4 address, not the default network (section 2.2). A prefix of /22 or narrower is swept whole; a wider one, only the /24 around the phone. A subnet of more than 1022 hosts is never swept.
The sweep also skips the phone's own address. A plotter set by name is matched by its resolved address.
3.2 Three ways to find a host
| Method | What it does | Time |
|---|---|---|
| mDNS / DNS-SD | Browses _nmea-0183._tcp, _signalk-ws._tcp and _signalk-http._tcp, one after another. | about 400 ms each |
| Navico GoFree beacon | Joins multicast group 239.2.1.1, port 2052, under a multicast lock. Each plotter announces a JSON datagram with its address, model, name and serial number. The serial is the unit's identity: its DHCP address changes, and Android 10 and later hide other devices' MAC addresses. | 2.5 s |
| TCP sweep | Connects to every address on the subnet, each port in turn, 4 at a time, then reads each open port for a sentence with a valid checksum. | connect 150 ms, read 900 ms |
Ports, in order: the configured one, then 10110, 2000, 1456, 39150, 10111, 5000 and 8888. The first four are known gateway families (Navico and similar, Digital Yacht and Quark-elec, Yacht Devices, Garmin and Vesper); the last three, common serial-to-Wi-Fi boxes. Signal K mode tries 3000. NMEA 2000 mode tries 1456 and 2000.
A host is marked "boat data" if it sends position or heading (RMC, GGA, GLL, HDG, HDT, HDM or THS); the check stops reading as soon as it sees one.
Worst case. On a /24 where every address silently drops packets, the sweep makes 253 attempts, 4 at a time, each ending at the 150 ms timeout: 64 rounds, about 9.6 s per port. All eight ports, about 77 s. In practice hosts answer or refuse in milliseconds; the search runs in the background.
3.3 When it searches
- Never while connected, and never on the first connection attempt.
- When instruments are off or no plotter address is set.
- When the configured plotter has been failing for 20 s.
- Then at most every 120 s while that holds.
- When the Wi-Fi changes: at once, then 10 s and 30 s later, while DHCP and routes settle. What was offered or declined on the earlier network is forgotten.
A configured plotter coming back is the TCP client's reconnect, not a search.
3.4 Picking one
A search connects by itself only to the unit whose serial was remembered from the beacon, else the only host found, else the only host sending position or heading. Otherwise the app shows the list and asks. A unit announcing a different serial from the remembered one is never taken.
4 The crew link
Crew devices share positions, instrument readings, nearby AIS targets, alarms and a shared document holding the crew list.
4.1 A mesh, no leader
Every device sends every message to every other device. No leader, no forwarding: a device never repeats another's message. "Captain" is a role in the shared document, not on the network.
4.2 The LAN line
Devices on the same Wi-Fi find each other by Android network service discovery, advertising _seabearing._tcp on an ephemeral TCP port, and keep a TCP connection to each peer, up to 16. Each message is one line.
{"s":"Qk3xT_9aL2c","c":"7be1…(32 hex)","m":{"v":3,"n":"…","x":"…"}}\n
s salt: 8 random bytes, base64url, new on every line
c tag: first 128 bits of SHA-256 over the crew's channel name and the salt
m the sealed message: version, 12-byte nonce, AES-GCM ciphertext (section 5)
A device finds which of its crews a line is for by hashing each of its channel names with the line's salt. A bystander on the Wi-Fi sees neither which crew is talking nor whether two lines are the same crew's. A salt seen before is dropped (the last 1024 are kept), so a replayed line does nothing.
| Limit | Value |
|---|---|
| Line length | 16 KiB |
| Peers sent to | 16 (crew devices first, then the newest others) |
| Connect timeout | 1500 ms |
| Keepalive line | every 10 s |
| Idle close | 45 s without a line or keepalive |
| Connections held | 8 in all, 2 per address |
| Rate limit, per sending address | 40 lines per 10 s |
| Rate limit, all addresses not known to be crew | 120 lines per 10 s |
Connections are duplex: a line travels either way on whichever socket exists. The keepalive is a fixed line, not a message. Sockets are made on the network that owns the peer's subnet, with TCP_NODELAY and keepalive on.
Plain TCP, no TLS: the lines are already ciphertext (section 5).
4.3 The relay
Off the same network, or with the LAN line down, messages go through a relay server on the internet. Each device opens one WebSocket over TLS and joins a private channel for its crew.
| Setting | Value |
|---|---|
| Heartbeat | every 25 s |
| Unanswered heartbeat | socket closed and reopened |
| Open timeout | 20 s |
| Reconnect backoff | 1 s, doubling, capped at 60 s |
| Acknowledgements | none (ack: false) |
| Echo to sender | none (self: false) |
The relay stores nothing: a message not heard is gone. The socket opens only while pairing or when the crew has other members, and carries only ciphertext.
4.4 LAN or relay
Every message is sent on the LAN line. It is also sent on the relay when the LAN line is not running, or some other member has not been heard on the LAN within the last 4 s. Leaving the crew, and the pairing envelopes of section 5.2, always go on both paths.
When everyone is heard on the LAN, the relay stays joined but carries nothing from this device.
The crew screen's link icon: LAN if a LAN message arrived from the member in the last 15 s, otherwise relay if a relay message did. After 2 minutes of hearing nothing from a member the screen says "Not heard for" and how long.
4.5 What is sent, and when
| Message | Cadence | Notes |
|---|---|---|
| Position | 1 Hz | Only a fix up to 3 s old is sent. |
| Instrument readings | 1 Hz | The readings chosen in section 2.6. |
| Heartbeat | after 2 s of silence | Also what lets each side judge a path (the 4 s rule). |
| AIS targets | every 10 s | A snapshot of up to 512 targets, nearest first. |
| AIS static data | every 10 min | Names and dimensions, repeated for devices that joined late. |
| Document digest | every 60 s | A digest of the shared crew document, so a device that missed a change can see it differs. |
| Raise, Silence | 3 times: 0, 5, 10 s | See section 5.4. |
| Track history | catch-up | Sent in chunks. |
5 Keys and encryption
Encryption is end to end between the crew's devices, above both paths: the network, the relay operator and anyone on the marina Wi-Fi see only ciphertext.
5.1 Primitives
| Purpose | Construction |
|---|---|
| Crew key | 32 random bytes, one per epoch. Every other key is derived from it, with a different label per use. |
| Key derivation | HKDF-SHA-256 |
| Message sealing | AES-256-GCM, a fresh random 12-byte nonce per message, the crew id and epoch bound in as associated data |
| Device identity | A P-256 key pair per device. The private key never leaves the device. A short device tag is the first 8 bytes of SHA-256 of the public key. |
| Key hand-over | A "box": ECDH on P-256, HKDF, then AES-256-GCM. Used to give the crew key to one device. |
| On the LAN | Plain TCP carrying ciphertext |
| On the relay | TLS to the server, and the same ciphertext inside |
| Calls | WebRTC's DTLS-SRTP |
Inside a sealed message is a small record: the crew and epoch, the sender and its device tag, a sequence number, a timestamp, a kind (position, heartbeat, alarm and so on), the body, and optionally the one member it is addressed to.
5.2 Pairing
One QR scan. The code is a one-time pairing secret, not the crew key.
- Invite. The inviter makes a 16-byte random secret and a one-time P-256 key. The QR carries the inviter's device id, the secret, a hash of the one-time public key (a commitment), an expiry 5 minutes ahead, and the inviter's name.
- Scan. Both sides derive a pairing channel name and an AES key from the secret with HKDF. Everything in the pairing is sealed with that key and sent on both paths.
- Join. The joiner sends its id, name, device public key and a one-time public key, every 5 s until answered.
- Crew. The inviter adds the joiner to the crew document and answers with the crew id, epoch, crew key and document, in a box sealed to the joiner's one-time key: only that joiner can open it, and, the code having committed to the inviter's key, only the inviter can have made it. A used code, or a joiner claiming an existing member's id with a different device key, gets "used" or "refused" instead, authenticated by an HMAC under the secret the two one-time keys share.
- Joined. The joiner takes the crew, replacing any it had, and says so at 0, 1 and 3 s.
5.3 Removing a member
Removing a device starts a new epoch: a new random crew key, sealed to each remaining member's device key, so the removed device never gets it, and with it new derived keys and a new channel name.
The old epoch is accepted for 24 hours, for a phone asleep during the change and frames in flight, then dropped.
5.4 Sequence numbers and delivery
Each message carries a sequence number unique per sending device, accepted once, which is what makes sending on two paths safe.
- Allocation. Numbers are reserved in recorded blocks of 1000, so a restart can skip numbers but never reuse one.
- Acceptance. Per sender, a receiver keeps the highest number seen and the recent ones, and drops a number already seen or more than 1024 behind the highest: modest reordering passes, a replay does not. The highest numbers are saved once a minute.
- No acknowledgements. Position and instrument data are replaced a second later.
- Alarm presses. A Raise or a Silence is sent three times, at 0, 5 and 10 s. Each press is acted on once and ignored if it is more than 30 s old when it arrives.
6 Calls
WebRTC audio. The offer, answer, ICE candidates, hang-up and busy signals are ordinary crew messages, addressed to one member and sealed like any other. An offer rings for up to 60 s.
- Topology. One
RTCPeerConnectionper pair: a mesh of up to 6 people. Full duplex; mute turns off the local track. Echo cancellation, noise suppression and automatic gain are on. - ICE servers. From a cloud function, with the user's account session, limited to 120 requests per user per hour: a STUN server on port 443 and TURN credentials from Amazon's managed service, valid for at most 300 s. TURN is used only when the devices cannot reach each other directly.
- Fallback. No answer in 4 s: public STUN (
stun.l.google.com:19302andglobal.stun.twilio.com:3478) and no TURN. - Camera share. WebRTC on the LAN only, with no ICE servers, so only between devices that reach each other directly. Offered to members heard in the last minute, retried every 10 s backing off to 10 minutes, and abandoned if not open within 30 s.
7 What Android has to be told
With the screen off, Android stops an app's networking unless it holds these.
| Resource | Held when | Why |
|---|---|---|
| Foreground service | An anchor watch, an instrument connection or a crew link is active | Keeps the process, its sockets and its timers alive with the screen off. |
| Wi-Fi lock | A network instrument is in use, or the crew link is on | Keeps the radio at full power; low-latency mode on Android 10 and later, high-performance before. |
| Multicast lock | Crew LAN discovery; the Navico beacon listen | Without it the Wi-Fi chip discards multicast packets to save power. |
| Partial wake lock | The crew link is on | Keeps the crew link's timers running with the screen off. |
| Location updates | The crew link is on, where location is allowed | This device's position goes to the crew every second. |
Sockets bound to the Wi-Fi Network | Instrument connections and probes; crew LAN sockets bound to the network that owns the peer's subnet | Mobile data stays the default route, and the boat's LAN still works. |
A change of Wi-Fi network wakes the instrument client out of its backoff and starts the search of section 3.3.
8 Bandwidth
Estimates, not yet measured on a boat: sizes on the wire (a LAN line with its salt and tag, or a relay message with its framing) times the cadences above. MB are 106 bytes.
| Flow, per device | Cadence | LAN line | Relay |
|---|---|---|---|
| Position (own GPS) | 1 Hz | ~440 B/s | ~540 B/s |
| Instruments frame (the device reading the boat's instruments) | 1 Hz | ~910 B/s | ~1,010 B/s |
| Shared-state digest | every 60 s | ~6 B/s | ~8 B/s |
| Heartbeat | after 2 s of silence | 0 while positions flow | 0 |
| AIS, 50 targets | every 10 s | 1.20 MB/h | 1.24 MB/h |
| AIS, 300 targets (sent in two parts) | every 10 s | 6.72 MB/h | 6.80 MB/h |
On the LAN each frame is written once per other device; on the relay it goes up once and comes down to each of the others. For a crew of four with one device reading the instruments, in MB an hour, up / down:
| AIS targets | Instrument device, LAN | Instrument device, relay | Phone, LAN | Phone, relay |
|---|---|---|---|---|
| 0 | 14.7 / 4.8 | 5.6 / 5.9 | 4.8 / 8.1 | 2.0 / 9.6 |
| 50 | 18.3 / 4.8 | 6.9 / 5.9 | 4.8 / 9.3 | 2.0 / 10.8 |
| 300 | 34.8 / 4.8 | 12.4 / 5.9 | 4.8 / 14.8 | 2.0 / 16.4 |
A phone ashore on mobile data in a crew of four uses roughly 100–165 MB down and 20 MB up over a ten-hour night. The AIS snapshot is the largest flow. Track history catch-up is a one-off: about 36 KB per device per hour of track, paced at about one message a second, packed so it fits a LAN line.
Corrections and questions: write to us.