Most edge demos look great on a laptop.
I'm currently building HormigasAIS, a lightweight edge architecture designed to run agents and services from constrained devices. One of the core pieces is a binary protocol called LBH (Lenguaje Binario HormigasAIS) and a small WebSocket client that keeps the channel alive under real mobile conditions.
This post shows the current state of that client — specifically the heartbeat + telemetry layer — a recent resilience improvement for Android background behavior, and what happened when I moved from a browser demo to an actual installed app on the phone.
The Core: LBHHeartbeatAnt
The client is a single class that manages:
- WebSocket connection to a local Edge Node (
ws://hostname:8765) - Binary LBH frames
- Heartbeat (pheromone) messages
- Inactivity detection
- Automatic reconnection
Key design decisions:
- Binary frames start with magic bytes
"LA"(0x4C,0x41) - Type
0x01→ Telemetry - Type
0x02→ Heartbeat / ACK (called "Feromona") - When the user is inactive for 30 seconds, the client enters a
HIBERNATINGstate while keeping the socket open - Any user activity (click, touch, keydown…) "unfreezes" the channel
Here's the essential part of the heartbeat emission:
emitHeartbeatPheromone() {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setUint8(0, 0x4C); // L
view.setUint8(1, 0x41); // A
view.setUint8(2, 0x02); // Heartbeat / Feromona
view.setUint8(3, 0x01); // Standby flag
view.setUint32(4, 9999);
view.setUint32(8, 0);
view.setUint32(12, Math.floor(Date.now() / 1000));
this.ws.send(buffer);
}
Telemetry frames follow the same structure (type 0x01) and carry a sensor ID + value.
Real Runtime Behavior
From the Edge Node logs you can see the system working:
🐜 Trama 0x01 | Sensor=1001 | Val=600 | TS=1789366095
⚠️ [AGENTE AUTÓNOMO] Alerta Sensor 1001 → Val=600
🐜 Respuesta LBH enviada
🐜 Trama 0x01 | Sensor=1001 | Val=400 | TS=1789366395
🐜 Respuesta LBH enviada
And the continuous heartbeat ACKs:
❄️ Feromona 0x02 ACK (socket preservado)
The UI on the phone simply shows a telemetry input and a green "Enviar Trama LBH" button.
When the channel is healthy, it displays:
«Canal Activo (Descongelado)»
The goal is deliberately simple: the user should not need to understand the underlying socket state, frame format, or reconnection mechanism.
The Android Problem and the Fix
Android browsers are aggressive.
When a PWA goes to the background, the WebSocket can be killed without producing a clean onclose event. The normal 3-second reconnect timer is therefore too slow for a good user experience in some situations.
I added a visibilitychange listener:
setupVisibilityListener() {
document.addEventListener("visibilitychange", () => {
if (document.hidden) return;
const isDead = !this.ws ||
this.ws.readyState === WebSocket.CLOSED ||
this.ws.readyState === WebSocket.CLOSING;
if (isDead) {
this._setState(
"OFFLINE",
"🟡 Verificando canal tras reanudar..."
);
}
this.connect(); // safe to call repeatedly
});
}
Now, as soon as the user returns to the app, the client checks the socket state and reconnects immediately if needed.
The important part is not trying to fight Android's lifecycle behavior. Instead, the client treats visibility changes as part of the runtime environment.
Update: Tested as a Real Installed App
After writing the piece above, I installed the PWA directly on the Android device using the browser's install prompt — not just a localhost browser tab.
I then tested it with split-screen:
- The PWA running in one pane
- The Edge Node's live log running in the other
With the screen locked or the app pushed to the background, the log kept showing:
❄️ Feromona 0x02 ACK (socket preservado)
continuously.
That confirmed an important behavior in this particular test environment: the socket survived backgrounding as an installed app, not only as an open browser tab.
This distinction matters when building edge agents for constrained mobile hardware. The phone isn't just being used as a browser; it is becoming part of the edge runtime.
A Known Rough Edge
There is still one small race condition on the reconnect path.
Switching visibility very quickly and repeatedly — for example, rapidly switching between apps — can occasionally produce a server log like:
cliente conectado
cliente desconectado
where the cliente conectado entry for the new socket can appear before the matching cliente desconectado entry from the previous socket.
The server handles both connections cleanly:
- No leaked connections
- No corrupted frames
- No protocol failure
However, the log ordering isn't strictly sequential in that edge case. The cause is a small gap in the current connect() guard. At the moment, it checks for OPEN and CONNECTING, but does not yet treat CLOSING as a state that should block a new connection attempt.
Tightening that guard is on the list. For example, the intended logic is conceptually:
if (
this.ws &&
(
this.ws.readyState === WebSocket.OPEN ||
this.ws.readyState === WebSocket.CONNECTING ||
this.ws.readyState === WebSocket.CLOSING
)
) {
return;
}
One Important Security Limitation
It is also worth being upfront about the current security boundary.
The WebSocket server currently has no authentication layer. That means any client on the same local network can send LBH frames to the endpoint.
For a local development or demonstration node, this is acceptable for the current testing stage. It is not appropriate to expose this WebSocket endpoint directly on a public network in its current form. Authentication and authorization are therefore part of the next hardening stage.
Why This Matters for Edge Computing
The interesting part of this experiment isn't simply keeping a WebSocket alive. The larger question is:
«Can constrained devices participate in a useful distributed computing architecture without behaving like thin clients that constantly depend on a remote cloud?»
The current architecture is intentionally lightweight:
┌──────────────────────┐
│ Android PWA │
│ │
│ Telemetry + LBH │
│ Heartbeat │
│ Reconnection │
└──────────┬───────────┘
│
│ WebSocket
│ Binary LBH
▼
┌──────────────────────┐
│ Edge Node │
│ │
│ LBH Parser │
│ Agent Logic │
│ Telemetry │
│ ACK / Pheromone │
└──────────────────────┘
The phone can therefore act as both:
- A user interface, and
- A constrained edge computing endpoint.
That opens an interesting path for low-cost deployments where dedicated server hardware isn't always available.
Current Focus
This is still early-stage infrastructure.
The goal is to turn these lightweight LBH agents into injectable services that universities and small/medium businesses (Pymes) can use without depending on heavy cloud stacks.
The entire development loop — coding, testing, sealing, and deploying — happens from an Android device running Termux. That constraint is intentional. If the development and deployment workflow can operate from a phone, the same architecture can potentially be adapted to other low-resource environments.
What's Next
The next engineering steps are:
- More formal agent decision layer
- Cleaner packaging of the PWA + Node
- Authentication for the Edge Node's WebSocket endpoint
- Fixing the
CLOSING-state race in the reconnect guard - Documentation and examples aimed at educational and SME use cases
The focus is not on making the architecture unnecessarily large. It is on making the smallest useful pieces reliable enough to become infrastructure.
Final Notes
This experiment started as a simple browser-based heartbeat mechanism. Moving it onto an actual Android device changed the nature of the test.
Background execution, application lifecycle, WebSocket state, reconnection, telemetry, and protocol framing all become part of the same engineering problem.
That's where edge computing gets interesting. The device is no longer just displaying a dashboard. It is participating in the system.
If you're working on edge systems, constrained devices, or protocol design and want to exchange notes, feel free to reach out.
By: Cristhiam Leonardo Hernández Quiñonez | Protocol Architect & Founder at HormigasAIS | hormigasais.com | GitHub
Top comments (0)