Build

Device firmware

Device SDK · nexting_device.h · C99

Use the portable C99 core to decode bounded BLE chunks, drive approval UI and retries, and apply Agent status without bringing an allocator or operating system into your firmware.

1. Design contract

The SDK is portable C99, performs no heap allocation, and owns no transport. Every buffer is caller-owned and fixed-capacity. Feed radio chunks in, render accepted state, and send encoded bytes through your BLE stack. A complete logical frame is at most 4096 bytes including its LF; an encode buffer therefore needs 4097 bytes to also hold the C NUL.

NameTypeDescription
NEXTING_DEVICE_REQUEST_ID_CAPACITYrequired
65 bytes64 ASCII ID bytes plus C NUL.
NEXTING_DEVICE_SUMMARY_CAPACITYrequired
241 bytes240 UTF-8 summary bytes plus C NUL.
NEXTING_DEVICE_DEFAULT_MAX_MESSAGE_BYTESrequired
4096 bytesCompact JSON and its terminating LF.
encode output bufferrequired
4097 bytesMaximum frame plus the encoder's C NUL.
NEXTING_DEVICE_MAX_TTL_MSrequired
300000 msLargest accepted relative approval lifetime.
NEXTING_DEVICE_ANSWER_RETRY_MSrequired
1000 msRetry interval while resolution is missing.
NEXTING_DEVICE_STATUS_LABEL_CAPACITYrequired
65 bytes64 UTF-8 label bytes plus C NUL.

2. Messages

nexting_device_message_t is the single decoded value. Its tagged fields cover all five wire messages; status carries at most eight unique agent slots.

NEXTING_DEVICE_MESSAGE_PRESENT
NEXTING_DEVICE_MESSAGE_ANSWER
NEXTING_DEVICE_MESSAGE_RESOLVED
NEXTING_DEVICE_MESSAGE_ERROR
NEXTING_DEVICE_MESSAGE_STATUS

3. Codec

Decode and encode fail closed. Invalid UTF-8, duplicate known fields, unknown enums, non-canonical integers, out-of-range values, and embedded NUL never reach state or rendering code.

nexting_device.hc
nexting_device_result_t nexting_device_decode(
    const char *wire,
    size_t wire_length,
    nexting_device_message_t *output);

nexting_device_result_t nexting_device_encode(
    const nexting_device_message_t *message,
    char *output,
    size_t output_capacity,
    size_t *output_length);

The same nexting_device_message_t carries navigation/1, keys/1, rotary/1, voice/1, text/1, usage/1, and config/1 through fixed-capacity interaction payloads. Device Info exposes one support boolean per profile. Sequence-bearing physical input is monotonic per source and resets with the connection.

generic key eventc
nexting_device_message_t event = {0};
event.type = NEXTING_DEVICE_MESSAGE_KEY_EVENT;
event.interaction.slot = 3;
event.interaction.gesture = NEXTING_DEVICE_GESTURE_PRESS;
event.interaction.sequence = ++key_sequence;
nexting_device_encode(&event, wire, sizeof wire, &wire_length);

4. Newline stream framing

ATT writes can split or combine frames. The stream parser appends bytes to your 4096-byte storage, emits one delimiter-stripped JSON message at each LF, never executes a partial message, and discards oversize input until the next LF. Reset it on disconnect.

caller-owned streamc
uint8_t storage[NEXTING_DEVICE_DEFAULT_MAX_MESSAGE_BYTES];
nexting_device_stream_t stream;

nexting_device_stream_init(&stream, storage, sizeof storage);
nexting_device_stream_push(&stream, chunk, chunk_length, on_message, context);
nexting_device_stream_reset(&stream);

5. Approval state machine

The phases are NEXTING_DEVICE_PHASE_IDLE,NEXTING_DEVICE_PHASE_PENDING, andNEXTING_DEVICE_PHASE_WAITING_RESOLUTION. Present replaces the current request. A local choice moves Pending to Waiting Resolution and arms an identical-answer retry every 1000 ms. A matchingnexting_device_state_on_resolved clears the request from Pending or Waiting Resolution; a mismatched ID changes nothing. In a periodic loop, call nexting_device_state_tick beforenexting_device_state_retry_answer: retry_answer also clears an expired Waiting Resolution state but returns false, so calling it first would hide the expiry transition from the UI. A press can race the deadline too: nexting_device_state_choose checks TTL, clears an expired SDK state to Idle, and returns an error. On a choose error, clear visible UI only when that call started in Pending and left the phase Idle; if the phase is still Pending—or the press was already stale in Idle—preserve the current UI so another valid press can retry.

NameTypeDescription
nexting_device_state_on_present
Idle → PendingCopies the request and sets a monotonic deadline.
nexting_device_state_choose
Pending → Waiting / IdleBuilds the first answer, or clears to Idle if TTL already expired.
nexting_device_state_retry_answer
WaitingBuilds the same answer when the retry deadline arrives.
nexting_device_state_on_resolved
Pending / Waiting → IdleClears only a matching request ID.
nexting_device_state_tick
Any active → IdleExpires against elapsed monotonic time.

6. Status state

A valid status message is a full replacement: absent slots clear and an empty array clears all slots. Validation happens before mutation, so a malformed frame preserves the previous status atomically. Disconnect also clears every volatile slot. Only apply status when the device advertises the status/1 profile and a positivestatusSlots capability.

atomic status applyc
nexting_device_status_state_t status;
nexting_device_status_init(&status);

if (nexting_device_status_on_message(&status, message) == NEXTING_DEVICE_OK) {
  render_status(&status);
}

nexting_device_status_disconnect(&status);

7. Complete integration

This translation unit enforces a strict serialized app event loop. BLE, GPIO, timer, and disconnect callbacks only enqueue through the platform-owned queue; they never enter SDK state directly. The queue must copy each event and BLE payload before returning. Only one event-loop context calls app_event_loop_init andapp_event_loop_dispatch, and it must never reenter either function. The platform-neutral queue hook deliberately makes no ISR-safety claim: adapt callback context and wake-up mechanics to your RTOS or bare-metal target. Supply monotonic time, BLE Notify, UI, and queue hooks; the exact public APIs compile as C99 with the SDK source.

device_integration.cc
#include "nexting_device.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

typedef enum {
  APP_EVENT_BLE_BYTES,

Configure, build, and run all C SDK tests directly from the exported source tree with these exact commands.

C SDK verificationsh
cmake -S /path/to/nexting/devices/sdk/c -B build/c-sdk -DCMAKE_BUILD_TYPE=Debug
cmake --build build/c-sdk --parallel
ctest --test-dir build/c-sdk --output-on-failure
The current Zephyr reference uses this C core but advertises onlyapproval/1. Adding status rendering requires a real slot mapping, a statusSlots declaration, and board verification.