Skip to content

Mobile App

The Haptique mobile app (apps/mobile/) is an Expo/React Native application for controlling devices, managing rooms, running scenes, and interacting with the HOS server. It connects to the server via REST API for commands, SSE for live state updates, and optionally Socket.IO for designer features.


The app uses a root stack navigator with an unauthenticated entry flow and a bottom tab navigator for authenticated users.

Root Stack

ScreenComponentPurpose
DiscoveryDiscoveryScreenUser enters the server base URL (e.g., http://192.168.1.x:8080)
LoginLoginScreenUsername and password entry
Authenticating(loading screen)Awaits login response
MainAuthenticatedAppEntryBottom tab navigator (authenticated shell)

Bottom Tabs

Inside AuthenticatedAppEntry:

TabScreenPurpose
HomeHomeNavigator (nested stack)Rooms, device control
MediaMediaScreenMedia playback
ZonesZonesScreenZone management
ScenesScenesScreenScene execution
ProfileProfileScreenUser settings
CustomCustomTabScreenDesigner-configurable tab

Home Stack

HomeNavigator lives at src/features/navigation/HomeNavigator.tsx and manages the primary device interaction flow:

ScreenComponentPurpose
RoomsHomeRoomsHomeScreenRoom list entry point
RoomRemoteHomeRoomieRoomsHomeScreenRoomie rooms view
RoomsRoomsScreenRoom browser
RoomDashboardRoomDashboardScreenSingle room dashboard
DeviceControlResolverDeviceControlResolverScreenRoutes to device-type-specific control screen
LightControlLightControlScreenLight device control (brightness, color, power)
AvControlAvControlScreenAV device control (input, volume, transport)
MediaControlMediaControlScreenMedia device control
HvacControlHvacControlScreenHVAC device control (temperature, mode, fan)
GenericControlGenericCommandScreenGeneric fallback for unmapped device types

Key Data Contexts

Three React contexts carry the app's global state. They are initialized in App.tsx and consumed throughout the screen hierarchy.

ContextFileResponsibilities
HaptiqueContextsrc/contexts/HaptiqueContext.tsxController session (base URL), auth token, bootstrap data (devices, zones, spaces, programs, actionMaps), SSE connection, command dispatch functions
ThemeContextsrc/contexts/ThemeContext.tsxDark/light theme mode, color palette, typography tokens
RoomsDataContextsrc/features/rooms/RoomsDataContext.tsxRoom list, per-room device lists, favorite devices, room-level refresh

HaptiqueContext is the central data hub — nearly every screen reads from it to access devices, rooms, and dispatch commands. It owns the SSE connection to /app/os/state/stream and updates device state slices in real time as state events arrive.

ThemeContext is consumed by all visual components for consistent color and typography. It reads the system preference on first launch and persists the user's override in local storage.

RoomsDataContext supplements HaptiqueContext with room-scoped caching. Room screens subscribe to it to get pre-filtered device lists per room without triggering full re-renders of the global context.


Feature Directories

The app is organized into feature slices under src/features/:

FeaturePathPurpose
controlssrc/features/controls/Device control screens: light, av, media, hvac, generic
coresrc/features/core/osApi.ts — typed HTTP client for all server communication
designersrc/features/designer/Tab config, designer preview, custom screen
devicessrc/features/devices/Device type resolution, screen routing via DeviceControlResolver
mediasrc/features/media/Media playback, hardware volume bridge
navigationsrc/features/navigation/HomeNavigator, gesture hooks, BottomBarVisibility
roomssrc/features/rooms/RoomsDataContext, adapters, room screens
scenessrc/features/scenes/Scene execution
sharedsrc/features/shared/Theme helpers, error boundary, common components

How the App Connects to the Server

The connection lifecycle runs in six steps. Steps 1-5 are the standard authenticated flow. Step 6 is designer-only.

User                  App (HaptiqueContext)          HOS Server
 |                           |                            |
 |  Enter base URL           |                            |
 +-------------------------> |                            |
 |  (DiscoveryScreen)        |                            |
 |                           |                            |
 |  Submit credentials       |                            |
 +-------------------------> |                            |
 |  (LoginScreen)            | POST /app/os/auth/login    |
 |                           +--------------------------> |
 |                           | <-- bearer token ----------+
 |                           | (stored in react-native-   |
 |                           |  keychain)                 |
 |                           |                            |
 |                           | GET /bootstrap             |
 |                           | GET /media-services/status | (parallel)
 |                           | GET /programs              |
 |                           | GET /spaces                |
 |                           | GET /action-maps           |
 |                           +--------------------------> |
 |                           | <-- bootstrap data --------+
 |                           |                            |
 |                           | GET /app/os/state/stream   |
 |  (authenticated shell)    |   ?accessToken=<token>     |
 |  <----------------------- +--------------------------> |
 |                           |                            |
 |                           | <-- SSE: ready -------------+
 |                           | <-- SSE: snapshot ---------+
 |                           | <-- SSE: state (ongoing) --+
 |                           | <-- SSE: ping (keepalive) -+

Step-by-step:

  1. DiscoveryDiscoveryScreen captures the server base URL and stores it in context (e.g., http://192.168.1.x:8080). No auth required at this step.

  2. LoginPOST /app/os/auth/login with username and password. On success, the bearer token is stored securely in react-native-keychain. The token is attached to all subsequent requests via the Authorization: Bearer <token> header.

  3. Bootstrap (parallel)HaptiqueContext fires five parallel requests:

    • /bootstrap — core device registry, zones, spaces
    • /media-services/status — streaming service availability
    • /programs — automation programs
    • /spaces — space configurations
    • /action-maps — command mappings
  4. Live updates (SSE) — A persistent SSE connection opens on GET /app/os/state/stream?accessToken=<token> using react-native-sse. Event types:

    • ready — server confirms stream is open
    • snapshot — full device state dump on connect
    • state — incremental state change for a single device
    • event — protocol event from a driver
    • ping — keepalive
  5. Commands — Dispatched from control screens via HaptiqueContext helper functions:

    • POST /devices/:id/command — device action dispatch
    • POST /programs/:id/run — trigger automation program
    • POST /action-maps/:id/trigger — trigger action map
  6. Designer only (optional)Socket.IO connection at /socket/designer for live design push (used only when the designer feature is active).


See Also

  • Server Internals — how the server processes requests dispatched by the app
  • API Reference — full endpoint inventory, SSE event shapes, Socket.IO events
  • Dev Setup — running the mobile app locally against a live HOS server