Первый раз я экспериментировал с TensorFlow.js для микроконтроллеров, я был очень взволнован тем фактом, что модель машинного обучения была передана через bluetooth на мой Ардуино. Всего за несколько секунд на сайте было включено управление жестами! Мое волнение быстро превратилось в любопытство; как это на самом деле работает?

Чтобы лучше понять это, я потратил время на изучение сценария с открытым исходным кодом. tf4micro-motion-kit.js который используется как часть проекта Крошечный тренажер движения. В этом посте я собираюсь объяснить, как модель машинного обучения может быть передана через Bluetooth из браузера в Arduino.

Если вам интересно, как эту технологию можно использовать для создания веб-приложений, управляемых жестами, ознакомьтесь с предыдущей записью в блоге, которую я написал об этом.


Bluetooth и ГАТТ

Основная концепция, которую необходимо понять для остальной части этого сообщения в блоге, заключается в том, что устройства Bluetooth с низким энергопотреблением (BLE) передают данные туда и обратно, используя то, что называется Сервисы а также характеристики, которые представляют собой концепции, являющиеся частью GATT (Generic ATTtribute Profile). GATT использует протокол данных ATT (Attribute Protocol), который использует 16-битные или 128-битные UUID (универсальные уникальные идентификаторы) для хранения услуг и характеристик.

Вы можете думать о службах и характеристиках как о переменных, которые будут хранить данные о функциях вашего устройства, которые можно читать или записывать из другого приложения или устройства.

Например, служба, которую вы можете найти на многих устройствах BLE, называется Battery Service. Он содержит несколько характеристик, например уровень заряда батареи и характеристику состояния батареи. Когда вы покупаете готовое устройство, эти сервисы и характеристики уже установлены для вас, и вы можете выполнять для них некоторые запросы на «чтение» или «запись».

Однако на Arduino вы можете создавать свои собственные сервисы и характеристики. Когда дело доходит до загрузки модели машинного обучения на доску, вам может понадобиться универсальный сервис с такими характеристиками, как состояние передачи, общая длина файла, настройки, с которыми была обучена ваша модель, и многое другое.

Как выглядят эти услуги и характеристики? Как упоминалось выше, это либо 16-битные, либо 128-битные UUID, поэтому идентификатор службы может быть:

"81c30e5c-0000-4f7d-a886-de3e90749161"
Войти в полноэкранный режим

Выйти из полноэкранного режима

В этом примере у вас есть 32 шестнадцатеричных значения. Шестнадцатеричное значение представлено как 2^4, поэтому 4 бита могут содержать значение либо 0, либо 1. В результате 32 x 4 = 128 бит.

Когда эти сервисы и характеристики создаются в программе, работающей на Arduino, их можно считывать из браузера, используя эти UUID.

Посмотри на программа загрузки Arduino если вы хотите увидеть, как создаются все услуги и характеристики.

Теперь давайте перейдем к тому, как это работает на практике.


Подключение к плате

Прежде всего, плата должна быть каким-то образом подключена к браузеру, чтобы иметь возможность отправлять или получать данные. В данном проекте это соединение установлено через bluetooth. С использованием Веб-API Bluetoothнеобходимо начать с использования requestDevice метод на navigator.bluetooth объект. Вы можете передать некоторые фильтры, если хотите, чтобы браузер показывал вам только интересующие вас устройства при установлении соединения.

Например, в tf4micro-motion-kit.js scriptUUID основной службы, созданный в программе, работающей на Arduino, используется в качестве фильтра для идентификации устройства.

const SERVICE_UUID = "81c30e5c-0000-4f7d-a886-de3e90749161";
const device = await navigator.bluetooth.requestDevice({
     filters: [{ services: [SERVICE_UUID] }],
 });
Войти в полноэкранный режим

Выйти из полноэкранного режима

Приведенный выше код возвращает обещание, и следующим шагом является подключение к устройству:

const server = await device.gatt.connect();
Войти в полноэкранный режим

Выйти из полноэкранного режима

Если этот шаг выполнен успешно, вы можете начать сохранять ссылки на эти услуги и характеристики в переменных, чтобы использовать их позже. Например, сохранение ссылки на основной сервис выполняется с помощью следующей строки:

const service = await server.getPrimaryService(SERVICE_UUID);
Войти в полноэкранный режим

Выйти из полноэкранного режима

И вы можете определить различные характеристики с помощью приведенного ниже кода, например, характеристику длины файла, используя тот же идентификатор, что и определенный в программа загружена в ардуино:

const FILE_LENGTH_UUID = "bf88b656-3001-4a61-86e0-769c741026c0";
const fileLengthCharacteristic = await service.getCharacteristic(FILE_LENGTH_UUID);
Войти в полноэкранный режим

Выйти из полноэкранного режима

Приведенная выше характеристика используется для обозначения размера файла модели машинного обучения в байтах, поэтому при его передаче в Arduino вы можете проверить, когда был передан весь файл.

После connect() был вызван метод и вы сохраняете все ссылки на интересующие вас услуги и характеристики, доска подключается к браузеру и можно переходить к следующему шагу.


Загрузка модели машинного обучения

Модель хранится в папке вашего приложения в виде .tflite файл. Во-первых, его необходимо загрузить с помощью fetch API или XMLHTTPRequest. Во-вторых, ответ должен служить буфером массива.

const loadFile = (modelUrl) => {
 return new Promise((resolve, reject) => {
   const oReq = new XMLHttpRequest();
   oReq.open("GET", modelUrl, true);
   oReq.responseType = "arraybuffer";

   oReq.onload = function () {
     const arrayBuffer = oReq.response;
     if (arrayBuffer) {
       resolve(arrayBuffer);
     } else {
       reject(new Error("Failed fetching arrayBuffer"));
     }
   };

   oReq.onerror = reject;
   oReq.send(null);
 });
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Как только файл загружен, следующий шаг подготавливает файл к передаче.


Разделение модели машинного обучения

Arduino Nano 33 BLE Sense разработан вокруг Северный полупроводник чип с максимальной единицей передачи (MTU) 23 байта, что представляет собой наибольший размер пакета, который может быть отправлен за один раз. Согласно с этот ресурсмаксимальная пропускная способность для этого размера составляет 128 кбит/с, поэтому вам необходимо разделить модель машинного обучения на пакеты такого размера, чтобы иметь возможность передавать ее на Arduino.

Вы также должны убедиться, что общий размер файла не слишком велик для обработки микроконтроллером.

Для этого вы начинаете со считывания характеристики, содержащей максимальное значение размера файла, из Arduino.

let maximumLengthValue = await fileMaximumLengthCharacteristic.readValue();
let maximumLengthArray = new Uint32Array(maximumLengthValue.buffer);
let maximumLength = maximumLengthArray[0];
// The fileBuffer variable is the model buffer you loaded in the code sample above.
 if (fileBuffer.byteLength > maximumLength) { 
   console.log(
     `File length is too long: ${fileContents.byteLength} bytes but maximum is ${maximumLength}.`
   );
   return;
 }
Войти в полноэкранный режим

Выйти из полноэкранного режима

Если файл не слишком длинный, вы можете начать его разбивать и передавать. В приведенном ниже коде подробно описаны различные шаги.

// 1. Start by writing the length value of the model to the file length characteristic on the Arduino.
let fileLengthArray = Int32Array.of(fileContents.byteLength);
await fileLengthCharacteristic.writeValue(fileLengthArray);

// 2. Pass the file buffer and the starting index to the function
sendFileBlock(fileContents, 0)

async function sendFileBlock(fileContents, bytesAlreadySent) {
// 3. Calculate the remaining bytes.
 let bytesRemaining = fileContents.byteLength - bytesAlreadySent;
 const MAX_BLOCK_LENGTH = 128;
 const blockLength = Math.min(bytesRemaining, MAX_BLOCK_LENGTH);

// 4. Transform the content into a Uint8array
 const blockView = new Uint8Array(fileContents, bytesAlreadySent, blockLength);

// 5. Write the value to the characteristic on the Arduino
 return fileBlockCharacteristic
   .writeValue(blockView)
   .then((_) => {
// 6. Remove the length of the block already sent from the size of bytes remaining
     bytesRemaining -= blockLength;
// 7. If the file has not finished transferring, repeat
     if (bytesRemaining > 0 && isFileTransferInProgress) {
       console.log(`File block written - ${bytesRemaining} bytes remaining`);
       bytesAlreadySent += blockLength;
       return sendFileBlock(fileContents, bytesAlreadySent);
     }
   })
   .catch((error) => {
     console.log(error);
     console.log(
       `File block write error with ${bytesRemaining} bytes remaining, see console`
     );
   });
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Как только этот код запустится, он должен передавать модель машинного обучения, пакет за пакетом, пока не останется байтов. Несмотря на то, что это работает, вы можете добавить дополнительный шаг, чтобы убедиться, что полученный файл совпадает с переданным файлом.


Контрольная сумма

Контрольную сумму можно использовать для обнаружения ошибок, которые могли возникнуть при передаче файла между браузером и Arduino. tf4micro-motion-kit.js библиотека использует CRC32 Функция проверки целостности данных.

Он вычисляет 32-битный циклическая избыточная контрольная сумма для переданных данных и повторяет этот расчет на принимающей стороне. Если два значения не совпадают, данные каким-то образом были повреждены, и сообщение об ошибке может означать, что передача не удалась.

Я не буду углубляться в то, как реализована функция CRC32, но вы можете просмотреть исходный код в хранилище.


Как Arduino запускает модель

После того, как Arduino получит полный файл и контрольная сумма подтвердит целостность данных, код на устройстве получает оперативные входные данные от встроенных датчиков, используя IMU.readAcceleration преобразует эти данные в тензоры и передает их модели для прогнозирования жестов. Этот код написан на C++ и является частью программы, которую необходимо загрузить в Arduino, поэтому я не буду вдаваться в подробности, но вы можете взглянуть на код в этот репозиторий.


Вывод

Передача модели машинного обучения из браузера в Arduino через bluetooth выполняется в 5 шагов, включая подключение платы к браузеру с помощью Web Bluetooth API, разбиение модели на фрагменты, передачу их один за другим на устройство, запуск в режиме реального времени. данные со встроенных датчиков Arduino и уведомление внешнего интерфейса после обнаружения жеста.

Если вы хотите глубже изучить весь код, написанный для скетча Arduino, вы можете найти его в этот репозиторий и если вы хотите взглянуть на код, написанный для создания модели, Проект Tiny Motion Trainer также имеет открытый исходный код.!

Если вы строите что-нибудь интересное с помощью этой технологии, дайте нам знать!

А пока вы можете быть в курсе обновлений разработчиков Stripe на следующих платформах:
📣 Подписаться @StripeDev а также Наша команда на Твиттер
📺 Подписывайтесь на нашу Канал YouTube
💬 Присоединяйтесь к официальному дискорд сервер
📧 Подпишитесь на Дайджест разработчиков


Об авторе

Фотография профиля Чарли.  У нее длинные каштановые волосы, и она носит очки.  Она стоит перед белой стеной с фиолетовыми огнями.

Чарли Джерард является защитником разработчиков в Stripe, креативный технолог а также Эксперт по разработчикам Google. Она любит исследовать и экспериментировать с технологиями. Когда она не программирует, ей нравится проводить время на свежем воздухе, пробовать новые сорта пива и читать.