Created
December 11, 2019 15:58
-
-
Save ericdevries/6941fd8d2acbb235cfea107bcab69be7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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