Zephyr, 웹 블루투스 및 접근성

일반 마우스를 사용하는 UI 상호 작용은 우리 모두가 당연하게 여기는 것입니다. 그러나 일부에게는 이것이 좀 더 번거로울 수 있습니다.

Nordic Semiconductor의 nRF52840 Dongle은 메인SoC이 USB 및 Bluetooth 저에너지 연결을 모두 제어하는 ​​강력한 소형 장치입니다.



동글에서 실행되는 간단한 펌웨어Zephyr RTOS가 BLE에 연결된 전화/태블릿의 모든 컴퓨터에서 저렴하고 쉽게 사용자 지정되는 USB 마우스 컨트롤을 제공할 수 있는 방법을 살펴보겠습니다.



펌웨어



동글은 한 쪽에서는 BLE 연결 장치 사이의 브리지 역할을 하고 다른 쪽에서는 표준 USBHID 마우스 역할을 해야 하므로 이러한 기능을 펌웨어에서 활성화해야 합니다.
또한 웹 블루투스를 이용하여 웹 어플리케이션에서 쉽게 제어할 수 있도록 간단한GATT 서비스를 생성해야 한다.



참고: 이 게시물에서는 펌웨어(C 사용) 및 웹 애플리케이션(JavaScript 사용)의 주요 부분에 대한 개요만 살펴보겠습니다. PoC의 전체 소스는 GitHub에서 사용할 수 있으며 자세한 내용은 별도의 게시물에서 확인할 수 있습니다(관심 있는 경우 댓글을 달아주세요 ;)).

Zephyr에서 GATT 서비스 구조 정의:

/* Simple Mouse Service Declaration */
BT_GATT_SERVICE_DEFINE(sm_svc,
    BT_GATT_PRIMARY_SERVICE(&simplems_uuid),
    BT_GATT_CHARACTERISTIC(&simplems_move_xy_uuid.uuid, BT_GATT_CHRC_WRITE,
                BT_GATT_PERM_WRITE,
                NULL, write_move_xy, NULL),
    BT_GATT_CUD("Move XY", BT_GATT_PERM_READ),
    BT_GATT_CHARACTERISTIC(&simplems_buttons_uuid.uuid, BT_GATT_CHRC_WRITE,
                BT_GATT_PERM_WRITE,
                NULL, write_buttons, NULL),
    BT_GATT_CUD("Buttons", BT_GATT_PERM_READ),
);

서비스에 정의된 특성은 속성이 연결된 '변수'로 볼 수 있으며, 이를 통해 읽기, 쓰기 및/또는 알림을 받을 수 있습니다(~ 변경 사항 듣기). 이 예제에서는 연결된 응용 프로그램에서 마우스 이동 및 버튼 이벤트를 작성하는 데에만 관심이 있습니다.

클라이언트(웹 애플리케이션)가 동글을 찾고 펌웨어를 실행하려면 BLE 광고도 설정해야 합니다.

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_SIMPLE_MOUSE_SERVICE),
};

...

err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
    printk("Advertising failed to start (err %d)\n", err);
    return;
}

printk("Advertising successfully started\n");

연결된 클라이언트(이 경우 웹 블루투스를 사용하는 웹 애플리케이션)가 예를 들어 마우스 커서를 움직이면 dX 및 dY 값이 부호 있는 2바이트로 simple_move_xy 특성에 전송되고 쓰기 처리기에 의해 처리되며 등록된 콜백 함수로 전송됩니다.

static ssize_t write_move_xy(struct bt_conn *conn,
                  const struct bt_gatt_attr *attr,
                  const void *buf, uint16_t len,
                  uint16_t offset, uint8_t flags)
{
    int8_t val[2];

    if (offset != 0) {
        return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
    } else if (len != sizeof(val)) {
        return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
    }

    (void)memcpy(&val, buf, len);

    if (sm_cbs->move_xy) {
        sm_cbs->move_xy(val[0], val[1]);
    }

    return len;
}

콜백 함수는 이를 USB HID 마우스 이동 '이벤트'로 변환하고 USB 동글이 연결된 호스트 컴퓨터에 기록합니다.

static void sm_move_xy_cb(int8_t distx, int8_t disty) {
    printk("Move (x,y) = (%d, %d)\n", distx, disty);

    uint8_t statex = status[MOUSE_X_REPORT_POS];
    uint8_t statey = status[MOUSE_Y_REPORT_POS];

    statex += distx;
    statey += disty;

    if (status[MOUSE_X_REPORT_POS] != statex || status[MOUSE_Y_REPORT_POS] != statey) {
        status[MOUSE_X_REPORT_POS] = statex;
        status[MOUSE_Y_REPORT_POS] = statey;
        k_sem_give(&sem);
    }
}

...

while (true) {
    k_sem_take(&sem, K_FOREVER);

    report[MOUSE_BTN_REPORT_POS] = status[MOUSE_BTN_REPORT_POS];
    report[MOUSE_X_REPORT_POS] = status[MOUSE_X_REPORT_POS];
    status[MOUSE_X_REPORT_POS] = 0U;
    report[MOUSE_Y_REPORT_POS] = status[MOUSE_Y_REPORT_POS];
    status[MOUSE_Y_REPORT_POS] = 0U;
    err = hid_int_ep_write(hid_dev, report, sizeof(report), NULL);
    if (err) {
        printk("HID write error, %d\n", err);
    }
}

참고: 이 코드는 간단한 PoC를 위해 Zephyr 프로젝트의 USB HID 마우스 샘플에서 파생되었습니다.

웹 드라이버



제어 웹 애플리케이션의 다른 사용자는 요구 사항이 매우 다를 수 있습니다. 일부는 간단한 터치 조이스틱으로 마우스를 제어하기를 원할 수도 있고(이 예제에서와 같이) 다른 일부는 음성 명령이나 가속도계를 사용하여 마우스를 제어하기를 원할 수도 있습니다.

이를 위해 모든 프레임워크/웹 애플리케이션에 포함할 수 있는 간단한 웹 드라이버가 생성됩니다. 이 드라이버는 연결 및 명령 처리를 위해 Web Bluetooth를 사용하여 동글과의 필수 통신을 처리합니다.

전체 소스는 available on GitHub 이지만 여기에 몇 가지 필수 스니펫이 있습니다. 장치에 연결:

async scan() {
    const device = await navigator.bluetooth.requestDevice({
        filters: [
            { services: [SimpleMouseLinkUUID] },
            { name: 'Simple Mouse Link'}
        ]
    });

    if (device) {
        await this.openDevice(device);
    }
}

GATT 서비스 및 쓰기 명령 핸들러에 대한 액세스 권한 얻기:

async fetchWriteCharacteristics(server) {
    const service = await server.getPrimaryService(SimpleMouseLinkUUID);
    this.#writeFuncs.move_xy = await service.getCharacteristic(SimpleMouseMoveXYUUID);
    this.#writeFuncs.buttons = await service.getCharacteristic(SimpleMouseButtonsUUID);
}

마우스 이동 명령 보내기:

move(x, y) {
    if (this.#writeFuncs.move_xy) {
        const xx = cap_val(x);
        const yy = cap_val(y);
        console.log("Mouse move:", xx, yy)
        if (xx || yy) this.#writeFuncs.move_xy.writeValue(new Uint8Array([xx, yy]));
    }
}

전체 코드 및 데모!



펌웨어 및 웹 애플리케이션의 전체 소스는 GitHub에서 찾을 수 있으며 라이브 데모는 here에서 찾을 수 있습니다.

기능을 요청하거나 문제를 보고하거나 아래에 의견을 남겨주세요 :). 웹 연결 하드웨어용 Zephyr를 시작하는 방법과 후속 게시물에서 더 자세히 설명할 계획입니다.

지금은 동글이 PC에 연결되고 마우스가 Web Bluetooth를 통해 연결된 전화로 제어되는 작동 중인 데모 웹 응용 프로그램의 작은 비디오 캡처입니다(불행히도 BLE 연결 대화 상자를 표시하는 UI는 다음을 통해 표시되지 않음). DevTools 링크):



다음은 Web Bluetooth 대화 상자가 표시되는 전화기의 화면 캡처입니다.







Simple Mouse Link 웹 애플리케이션을 실행하는 전화에서 화면 캡처.



youtube.com

좋은 웹페이지 즐겨찾기