Skip to content

Add BLE primitives - #416

Draft
MaartenS11 wants to merge 2 commits into
mainfrom
feat/zephyr-ble
Draft

MaartenS11 wants to merge 2 commits into
mainfrom
feat/zephyr-ble

Conversation

@MaartenS11

Copy link
Copy Markdown
Member

Mostly glue code that exposes parts of the zephyr bluetooth API to wasm using primitives. I was able to do most of that using claude with some manual intervention. I have currently only tested this on the ESP32 C3 super mini.

Below is an example of a robot that is controlled using BLE, the user can send the messages, forward, left, right to make the robot move. I have tried this by sending commands from my mac and also from my phone using the nRFConnect app.

@external("env", "chip_digital_write") declare function chip_digital_write(pin: i32, value: i32): void;
@external("env", "chip_pin_mode") declare function chip_pin_mode(pin: i32, mode: i32): void;
@external("env", "chip_delay") declare function chip_delay(time: i32): void;

@external("env", "ble_enable") declare function ble_enable(nameAddr: ArrayBuffer, nameLen: i32): i32;
@external("env", "ble_service_create") declare function ble_service_create(uuidAddr: ArrayBuffer, uuidLen: i32): i32;
@external("env", "ble_characteristic_create") declare function ble_characteristic_create(service: i32, uuidAddr: ArrayBuffer, uuidLen: i32, properties: i32, permissions: i32): i32;
@external("env", "ble_characteristic_read") declare function ble_characteristic_read(index: i32, bufAddr: ArrayBuffer, len: i32): i32;
@external("env", "ble_characteristic_available") declare function ble_characteristic_available(index: i32): i32;
@external("env", "ble_advertise_start") declare function ble_advertise_start(): i32;
@external("env", "ble_connected") declare function ble_connected(): bool;

enum PinMode {
    Input = 0,
    Output = 2
}

// Bit values from Zephyr's bt_gatt_chrc/bt_gatt_perm enums.
const CHRC_WRITE: i32 = 0x08;
const CHRC_WRITE_NO_RESP: i32 = 0x04;
const PERM_WRITE: i32 = 0x02;

function uuid(b0: i32, b1: i32, b2: i32, b3: i32, b4: i32, b5: i32, b6: i32, b7: i32,
             b8: i32, b9: i32, b10: i32, b11: i32, b12: i32, b13: i32, b14: i32, b15: i32): ArrayBuffer {
    const buf = new Uint8Array(16);
    buf[0] = b0; buf[1] = b1; buf[2] = b2; buf[3] = b3;
    buf[4] = b4; buf[5] = b5; buf[6] = b6; buf[7] = b7;
    buf[8] = b8; buf[9] = b9; buf[10] = b10; buf[11] = b11;
    buf[12] = b12; buf[13] = b13; buf[14] = b14; buf[15] = b15;
    return buf.buffer;
}

// Nordic UART Service UUIDs, recognised by most generic BLE terminal apps
// (e.g. "Serial Bluetooth Terminal", nRF Connect) so no custom phone app is
// needed to send commands.
function serviceUuid(): ArrayBuffer {
    return uuid(0x6e, 0x40, 0x00, 0x01, 0xb5, 0xa3, 0xf3, 0x93, 0xe0, 0xa9, 0xe5, 0x0e, 0x24, 0xdc, 0xca, 0x9e);
}

function commandUuid(): ArrayBuffer {
    return uuid(0x6e, 0x40, 0x00, 0x02, 0xb5, 0xa3, 0xf3, 0x93, 0xe0, 0xa9, 0xe5, 0x0e, 0x24, 0xdc, 0xca, 0x9e);
}

function stop(): void {
    chip_digital_write(0, 1);
    chip_digital_write(1, 1);
    chip_digital_write(2, 1);
    chip_digital_write(3, 1);
}

function forward(): void {
    chip_digital_write(0, 1);
    chip_digital_write(1, 0);
    chip_digital_write(2, 1);
    chip_digital_write(3, 0);
    chip_delay(500);
    stop();
}

function left(): void {
    chip_digital_write(0, 1);
    chip_digital_write(1, 1);
    chip_digital_write(2, 1);
    chip_digital_write(3, 0);
    chip_delay(500);
    stop();
}

function right(): void {
    chip_digital_write(0, 1);
    chip_digital_write(1, 0);
    chip_digital_write(2, 1);
    chip_digital_write(3, 1);
    chip_delay(500);
    stop();
}

export function main(): void {
    chip_pin_mode(0, PinMode.Output);
    chip_pin_mode(1, PinMode.Output);
    chip_pin_mode(2, PinMode.Output);
    chip_pin_mode(3, PinMode.Output);
    chip_pin_mode(4, PinMode.Output);
    chip_pin_mode(10, PinMode.Output); // Led

    chip_digital_write(10, 0);
    chip_delay(5000);

    ble_enable(String.UTF8.encode("WaaRover", true), String.UTF8.byteLength("WaaRover", true));

    const service = ble_service_create(serviceUuid(), 16);
    const command = ble_characteristic_create(
        service, commandUuid(), 16,
        CHRC_WRITE | CHRC_WRITE_NO_RESP, PERM_WRITE);
    ble_advertise_start();

    chip_digital_write(10, 1); // Led on once advertising, for visual confirmation

    // Motor driver is asleep by default, only wake it once BLE is ready.
    chip_digital_write(4, 1);
    stop();

    const bufSize = 20;
    const buf = new ArrayBuffer(bufSize);
    let wasConnected = false;
    while (true) {
        const isConnected = ble_connected();
        if (wasConnected && !isConnected) {
            ble_advertise_start(); // resume advertising after a disconnect
        }
        wasConnected = isConnected;

        if (ble_characteristic_available(command) == 1) {
            const len = ble_characteristic_read(command, buf, bufSize);
            const cmd = String.UTF8.decodeUnsafe(changetype<usize>(buf), len);
            if (cmd.startsWith("forward")) {
                forward();
            } else if (cmd.startsWith("left")) {
                left();
            } else if (cmd.startsWith("right")) {
                right();
            }
        }
        chip_delay(50);
    }
}

Mostly glue code that exposes parts of the zephyr bluetooth API to wasm using
primitives. I was able to do most of that using claude with some manual
intervention. I have currently only tested this on the ESP32 C3 super mini.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant