Skip to content

Instantly share code, notes, and snippets.

@ericdevries
Created December 11, 2019 15:58
Show Gist options
  • Select an option

  • Save ericdevries/6941fd8d2acbb235cfea107bcab69be7 to your computer and use it in GitHub Desktop.

Select an option

Save ericdevries/6941fd8d2acbb235cfea107bcab69be7 to your computer and use it in GitHub Desktop.
import { BleManager } from 'react-native-ble-plx';
var Buffer = require('buffer').Buffer;
const SERVICE_ID = '0000fefb-0000-1000-8000-00805f9b34fb';
const DATA_TXCH_ID = '00000002-0000-1000-8000-008025000000';
const DATA_RXCH_ID = '00000001-0000-1000-8000-008025000000';
const CREDITS_TXCH_ID = '00000004-0000-1000-8000-008025000000';
const CREDITS_RXCH_ID = '00000003-0000-1000-8000-008025000000';
const MAX_PAYLOAD_SIZE = 240;
const FRAME_PADDING = 6;
const bytesPerResponse = 20;
function getAmountOfPages(totalBytes) {
let pages = Math.floor(totalBytes / MAX_PAYLOAD_SIZE);
if (totalBytes % MAX_PAYLOAD_SIZE !== 0) {
pages += 1;
}
return pages;
}
function getAmountOfMessagesForResponse(totalBytes, page) {
const pages = getAmountOfPages(totalBytes);
if (page !== pages - 1) {
return Math.ceil((FRAME_PADDING + MAX_PAYLOAD_SIZE) / bytesPerResponse);
}
return Math.ceil((totalBytes % MAX_PAYLOAD_SIZE) / bytesPerResponse);
}
function joinNumbers(lo, hi) {
return ((hi << 8) & (255 << 8)) + (lo & 255);
}
function splitNumbers(num) {
return [num & 255, num >> 8]; // 255 = b11111111
}
async function wait() {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 1000);
});
}
class DeviceManager {
manager = null;
onConnect = null;
start = (onConnect = null) => {
console.log('manager', this.manager)
this.onConnect = onConnect;
if (!this.manager) {
this.manager = new BleManager();
console.log('manager', this.manager)
}
const subscription = this.manager.onStateChange((state) => {
console.log('STATE', state)
if (state === 'PoweredOn') {
this.scanAndConnect();
subscription.remove();
}
}, true);
return this;
}
destroy = async () => {
await this.manager.destroy();
}
scanAndConnect = () => {
console.log('scanning')
this.manager.stopDeviceScan();
this.manager.startDeviceScan([SERVICE_ID], null, (error, device) => {
console.log(device.name);
// return;
if (error) {
console.log('ERROR', error)
// Handle error (scanning will be stopped automatically)
return
}
this.makeConnection(device);
this.manager.stopDeviceScan();
});
}
makeConnection = async (device) => {
console.log('making connection')
this.device = device;
this.device.onDisconnected((error, device) => {
console.log('ONDISCONNECT ERROR', error)
console.log('ONDISCONNECT DEVICE', device);
});
try {
this.device = await device.connect().then(d => d.discoverAllServicesAndCharacteristics());
console.log('services', await this.device.services())
// const discovered = await device.discoverAllServicesAndCharacteristics();
const chars = await this.device.characteristicsForService(
// device.id,
SERVICE_ID,
// DATA_RXCH_ID,
)
let dataRx, dataTx, creditsRx, creditsTx;
for (const c of chars) {
if (c.uuid === DATA_RXCH_ID) dataRx = c;
if (c.uuid === DATA_TXCH_ID) dataTx = c;
if (c.uuid === CREDITS_RXCH_ID) creditsRx = c;
if (c.uuid === CREDITS_TXCH_ID) creditsTx = c;
}
this.dataRx = dataRx;
this.dataTx = dataTx;
this.creditsRx = creditsRx;
this.creditsTx = creditsTx;
creditsTx.monitor((error, c) => {
if (c && c.value) {
const buffer = Buffer.from(c.value, 'base64');
console.log('credits', buffer)
}
})
const bp = Buffer.from([200]).toString('base64');
await creditsRx.writeWithResponse(bp);
if (this.onConnect) {
this.onConnect();
}
} catch (e) {
console.log('e',e.reason, e)
} finally {
// await this.manager.destroy();
}
}
requestCredits = async (amount) => {
await this.creditsRx.writeWithResponse(Buffer.from([amount]).toString('base64'));
this._creditCounter += amount;
await wait();
}
getResponsePaged = (payloadSize) => {
return new Promise((resolve, reject) => {
const dataTx = this.dataTx;
const buffer = [];
const unsubscribe = dataTx.monitor((error, c) => {
if (error) {
console.log('ERROR', error);
return;
}
const bufView = Buffer.from(c.value, 'base64');
buffer.push(...bufView);
if (buffer.length >= (payloadSize + 6)) {
// console.log('resolving with size', buffer.length, payloadSize)
resolve(buffer);
unsubscribe.remove();
}
});
});
}
getResponse = () => {
return new Promise((resolve, reject) => {
const dataTx = this.dataTx;
const unsubscribe = this.dataTx.monitor((error, c) => {
if (error) {
console.log('ERROR', error);
return;
}
const buffer = Buffer.from(c.value, 'base64');
unsubscribe.remove();
resolve(buffer);
});
});
}
sendMessage = async (command, data) => {
if (!data) data = [0]
const length =
2 + // device identifier (0 0)
1 + // length itself
1 + // command
data.length + // data
1; // checksum
const sum = data.reduce((a, b) => a + b, 0);
const checksum = (length + command + sum) & 255;
const request = Buffer.from([
33,
length,
0,
0,
command,
...data,
checksum,
]);
console.log('writing payload', request)
await this.dataRx.writeWithoutResponse(request.toString('base64'));
}
getPaginatedData = async (totalBytes, addrLow, addrHigh, addrSlave) => {
const pages = getAmountOfPages(totalBytes);
const ret = [];
let addr = joinNumbers(addrLow, addrHigh);
for (let i = 0; i < pages; ++i) {
const msgAmount = getAmountOfMessagesForResponse(totalBytes + 6, i);
const [l, h] = splitNumbers(addr);
const pagesize = Math.min(MAX_PAYLOAD_SIZE * (i + 1), totalBytes) - (MAX_PAYLOAD_SIZE * i);
await this.requestCredits(msgAmount);
// now read the data
const getResponse = this.getResponsePaged(pagesize);
await this.sendMessage(20, [l, h, addrSlave, pagesize]);
const bytes = await getResponse;
// strip frame stuff and add it to resulting content
ret.push(...bytes.slice(5, bytes.length - 1));
addr += MAX_PAYLOAD_SIZE
}
// data is 0 terminated, strip the \0
const text = new TextDecoder('utf-8').decode(Uint8Array.from(ret.slice(0, ret.length - 1)));
console.log('text: %s', text);
return JSON.parse(text);
}
sendAndReceive = async (command, data) => {
await this.requestCredits(1);
await this.sendMessage(command, data);
const response = await this.getResponse();
const slaveAddress = response[6];
const addressHigh = response[7];
const addressLow = response[8];
const totalBytes = response[10];
const paginatedData = await this.getPaginatedData(
totalBytes,
addressLow,
addressHigh,
slaveAddress
);
return paginatedData;
}
}
const manager = new DeviceManager();
export function something() {
return new Promise((resolve, reject) => {
manager.start(async () => {
try {
const response = await manager.sendAndReceive(50, 0);
resolve(response);
} catch (e) {
reject(e)
} finally {
await manager.destroy();
}
});
})
}
export default manager;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment