Skip to content
This repository has been archived by the owner on Jul 21, 2024. It is now read-only.

Commit

Permalink
feat: initial working full monitor, custom shell command
Browse files Browse the repository at this point in the history
  • Loading branch information
DaBs committed Oct 4, 2022
1 parent bf2f78f commit cfa4dcd
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 13 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ node_modules

# Yarn
yarn-error.log
*.log
*.log

# Build output
dist
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"version": "0.0.1",
"main": "index.js",
"license": "MIT",
"scripts": {
"prepublish": "yarn tsc"
},
"dependencies": {
"easy-bits": "^1.2.2",
"serialport": "^10.4.0",
Expand Down
50 changes: 40 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface NRFDescriptors {

export interface NRFBTCharacteristic {
uuid: string;
handle: string;
handle: number;
serviceUUID?: string;
properties: BitSet<CharacteristicProperties>;
value?: Buffer;
Expand All @@ -64,13 +64,14 @@ export interface NRFBTDevice {
addressType: NRFBTAddressType;
advertisedProperties: BitSet<AdvertisedProperties>;
rssi: number;
mtu?: number;
name?: string;
scanResponse?: Buffer;
}

export class NRFBTShell {

private comPort: string;
private path: string;
private baudRate: number;

private currentIncomingMessage: string = '';
Expand All @@ -86,13 +87,13 @@ export class NRFBTShell {
private scanUnsubscribe: (() => void) | null = null;

constructor(path: string = 'COM3', baudRate: number = 115200) {
this.comPort = path;
this.path = path;
this.baudRate = baudRate;
}

private updateDevice(address: string, device: NRFBTDevice): NRFBTDevice {
private updateDevice(address: string, device: Partial<NRFBTDevice>): NRFBTDevice {
if (!this.btDevices[address]) {
this.btDevices[address] = device;
this.btDevices[address] = { ...device, address } as NRFBTDevice;
}

this.btDevices[address] = { ...this.btDevices[address], ...device };
Expand Down Expand Up @@ -202,6 +203,7 @@ export class NRFBTShell {
const matches = filterByPattern ? messageBuffer.match(filterByPattern) : null;
const stopMatch = stopPattern ? !!messageBuffer.match(stopPattern) : false;
if (!filterByPattern || matches) {
if (filterByPattern && filterByPattern.lastIndex) filterByPattern.lastIndex = 0;
callback(messageBuffer, matches);
}

Expand Down Expand Up @@ -292,7 +294,7 @@ export class NRFBTShell {
public async init() {
if (!this.serialPort) {
await new Promise((resolve, reject) => {
this.serialPort = new SerialPort({ path: this.comPort, baudRate: this.baudRate }, (err) => {
this.serialPort = new SerialPort({ path: this.path, baudRate: this.baudRate }, (err) => {
if (err) {
reject(err);
} else {
Expand Down Expand Up @@ -357,6 +359,16 @@ export class NRFBTShell {
this.attachDisconnectListener(() => {}, btAddress);
}

public async getMTU(btAddress: string) {
await this.guardSelectedDevice(btAddress);
const mtuValuePromise = this.waitForMessage(/MTU size: (\d+)/);
this.writeMessage('gatt att_mtu');
const mtuValue = await mtuValuePromise;
if (!mtuValue.matches) return null;
const mtu = parseInt(mtuValue.matches[1], 10);
this.updateDevice(btAddress, { mtu });
}

public async discoverPrimaryServices(btAddress: string) {
await this.guardSelectedDevice(btAddress);
const discoverRemove = this.attachToMessages((message, matches) => {
Expand Down Expand Up @@ -393,7 +405,7 @@ export class NRFBTShell {
if (characteristicMatches) {
const [, uuid, handle] = characteristicMatches;
const characteristicProperties = new BitField<CharacteristicProperties>();
currentCharacteristic = { uuid, handle, serviceUUID, properties: characteristicProperties };
currentCharacteristic = { uuid, handle: parseInt(handle, 16), serviceUUID, properties: characteristicProperties };
if (outputLines[i + 1]?.includes('Properties:')) {
i++; // We skip this line;
let propertiesLine = i + 1;
Expand Down Expand Up @@ -433,7 +445,7 @@ export class NRFBTShell {
await this.guardSelectedDevice(btAddress);
const characteristic = this.btCharacteristics[btAddress][characteristicUUID];
const collectedMessagesPromise = this.collectMessagesBetween(/Read pending/, /Read complete: err (0x\d{2}) length 0/);
this.writeMessage(`gatt read ${parseInt(characteristic.handle, 10) + 1} 0`);
this.writeMessage(`gatt read ${(characteristic.handle + 1).toString(16)} 0`);
const collectedMessages = await collectedMessagesPromise;
let collectedLines = collectedMessages.split('\n');

Expand Down Expand Up @@ -477,7 +489,7 @@ export class NRFBTShell {
await this.guardSelectedDevice(btAddress);
const characteristic = this.btCharacteristics[btAddress][characteristicUUID];
const waitingPromise = this.waitForMessage(/Write complete: err (0x\d{2})/);
this.writeMessage(`gatt write ${parseInt(characteristic.handle, 10) + 1} 0 ${data.toString('hex')}`);
this.writeMessage(`gatt write ${(characteristic.handle + 1).toString(16)} 0 ${data.toString('hex')}`);
const { message, matches } = await waitingPromise;
if (matches && matches[1] !== '0x00') {
throw new Error(`Error writing to characteristic ${characteristic.uuid}, err: ${matches[1]}`);
Expand All @@ -486,9 +498,27 @@ export class NRFBTShell {
return true;
}

public async monitorCharacteristic(btAddress: string, characteristicUUID: string) {
public async monitorCharacteristic(btAddress: string, characteristicUUID: string, callback: (error: Error | null, data: Buffer | null) => void) {
await this.guardSelectedDevice(btAddress);
const characteristic = this.btCharacteristics[btAddress][characteristicUUID];

const unsubscribe = this.attachToMessages(async (message, matches) => {
if (!matches) return null;
const [, address, length] = matches;
const notifyDataPromise = this.waitForMessage(/Notify data: (.+)/);
this.writeMessage(`read_notify_data ${address} ${length}`);
const notifyData = await notifyDataPromise;
if (!notifyData.matches?.[1]) return null;
const notifyDataBuffer = Buffer.from(notifyData.matches[1].replace(/ /gm, '').replace(/0x/gm, ''), 'hex');
if (callback) callback(null, notifyDataBuffer);
}, /Notification: data (0x[a-zA-Z0-9]+) length (\d+)/);

// FIXME: Should discover descriptors and locations for it instead of this crap, but eh.
this.writeMessage(`gatt subscribe ${(characteristic.handle + 2).toString(16)} ${(characteristic.handle + 1).toString(16)} 0`);

return () => {
if (unsubscribe) unsubscribe();
}
}


Expand Down
8 changes: 7 additions & 1 deletion tests/integration-test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { SerialPort } from "serialport";
import { NRFBTDevice, NRFBTShell } from '../src/index';


const main = async () => {
const shell = new NRFBTShell();

const shell = new NRFBTShell('/dev/tty.usbmodem141201');

await shell.init();
console.log('Initialized');
Expand All @@ -18,6 +20,10 @@ const main = async () => {
const value = await shell.readCharacteristic(device.address, '57ba394e-4abd-4a91-b802-10d3a0d100f5');
console.log(value);
await shell.writeCharacteristic(device.address, 'd9b912db-ba72-4aff-b517-2c8a95401cfd', Buffer.from([0xaa, 0xbb]));
shell.monitorCharacteristic(device.address, '57ba394e-4abd-4a91-b802-10d3a0d100f5', (error, data) => {
if (error) throw error;
console.log(data);
});
}


Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"downlevelIteration": true,
"esModuleInterop": true,
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"strict": true,
"sourceMap": true,
"lib": ["ESNext"]
"lib": ["ESNext"],
"outDir": "dist"
},
"include": [
"src/**/*"
Expand Down

0 comments on commit cfa4dcd

Please sign in to comment.