Skip to content

Driver Architecture

System Overview

HOS (Haptique OS) is a smart home operating system that runs as a server on the local network — either as an Electron desktop app or in headless Docker mode. The server exposes a REST/WebSocket API for mobile clients and a separate WebSocket endpoint exclusively for drivers.

Drivers are independent processes that connect to HOS over a raw WebSocket on the driver endpoint (ws://host:DRIVER_WS_PORT/driver). A driver's job is simple: announce the devices it controls, keep HOS informed of their state, and execute commands when asked. The driver process never touches the main HOS server codebase — it talks to HOS purely through the WebSocket protocol.

Mobile apps (iOS, Android, web) connect to HOS via REST API for one-time operations and receive real-time state updates via Socket.IO. When a driver pushes a state change, HOS immediately fans it out to all connected clients. This architecture means driver authors write straightforward event-driven code without any knowledge of client protocols or server internals.


Command Flow

When a user taps a device control in the mobile app, the command travels through HOS and arrives at the driver:

  Mobile App              HOS Server                        Driver Process
  ----------    --------------------------------    --------------------------

  [Tap command]
       |
       v
  REST API  -----> POST /app/os/devices/:id/command
                         |
                         v
                   ActionDispatcher
                   (routes to correct driver session)
                         |
                         v
                   Driver WebSocket  --------> [onAction() fires]
                                                     |
                                                     v
                                              [Execute on device]
                                                     |
                                                     v
                   <------ sendEvent('ACTION_RESULT')
                   <------ sendEvent('STATE_UPDATE')
                         |
                         v
                   DeviceStateManager
                   (stores updated state)
                         |
                         v
                   Socket.IO / SSE  ---------> [UI updates live]
                                                     |
                                                     v
                                              [Mobile App refreshes]

Step by step:

  1. User taps a control in the mobile app (e.g. "turn on the living room light")
  2. Mobile sends POST /app/os/devices/:id/command to the HOS REST API
  3. ActionDispatcher looks up which driver session owns that device and forwards the command
  4. The driver receives an ACTION message and the onAction() callback fires
  5. The driver executes the command on the physical device (network call, serial, IR, etc.)
  6. The driver sends ACTION_RESULT with a success flag and optional requestId correlation
  7. The driver sends STATE_UPDATE with the new device state (power: true, brightness: 100, etc.)
  8. HOS stores the updated state in DeviceStateManager and broadcasts it to all connected mobile clients via Socket.IO

The driver does not need to know anything about the mobile clients — it just emits state and the server handles distribution.


Driver Connection Model

Every driver connects to HOS using the same three-step handshake:

  Driver Process                         HOS Server
  -----------------------    -----------------------------------

  HosDriver.connect()
       |
       | ws://host:DRIVER_WS_PORT/driver
       |------------------------------------------------->
                                          DriverWebSocketServer
                                          accepts connection
       <-------------------------------------------------|
         { ok: true, event: 'CONNECTED' }

  driver.register()  (automatic when autoRegister: true)
       |
       |--- { method: 'driver.register',
       |       params: {
       |           driverKey: 'MY_DRIVER',
       |           instanceId: 'my-driver-001',
       |           protocolVersion: 1
       |       }
       |   } ------------------------------------------>
                                          Server validates fields,
                                          creates driver session
       <-------------------------------------------------|
         { ok: true, event: 'REGISTERED',
           driverKey: 'MY_DRIVER',
           instanceId: 'my-driver-001' }

  driver.on('registered', ...) fires
  — safe to send events now

Key points about the connection model:

  • Drivers connect to the /driver WebSocket path. The port is set by DRIVER_WS_PORT on the server (default: 8080).
  • The server sends a CONNECTED acknowledgement immediately on connection — before registration.
  • The driver must send driver.register with driverKey, instanceId, and protocolVersion: 1. The SDK sends this automatically when autoRegister: true (the default).
  • After receiving the REGISTERED ack, the driver can safely call sendEvent() to announce devices and push state.
  • If the connection drops, HosDriver reconnects automatically with exponential backoff (default: 1 second base, doubles each attempt, caps at 30 seconds) with ±10% jitter to avoid thundering-herd reconnect storms.
  • Close code 4001 means the server replaced this session with a newer connection that has the same driverKey + instanceId. HosDriver does not reconnect on 4001.

See Getting Started for a walkthrough of the HosDriver API and the exact constructor options.


Key Components

HosDriver (sdk/lib/) The TypeScript client library driver authors import. Wraps the raw ws WebSocket with typed message builders, automatic reconnect logic, and an EventEmitter interface. Driver authors interact exclusively with this class — they never write raw WebSocket code.

DriverWebSocketServer (server) Accepts driver connections on the /driver path. Validates every inbound message against the protocol schema, routes events to internal handlers, and sends ACTION commands down to the correct driver session when the user issues a command.

ActionDispatcher (server) Routes commands from the REST API to the driver that owns the target device. Looks up the device-to-driver mapping, serializes the command, and delivers it over the driver's WebSocket session.

DeviceStateManager (server) Tracks live device state in memory, keyed by device ID. When a driver sends STATE_UPDATE, DeviceStateManager stores the new state and triggers the Socket.IO broadcast to mobile clients.

ProtocolHub (server) Central event bus that connects DriverWebSocketServer, ActionDispatcher, and DeviceStateManager. Driver events flow in through ProtocolHub; commands flow out through it. Driver authors do not interact with ProtocolHub directly — it is an internal server component.


What's Next