Other frameworks (ESP-IDF, etc.)
The library is two layers, so you can drive Hapbeat from non-Arduino environments without writing a whole separate SDK.
HapbeatProtocol.{h,cpp}— framework-agnostic C++ (includes onlystdint.h/stddef.h/string.h, no dynamic allocation). It just builds the wire bytes.Hapbeat.{h,cpp}— the transport layer that uses Arduino’sWiFiUDP.
Option A: pull in Arduino as a component (quickest)
Section titled “Option A: pull in Arduino as a component (quickest)”ESP-IDF can include arduino-esp32 as a component. With PlatformIO, name both
frameworks in platformio.ini:
[env:esp32-idf]platform = espressif32framework = arduino, espidfboard = esp32devlib_deps = hapbeat/arduino@^0.1.0Arduino APIs (WiFiUDP, etc.) are then available inside the IDF project and the
library works unchanged — no porting needed.
Option B: port to a native transport (clean)
Section titled “Option B: port to a native transport (clean)”Vendor HapbeatProtocol.{h,cpp} (MIT, two files) into your project and write
only the send path with your platform’s UDP socket. The wire format is
byte-compatible with contracts, so the device needs no changes.
Minimal example: ESP-IDF (lwIP sockets)
Section titled “Minimal example: ESP-IDF (lwIP sockets)”Bundle src/HapbeatProtocol.{h,cpp} and call it from C++ (.cpp). Wi-Fi is
assumed already connected via esp_wifi (e.g. example_connect()).
#include "HapbeatProtocol.h" // vendor the 2 files (framework-agnostic, MIT)#include "lwip/sockets.h"#include "lwip/inet.h"
static int s_sock = -1;static uint16_t s_seq = 0;
void hapbeat_begin(void) { s_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); int on = 1; setsockopt(s_sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // allow broadcast}
// Fire a kit event (command mode). Call after Wi-Fi is connected.void hapbeat_play(const char *event_id, float gain) { uint8_t buf[256]; size_t n = hapbeat::buildPlay(buf, sizeof(buf), ++s_seq, event_id, /*target=*/"", /*targetTimeUs=*/0, gain);
struct sockaddr_in dst = {}; dst.sin_family = AF_INET; dst.sin_port = htons(7700); // Hapbeat UDP port dst.sin_addr.s_addr = htonl(INADDR_BROADCAST); // 255.255.255.255 (all devices)
sendto(s_sock, buf, n, 0, (struct sockaddr *)&dst, sizeof(dst));}hapbeat_begin();hapbeat_play("sample-kit.sine_100hz", 0.6f); // on some inputSynthesized sine is the same idea: build buildStreamBegin → buildStreamData
(continuous, running offset) → buildStreamEnd, send them on the same socket, and
keep ~160 ms of lead-buffer pacing (see
Synthesized sine streaming and the Arduino Hapbeat.cpp).
Mbed / Zephyr / bare-metal
Section titled “Mbed / Zephyr / bare-metal”Same shape: build the packet with HapbeatProtocol, send to
<broadcast or device IP>:7700 with your platform’s UDP API.
- Mbed:
UDPSocket sock; sock.open(net);thensock.sendto(SocketAddress("255.255.255.255", 7700), buf, n);(callopen()with your network interface before sending; broadcast may needset_broadcasting(), while unicast to a device IP always works). - Zephyr: BSD sockets (
zsock_socket/zsock_sendto). - Bare-metal: the UDP send of whatever TCP/IP stack you use (e.g. raw lwIP).
Key points
Section titled “Key points”- Only the transport (send path) is ported. The protocol / wire format is
shared via
HapbeatProtocol, and the device is unchanged. - Send to broadcast
255.255.255.255:7700(all devices), or to a device IP learned viaPING→PONG(buildPing) for low-loss unicast (one device). - This is not a separate SDK — it is a transport port that shares the same
protocol core (a future
hapbeat/espidf, etc. would use the very same core).