Reviewing AI-Assisted BLE Services and Clients: Bytes, Notifications and Reconnection

An AI-generated BLE service and its matching client can agree with each other and still be wrong. Both sides may share the same byte-order assumption, skip subscription state, or appear to recover after reconnecting while displaying an old measurement. Review the wire contract and connection lifecycle independently before treating a successful demonstration as compatibility evidence.

This is a pedagogical design review of an invented telemetry service. The generated-style flaws, corrected codec and state model were constructed for explanation. No AI tool execution, phone interoperability test, RF measurement or delivered firmware result is claimed. None of the snippets is a complete production BLE implementation.

1. Define the input contract for both endpoints

Provide the peripheral platform, BLE stack version, client operating systems and supported versions, service and characteristic UUIDs, and intended characteristic properties. Define who initiates connections, required security, pairing and bonding policy, connection limits and behavior when the device restarts. Identify which APIs belong to which selected SDK; plausible API names are not documentation.

For this example, choose one telemetry characteristic carrying a fixed eight-byte frame. Byte 0 is protocol version 1; byte 1 contains reserved flags that must be zero; bytes 2–3 are an unsigned sequence; bytes 4–5 are signed temperature in hundredths of a degree Celsius; bytes 6–7 are unsigned battery millivolts. All multi-byte integers use little-endian order.

Specify that sequence numbers wrap modulo 65536 and are interpreted within one connection session. They help detect gaps but do not uniquely identify samples across device resets. Specify acceptable sample age and the policy for dropped data before deciding whether notifications are sufficient. A command that changes an actuator needs its own authorization and acknowledgment contract.

Eight-byte BLE telemetry wire format above a connection state chain ending in a verified ready session.
Protocol bytes and session readiness require separate acceptance evidence. All values are illustrative.

2. Review the draft’s hidden agreements

/* Deliberately flawed teaching sketch. */
struct Sample {
    uint8_t version;
    uint16_t sequence;
    float temperature;
};
notify(connection, &sample, sizeof sample);

/* Flawed client state rule: connection implies readiness. */
on_connected() { ready = true; }
on_disconnected() { connect_again_immediately(); }

A native C structure is not a wire format. Padding, alignment and byte order can differ, and the floating-point representation and units have not been defined. Even when one compiler happens to produce a suitable layout, the contract remains implicit. Packing the structure removes some padding but does not settle byte order, floating-point encoding, valid ranges or version handling.

The notify call also hides state and ownership questions. Is the client subscribed? Is the connection still valid? Does the stack copy the payload before returning, or must the buffer remain alive? What happens when transmission resources are exhausted? Review the selected stack’s actual API contract rather than inventing a universal return-value rule.

The client’s readiness flag is premature. A link can exist before service discovery, security or notification setup has completed. Immediate unbounded reconnects can create battery drain and an unusable user experience, while old asynchronous callbacks can accidentally update the new session. A connected icon is weak evidence that useful telemetry is flowing.

3. Review a byte-exact codec before BLE integration

# Executable-style protocol illustration, not firmware integration.
import struct

def encode_sample(sequence, temperature_centi_c, battery_mv):
    if not 0 <= sequence <= 65535:
        raise ValueError("sequence")
    if not -32768 <= temperature_centi_c <= 32767:
        raise ValueError("temperature")
    if not 0 <= battery_mv <= 65535:
        raise ValueError("battery")
    return struct.pack("<BBHhH", 1, 0, sequence,
                       temperature_centi_c, battery_mv)

def decode_sample(payload):
    if len(payload) != 8:
        raise ValueError("length")
    version, flags, seq, temp, mv = struct.unpack("<BBHhH", payload)
    if version != 1 or flags != 0:
        raise ValueError("unsupported format")
    return {"sequence": seq, "temperature_centi_c": temp,
            "battery_mv": mv}

# Proposed golden vector:
# sequence=0x1234, temperature=-1250, battery=3300
# bytes: 01 00 34 12 1e fb e4 0c

This codec makes layout, integer signedness and scale explicit. The wire value -1250 means -12.50 °C. The sequence and battery fields are unsigned; neither should be decoded through a signed 16-bit path. The proposed vector is a calculated example, not captured radio traffic. Derive matching vectors independently for the embedded implementation and each client language.

The application should validate input types and domain limits before encoding, then handle decoder errors without crashing its notification callback. The illustration’s representable integer range is not a promise about sensor accuracy or valid product temperatures. Decide how sensor faults are represented; do not reuse an ordinary temperature value as an undocumented error marker.

Reject an unsupported protocol version visibly and retain raw diagnostic bytes only under the product’s data-handling policy. Do not silently decode a future layout as version 1. If optional fields are later required, specify a new framing rule and compatibility behavior instead of appending bytes that older clients may misinterpret.

4. Make subscription and delivery semantics explicit

The Bluetooth SIG’s GATT specification defines client characteristic configuration and distinguishes notifications from indications. A notification has no ATT-level receipt acknowledgment. An indication’s confirmation does not prove that the receiving application stored or acted on the data. Product-level reliable delivery may require its own acknowledgment, sequence and retry design.

For an ordinary Handle Value Notification, the ATT specification limits the value to ATT_MTU minus three bytes. The illustrative eight-byte frame fits the twenty-byte value budget associated with the default 23-byte ATT MTU. Do not assume that requesting a larger MTU makes it available, or that link-layer data length directly equals application payload capacity.

Track effective subscription state for each peer. Bonding can affect persistence of client configuration, so review the stack and client behavior rather than assuming every reconnect starts identically. Bound the outgoing queue and decide whether backpressure drops old telemetry, rejects new data or pauses sampling. Report dropped samples through observable counters or protocol behavior, not silent optimistic success.

5. Treat reconnection as a new session

DISCONNECTED -> CONNECTING -> DISCOVERING
             -> SECURING_IF_REQUIRED -> SUBSCRIBING -> READY

On every new connection:
  allocate a new session generation; clear old ready state
  resolve services/characteristics using the current database
  establish required security and effective subscription state
  reject stale callbacks from older session generations
On disconnect:
  invalidate generation; cancel pending work; mark data stale
  retry with bounded backoff while user/session policy permits

This is a proposed state model. The exact order of security and discovery may depend on the service and platform. Readiness must mean all required gates are satisfied, including a verified usable subscription and a fresh-data policy. Include deadlines for connection, discovery and setup; a state that waits forever is not recovery.

A session generation token helps prevent a late disconnect or notification callback from an earlier connection changing current state. Resolve characteristics from the current service database rather than assuming cached numeric handles remain valid after a firmware update. Integrate the platform’s service-change and cache behavior, and test upgrade and downgrade paths explicitly.

Use bounded backoff that respects user cancellation, foreground/background policy and battery requirements. Decide what the interface shows while disconnected: last value with a timestamp and stale marker, or no current value. Never present a replayed or cached measurement as a new sample merely because a connection reopened.

6. Build an acceptance matrix that can reveal shared mistakes

  • Codec tests: use independently derived golden vectors for zero, negative temperatures, signed boundaries, sequence rollover, malformed lengths and unknown versions. Require exact bytes and defined error handling on every implementation.
  • Notification tests: test before subscription, after subscription, unsubscribe, queue exhaustion and the smallest supported MTU. Record application sequence gaps and stack errors without treating attempted sends as deliveries.
  • Reconnect tests: disconnect at each setup state, reboot the peripheral, restart the client and cancel retries. Acceptance requires bounded recovery, no duplicate active session and no stale callback restoring readiness.
  • Compatibility tests: enumerate actual phone models, operating-system versions, firmware builds, bonded/unbonded states and background conditions. Test each required combination rather than inferring interoperability from one laptop client.
  • Security tests: verify the required access rules before and after pairing, with the wrong peer and after bond removal. A successful transport connection does not establish authorization to read private data or issue commands.

7. Preserve evidence and scope the claim

Keep the protocol version, codec tests, stack configuration, application logs, packet captures where appropriate, device identities and exact pass/fail criteria. Record tested combinations and untested combinations separately. Use AI review to challenge assumptions and suggest cases, then require independent bytes and real endpoint behavior for acceptance.

Obeita’s device-connectivity service is a relevant route for integration requirements. The WS63 wireless-module project offers related wireless integration context. It does not establish that this invented BLE service, client matrix or AI workflow was used or validated in that project.

Similar Posts