背景
在js项目中需要操作蓝牙设备。找到一个window上使用的js类库(noble-winrt - npm). 今天就来扒一扒noble-winrt的实现方式,以备查看。
如何在js中启动BleServe.exe
- 使用spawn(BLE_SERVER_EXE, [""]),启动进程,并传递初始参数。
let BLE_SERVER_EXE = path.resolve(__dirname, "prebuilt", "BLEServer.exe");
console.log(process.env.EE_SERVER_ENV);
if (process.env.EE_SERVER_ENV === "local") {
// electron开发模式下
BLE_SERVER_EXE = path.resolve(__dirname, "prebuilt", "BLEServer.exe");
} else {
// electron发布模式下
const CURRENT_EXE_PATH = process.execPath;
const directoryPath = path.dirname(CURRENT_EXE_PATH);
BLE_SERVER_EXE = path.join(directoryPath, "resources", "prebuilt", "BLEServer.exe");
}
init() {
this._deviceMap = {};
this._requestId = 0;
this._requests = {};
this._subscriptions = {};
this._bleServer = spawn(BLE_SERVER_EXE, [""]);
this._bleServer.stdout.pipe(new nativeMessage.Input()).on("data", (data) => {
this._processMessage(data);
});
this._bleServer.stderr.on("data", (data) => {
console.error("BLEServer:", data);
});
this._bleServer.on("close", (code) => {
this.state = "poweredOff";
this.emit("stateChange", this.state);
});
}
BleServe.exe
-
需要依赖BLE_SERVER_EXE,具体路径在noble-winrt-xd\prebuilt\BLEServer.exe中。
-
BLEServer.exe是一个控制台程序,启动后会在控制台输入窗口输出,也可以在控制台窗口输入命令。
-
输入的格式是nativeMessage.Input()。
-
nativeMessage.Input()
是chrome-native-messaging
库中的一个类,用于处理 Chrome Native Messaging 协议的输入消息。这个库通常用于在 Chrome 扩展或 Electron 应用中与本地应用程序进行通信。nativeMessage.Input()
会解析从标准输入(stdin)接收到的数据,并将其转换为 JavaScript 对象。
接收BLEServer.exe的控制台输出
- 精简的代码
// this._bleServer.stdout.pipe(new nativeMessage.Input()).on("data", (data) => {
// this._processMessage(data);
// });
- 为方便知道原理,增加地址打印
this._bleServer.stdout.on("data", (data) => {
const stringRecv = data.toString("hex");
console.log("BLEServer:", stringRecv);
const input = new nativeMessage.Input();
input.write(data); // 假设 nativeMessage.Input 有 write 方法
input.on("data", (parsedData) => {
console.log(`BLEServer nativeMessage = ${JSON.stringify(parsedData)}`);
this._processMessage(parsedData);
});
});
比如收到数据 BLEServer: 110000007b225f74797065223a225374617274227d
,转为nativeMessage
就是{"_type":"Start"}.
- BLEServer是c代码,处理速度比较快;所以会出现多条命令粘连的情况。使用nativeMessage.Input会把命令格式化单个的json命令,还能保证数据不会丢失和不完整。
发送数据到BleServe.ext
_sendMessage(message) {
debug("out:", message);
const dataBuf = Buffer.from(JSON.stringify(message), "utf-8");
const lenBuf = Buffer.alloc(4);
lenBuf.writeInt32LE(dataBuf.length, 0);
this._bleServer.stdin.write(lenBuf);
this._bleServer.stdin.write(dataBuf);
}
- 注意message是json对象,先对它进行序列化,转为字符串。然后获取字符串的长度,小端模式写入lenBuf。
- 写入数据到控制台,先写数据体长度,再写数据体。
异步转为同步方式
- 比如方法notify用于接收notify通知类型的数据。
- 通过requestId这个关键key保存{ resolve, reject }回调方法。
- 当收到通知时,通过this._requests[message._id]获取到回调方法。
notify(address, service, characteristic, notify) {
this._sendRequest({
cmd: notify ? "subscribe" : "unsubscribe",
device: this._deviceMap[address],
service: toWindowsUuid(service),
characteristic: toWindowsUuid(characteristic),
})
.then((result) => {
if (notify) {
this._subscriptions[result] = { address, service, characteristic };
} else {
// TODO - remove from subscriptions
}
this.emit("notify", address, service, characteristic, notify);
})
.catch((err) => this.emit("notify", address, service, characteristic, err));
}
_sendRequest(message) {
return new Promise((resolve, reject) => {
const requestId = this._requestId++;
this._requests[requestId] = { resolve, reject };
this._sendMessage(Object.assign({}, message, { _id: requestId }));
});
}
case "response":
if (this._requests[message._id]) {
if (message.error) {
this._requests[message._id].reject(new Error(message.error));
} else {
this._requests[message._id].resolve(message.result);
}
delete this._requests[message._id];
}
break;