通过蓝牙与Arduino揭开机器学习的神秘化
#javascript #arduino #蓝牙 #tfjs

我第一次尝试TensorFlow.js用于微控制器时,我对通过蓝牙转移到Arduino的机器学习模型的事实感到非常兴奋。网站上启用了几秒钟的手势控制!我的兴奋迅速变成了好奇心。它如何实际工作?

为了更好地理解它,我花了时间跳入开源脚本tf4micro-motion-kit.js,该脚本被用作Tiny Motion Trainer项目的一部分。在这篇文章中,我将解释如何通过蓝牙从浏览器转移到Arduino。

如果您对如何使用该技术来创建手势控制的Web应用程序感兴趣,请查看我写的关于此的previous blog post

蓝牙和gatt

本博客其余部分要理解的核心概念是蓝牙低能(BLE)设备使用所谓的 Services 特征来回传输数据 ,这是Gatt的一部分(通用属性配置文件)。 Gatt依靠ATT(属性协议)数据协议,该协议使用16位或128位UUID(普遍唯一标识符)来存储服务和特征。

您可以将服务和特性视为可以保存有关设备功能的数据的变量,可以从其他应用程序或设备中读取或写入或写入。

例如,您可以在许多BLE设备上找到的服务称为电池服务。它包含多个特性,例如电池电量和电池电源状态特性。当您购买现成的设备时,这些服务和特征已经为您设置了,您也许可以对他们进行一些阅读或编写请求。

在Arduino上,您可以创建自己的服务和特征。当涉及到能够将机器学习模型上传到董事会时,您可能需要具有具有特征的通用服务,例如传输状态,文件的总长度,培训模型的设置等等。 /p>

这些服务和特征是什么样的?如上所述,它们要么是16位或128位UUID,因此服务ID可能是:

"81c30e5c-0000-4f7d-a886-de3e90749161"

在此示例中,您有32个十六进制值。十六进制值表示为2^4,因此可以保持0或1的值4位。结果,32 x 4 = 128位。

当在Arduino上运行的程序中创建这些服务和特征时,可以使用这些UUID从浏览器中读取它们。

如果您想查看如何创建所有服务和特征,请查看the Arduino upload program

现在,让我们继续前进。

连接到董事会

首先,董事会必须以某种方式连接到浏览器才能发送或接收数据。在此项目中,该连接是通过蓝牙建立的。使用Web Bluetooth API,您需要从navigator.bluetooth对象上使用requestDevice方法开始。如果您喜欢的话,您可以通过一些过滤器传递,以便浏览器仅向您显示您在启动连接时感兴趣的设备。

例如,在tf4micro-motion-kit.js script中,在Arduino上运行的程序中创建的主服务UUID用作识别设备的过滤器。

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);

,您可以使用以下代码定义不同的特征,例如,使用与program uploaded to the Arduino中定义的ID相同的ID来定义文件长度特征:

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围绕Nordic Semiconductor芯片设计,该芯片的最大传输单元(MTU)为23个字节,这代表了一次可以发送的最大数据包大小。根据this resource的说法,此大小的最大数据吞吐量为128 kbps,因此您需要将机器学习模型分为此大小的数据包,以便能够将其传输到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位cyclic redundancy checksum,并在接收端重复此计算。如果两个值不匹配,则数据已经以某种方式损坏了,并且错误消息可能表明传输失败。

我不会深入研究CRC32函数的实现方式,但您可以在the repository中查看源。

Arduino如何运行模型

一旦Arduino收到完整的文件并验证了数据的完整性,设备上的代码使用IMU.readAcceleration方法从内置的传感器中获取实时输入数据,将数据转换为张量,并提供馈送。它可以预测手势。此代码是用C ++编写的,是您需要上传到Arduino的程序的一部分,因此我不会详细介绍,但是您可以查看this repository中的代码。

结论

通过蓝牙将机器学习模型从浏览器转移到Arduino,以5个步骤完成,包括使用Web蓝牙API将板连接到浏览器,将模型分为块,将它们一个接一个地转移到设备上,运行,运行它具有来自Arduino的内置传感器的实时数据,并在检测到手势后通知前端。

如果您想更深入地研究为Arduino草图编写的整个代码,则可以在this repository中找到它,如果您想查看编写的代码以创建模型Tiny Motion Trainer project is also open-source

如果您对这项技术建立了任何有趣的东西,请告诉我们!

与此同时,您可以在以下平台上使用Stripe开发人员更新:
ð£关注@StripeDevour team上的Twitter
ðiscribe我们的Youtube channel
−加入官方Discord server
ð§注册Dev Digest

关于作者

Charlie's profile picture. She has long brown hair and is wearing glasses. She is standing in front of a white wall with purple lights.

Charlie Gerard是Stripe,creative technologistGoogle Developer Expert的开发人员倡导者。她喜欢研究和experimenting with technologies。当她不编码时,她喜欢在户外度过时光,尝试新的啤酒和阅读。