Skip to content

Agora-Build/HapticHatch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HapticHatch

ESP32-S3 firmware demonstrating high-frequency, high-resolution data streaming between two devices over Agora Signaling at 100 Hz — pushing the limits of low-latency peer-to-peer messaging on embedded hardware.

flowchart LR
    subgraph A["Device A"]
        A_sensor["sensor"]
        A_haptic["haptic"]
    end

    subgraph Cloud["Agora Signaling"]
    end

    subgraph B["Device B"]
        B_sensor["sensor"]
        B_haptic["haptic"]
    end

    A_sensor -->|"TX 100 Hz"| Cloud -->|"RX"| B_haptic
    B_sensor -->|"TX 100 Hz"| Cloud -->|"RX"| A_haptic
Loading

Hardware

Item Details
SoC ESP32-S3 with 8 MB embedded OPI PSRAM
Flash 8 MB

Prerequisites

Repository layout

HapticHatch/
├── components/
│   ├── aosl/            # ESP-IDF wrapper for the AOSL source tree
│   ├── haptic/          # Haptic driver (stub: logs intensity)
│   ├── rtsa_transport/  # Agora RTSA SDK transport + link glue
│   └── sensor/          # Sensor driver (stub: sine wave at 100 Hz)
├── main/
│   ├── main.c           # app_main: wires sensor → haptic, starts Signaling demo
│   ├── signaling_demo.c       # WiFi init, Signaling login, 100 Hz TX task, RX callback
│   └── signaling_demo.h
├── partitions.csv        # 3 MB factory partition (required by SDK size)
└── sdkconfig.defaults    # Target, flash, PSRAM, and credential defaults

Configuration

Copy sdkconfig.defaults and fill in your credentials before building.

# sdkconfig.defaults

CONFIG_AGORA_APP_ID="<your-agora-app-id>"

# Both tokens live in the build so you can flash both boards without
# touching sdkconfig in between — only change DEVICE_ID between flashes.
CONFIG_AGORA_SIGNALING_UID_A="device_a"
CONFIG_AGORA_SIGNALING_UID_B="device_b"
CONFIG_AGORA_SIGNALING_TOKEN_A="<signaling-token-for-device_a>"
CONFIG_AGORA_SIGNALING_TOKEN_B="<signaling-token-for-device_b>"
CONFIG_AGORA_SIGNALING_DEVICE_ID="A"   # "A" for first board, "B" for second

CONFIG_DEMO_WIFI_SSID="<your-ssid>"
CONFIG_DEMO_WIFI_PASSWORD="<your-password>"

Generate both Signaling tokens with atem

Signaling tokens expire after 3600 s, but you only need to generate them once per session — then you can flash both boards without regenerating.

atem token rtm create --rtm-user-id device_a   # → TOKEN_A
atem token rtm create --rtm-user-id device_b   # → TOKEN_B

Paste both tokens into sdkconfig.defaults.

How tokens and login work

Each UID (device_a, device_b) needs its own token. The token is signed by Agora's backend using your App ID's secret and binds to {App ID, UID, expiry}. The device presents the token to Agora at boot time; only after login succeeds can it send or receive messages.

sequenceDiagram
    autonumber
    actor Dev as Developer
    participant Atem as Atem CLI
    participant Agora as Agora Cloud
    participant Device as ESP32-S3

    Note over Dev,Atem: 1. Create a Signaling token — once per UID
    Dev->>Atem: atem token rtm create --rtm-user-id device_a
    Note over Atem: Sign token locally with<br/>App ID + App Certificate + UID<br/>(TTL 3600 s)
    Atem-->>Dev: Token string

    Note over Dev,Device: 2. Embed token in firmware and flash
    Dev->>Device: sdkconfig: APP_ID, UID_A/B, TOKEN_A/B, DEVICE_ID → idf.py flash

    Note over Device,Agora: 3. Boot-time login (signaling_demo.c)
    Device->>Device: WiFi STA connect
    Device->>Agora: agora_rtc_init(App ID)
    Device->>Agora: agora_rtc_login_rtm(UID, token)
    Agora->>Agora: Verify App ID + UID + signature + expiry
    Agora-->>Device: on_rtm_event(LOGIN, OK)

    Note over Device,Agora: 4. Messaging is now allowed
    loop every 10 ms (100 Hz)
        Device->>Agora: agora_rtc_send_rtm_data(peer_uid, payload)
        Agora-->>Device: on_rtm_data(from_uid, payload)
    end
Loading

Common login failures:

  • Token expired (TTL 3600 s) → regenerate with atem
  • DEVICE_ID doesn't match a filled-in token (e.g. DEVICE_ID="B" but TOKEN_B is empty)
  • App ID mismatch between the token and CONFIG_AGORA_APP_ID

Building

# 1. Activate ESP-IDF
source /path/to/esp-idf/export.sh

# 2. Remove stale config (required whenever sdkconfig.defaults changes)
rm -f sdkconfig

# 3. Build
idf.py build

Running two devices

With both tokens already in sdkconfig.defaults, the only thing that changes between the two flashes is CONFIG_AGORA_SIGNALING_DEVICE_ID.

Flash board 1 as UID A (/dev/ttyUSB0)

# sdkconfig.defaults
CONFIG_AGORA_SIGNALING_DEVICE_ID="A"
rm sdkconfig && idf.py -p /dev/ttyUSB0 build flash

Flash board 2 as UID B (/dev/ttyUSB1)

# sdkconfig.defaults
CONFIG_AGORA_SIGNALING_DEVICE_ID="B"
rm sdkconfig && idf.py -p /dev/ttyUSB1 build flash

Monitor both simultaneously

# terminal 1
idf.py -p /dev/ttyUSB0 monitor

# terminal 2
idf.py -p /dev/ttyUSB1 monitor

Expected output

device_a (transmitting to device_b, receiving from device_b):

I (...) signaling: Device ID=A → uid=device_a, peer=device_b
I (...) signaling: Agora SDK 1.10.0 initialized
I (...) signaling: Signaling login success uid=device_a
I (...) signaling: Starting 100 Hz Signaling sender
I (...) signaling: TX seq=1    ts=5080 ms force=0.57
I (...) signaling: TX seq=2    ts=5090 ms force=0.59
...
I (...) signaling: RX from=device_b   seq=1      ts=5150 ms force=0.420 type=haptic

Messages flow in both directions at 100 Hz.

Troubleshooting

Symptom Cause Fix
aosl_malloc N byte failed on boot PSRAM not enabled Verify CONFIG_SPIRAM=y in sdkconfig.defaults, do rm sdkconfig before rebuild
Signaling login failed err=101 with GetAddrErr code=2010005 Token expired, or the App ID's project doesn't have Signaling enabled, or a typo in the App ID Confirm the active atem project has Signaling enabled (atem project show), regenerate tokens, rm sdkconfig, rebuild
Invalid CONFIG_AGORA_SIGNALING_DEVICE_ID="..." followed by abort DEVICE_ID is not "A" or "B" Set CONFIG_AGORA_SIGNALING_DEVICE_ID="A" or "B" in sdkconfig.defaults
TX result state=2 warnings Peer not yet online Normal until both devices are logged in
idf.py: command not found IDF env not sourced source /path/to/esp-idf/export.sh
Build fails: AOSL_LOG_ERR undeclared Outdated AOSL tree (pre-fix) Update AOSL: cd ../aosl && git pull (fix is in AgoraIO-Community/aosl#3)

About

ESP32-S3 firmware demonstrating high-frequency, high-resolution data streaming between two devices over Agora Signaling at 100 Hz

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors