DEV Community

Cristhiam Leonardo Hernández Quiñonez
Cristhiam Leonardo Hernández Quiñonez

Posted on Originally published at blog.hormigasais.com

Building Resilient Edge Agents with LBH: A PWA Heartbeat System Running on Android

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 HIBERNATING state 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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And the continuous heartbeat ACKs:

❄️  Feromona 0x02 ACK (socket preservado)
Enter fullscreen mode Exit fullscreen mode

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
  });
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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     │
└──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The phone can therefore act as both:

  1. A user interface, and
  2. 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)