Skip to content

Instantly share code, notes, and snippets.

@carlosdelfino
Last active March 10, 2018 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlosdelfino/25f41a053eed2859fd1a8fba7ce84f63 to your computer and use it in GitHub Desktop.
Save carlosdelfino/25f41a053eed2859fd1a8fba7ce84f63 to your computer and use it in GitHub Desktop.
What is the best way to do a FIFO for Nativescript UI Chart
I have an algorithm that receives a stream of bytes for a Bluetooth serial with RFComm, this algorithm should process around 50KB / s (Bytes), but the bluetooth only delivers 57Kb / s (bits)
and thus creates an object based on the analysis of packets composed of 7 bytes in average, using only two bytes (short) and discarding the remaining ones, finally each object also a contactor to identify its order of obtaining, among other information to assist the management of the processing and display.
This process is very slow I should be able to at least process 7KB / s (twenty thousand bytes per second) but I get much less. on average 10B / s (ten bytes per second).
There is a calback that is responsible for updating the ViewModel property of the UI that displays a line chart (Nativescript-ui-pro Chart). The big problem is that this graph can only display 2000 to 2500 dots at a time for the image to be suitable, so I'm removing the odd bytes as in a fifo buffer.
Could anyone help me identify a more efficient way to update the graph and also improve the marsharling of data from Bluetooth to JavaScript
I put the code in this link: https://gist.github.com/carlosdelfino/25f41a053eed2859fd1a8fba7ce84f63
I suspect that the process is slow because of the byte offset in the callback array, or in the view
Tenho um algoritimo que recebe um stream de bytes por uma serial do Bluetooth com RFComm, este algoritimo deveria processar em torno de 50KB/s (Bytes), mas o bluetooth só entrega 57Kb/s(bits)
e assim cria um objeto baseado na analise de pacotes compostos de 7 bytes em média, usando apenas dois bytes (short) e descartando os restantes, finalmente cada objeto também um contator para identificar sua ordem de obtenção, entre outras informações para auxiliar o gerenciamento do processamento e exibição.
Este processo esta´muito lento eu deveria estar conseguindo no mínimo processar 7KB/s (vinte mil bytes por segundo) mas consigo bem menos. em média 10B/s (dez bytes por segundo).
Existe um calback que é responsável pela atualização da propriedade do ViewModel da UI que exibe um grafico de linhas (Nativescript-ui-pro Chart). O grande problema é que este gráfico só pode exibir 2000 a 2500 pontos por vez para que a imagem fique adequada, então vou removendo os bytes excentes como num buffer fifo.
Alguém poderia me ajudar a identificar uma forma mais eficiente de atualizar o gráfico e também melhorar o marsharling dos dados do Bluetooth (Android) para JavaScript
Eu coloquei o código neste link: https://gist.github.com/carlosdelfino/25f41a053eed2859fd1a8fba7ce84f63
Eu desconfio que o processo esteja lento devido ao deslocamento dos bytes no array do callback, ou na exibição
import { Bluetooth, BluetoothStatus, ConnectOptions, BluetoothInputStream, BluetoothDataProcessor } from "bluetooth-manager";
import * as EventsManagerModule from "../../events-manager";
const eventsManager = EventsManagerModule.getInstance();
import * as TraceManagerModule from "../../trace-manager";
const myTrace = TraceManagerModule.getInstance();
import { removeSignalFromByte, convertTwoBytesToWord } from "../../../libs/math";
import { Data, RawData } from "../../../data-objects";
import { State, StateUpdate, Point } from "./../-state"
import { DeviceOptions } from "./-device-options";
const START = 0;
const SYNC_2 = 1;
const PAYLOAD_LENGHT = 2;
const PAYLOAD = 3;
const CRC_CHECK = 4;
export default class BackgroundDataCollector implements BluetoothDataProcessor {
private _lastSignal: number = 0;
private _goodSignal: boolean = false;
private _lastBadSignalTimeOut: number = 0;
private _lastBadSignalTime: Date = null;
private _lastGoodSignalTimeRun: number = 0;
private _lastGoodSignalTime: Date = null;
private _timeOutConnect: number;
private _limitOfPayloads: number;
private _lastMinValue: number;
private _lastMaxValue: number;
private _lastHeartRate: number;
private _lastBatteryLevel: any;
private _lastRRInterval: any;
private _CountStep: number;
private _Count: number;
private _RawData: Point[];
private _callback: (callbackData: State) => void;
private _dataStream: BluetoothInputStream;
private _backgroundDataCollectorId: number;
private _backgroundDataCollectorStoped: boolean = false;
private _countLimit: number;
private _waitConnect: number;
private _lastConnect: number;
private _idxOffset: number;
private _lastStart: Date;
private _lastEnd: Date;
private _crcError: number;
private _connectOptions: DeviceOptions;
private _count: number;
public constructor(connectOptions: DeviceOptions, callback: (callbackData: State) => void) {
myTrace.write("Criando BackgroundDataCollector");
this._connectOptions = connectOptions;
this._callback = (callbackData: State) => {
//console.time("backgroundatacollector.callback");
setTimeout(callback, 1, callbackData);
//console.timeEnd("backgroundatacollector.callback");
};
this._count = 0;
this._CountStep = 1;
// no android retorna um inputstream.
this._dataStream = Bluetooth.getDataStream(this._connectOptions);
this._backgroundDataCollectorStoped = false;
this._lastSignal = 0;
this._goodSignal = false;
this._lastBadSignalTimeOut = 0;
this._lastBadSignalTime = null;
this._lastGoodSignalTimeRun = 0;
this._lastGoodSignalTime = null;
//this._connectOptions = super.connectOptions;
this._timeOutConnect = this._connectOptions.timeOutConnect;
this._waitConnect = this._connectOptions.waitConnect;
this._countLimit = this._connectOptions.tryConnect;
this._limitOfPayloads = this._connectOptions.limitOfPayloads;
Bluetooth.addDataProcessor(this._connectOptions, this);
myTrace.write("BackgroundDataCollector criado");
}
private get time(): number {
return (new Date()).getTime();
}
public start() {
this._lastConnect = this.time;
this._lastSignal = 0;
this._goodSignal = false;
this._lastBadSignalTimeOut = 0;
this._lastBadSignalTime = null;
this._lastGoodSignalTimeRun = 0;
this._lastGoodSignalTime = null;
this._lastRRInterval = 0;
this._lastHeartRate = 0;
this._lastSignal = 0;
this._lastBatteryLevel = 0;
this._lastMaxValue = 0;
this._lastMinValue = 0;
this._crcError = 0;
this._idxOffset = 0;
/**
* Armazena os dados do , para uso no callback
*
* A propriedade RawDAta, deve armazenar todo o coletado do inicio ao fim da conexão.
* Quando uma conexão terminar por que motivo for, os dados devem ser armazenados para uso na reconexão.
*
* Porém se ouver umc omando do usuário para desconectar e reconectar, então será uma nova coleta,
* sendo assim os dados serão perdidos, mas veja detalhes em RawData_DB.
*/
this._RawData = new Array(0);
/**
* Contagem de pontos do coletados no em formato RAW.
*/
this._Count = 0;
/*
* Adiciona como time out para ser executado somente após o termino de cada tentativa.
*/
if (!this._backgroundDataCollectorStoped) {
this._backgroundDataCollectorId = eventsManager.setTimeout(() => { this._collect() }, 1);
} else {
this._lastEnd = new Date();
let callbackData: State = {
Ended: this._lastEnd,
update: StateUpdate._ended
} as State;
this._callback(callbackData);
}
}
public stop() {
eventsManager.clearTimeout(this._backgroundDataCollectorId);
this._backgroundDataCollectorStoped = true;
this._lastEnd = new Date();
let callbackData: State = <State>{
Ended: this._lastEnd,
update: StateUpdate._ended
};
this._callback(callbackData);
}
private _collect() {
if (this._backgroundDataCollectorStoped) return;
let callbackData: State;
if (Bluetooth.isDeviceConnected(this._connectOptions)) {
let payload: number[] = this._processStream();
if (payload == undefined) return;
this._processPayLoad(payload);
if (this._goodSignal) {
callbackData = {
data: this._RawData,
connection: true,
status: BluetoothStatus.ok,
update: StateUpdate.data
} as State;
// ajustar o tempo da próxima chamada
this._callback(callbackData);
}
this._RawData = new Array<Point>(0);
// console.timeEnd("backgroundDataCollector.connected.collect");
} else {
this._tryConnect();
}
this._lastConnect = this.time;
/*
* Adiciona como time out para ser executado somente após o termino de cada tentativa.
*/
if (this._backgroundDataCollectorStoped ||
(this._goodSignal && this._lastGoodSignalTimeRun > this._connectOptions.signal.timeRun) ||
(this._lastBadSignalTimeOut > this._connectOptions.signal.timeOut)) {
this._backgroundDataCollectorStoped = true;
if (this._lastStart != undefined) {
this._lastEnd = new Date();
callbackData = {
Ended: this._lastEnd,
update: StateUpdate._ended,
status: BluetoothStatus.enabled
};
this._callback(callbackData);
}
} else {
this._backgroundDataCollectorId = eventsManager.setTimeout(() => { this._collect() }, 100);
}
}
private _tryConnect() {
let callbackData: State;
let nextConnect: number = this._lastConnect + this._waitConnect;
let atualConnect: number = this.time; // tempo que iniciou o backgroundDataCollector e se for o caso tentativas de conexão
if (this._count >= this._countLimit) {
// myTrace.write("Manager...backgroundDataCollector: limite de tentativa estourado: " + this._count);
this._count = 0;
this._backgroundDataCollectorStoped = true;
callbackData = {
connection: false,
status: BluetoothStatus.fail,
count: this._count,
countLimit: this._countLimit,
update: StateUpdate.connection
} as State;
this._callback(callbackData);
} else if (atualConnect >= nextConnect) {
this._count++;
//myTrace.write("Manager...backgroundDataCollector: tentativa de conexão: " + this._count);
try {
let connected = Bluetooth.isDeviceConnected(this._connectOptions);
if (!connected) {
Bluetooth.connect(this._connectOptions);
connected = Bluetooth.isDeviceConnected(this._connectOptions);
}
if (connected) this._count = 0;
callbackData = {
connected,
status: BluetoothStatus.trying,
count: this._count,
countLimit: this._countLimit
} as State;
} catch (err) {
myTrace.write("Manager...backgroundDataCollector.catch: " + err);
callbackData = {
connected: false,
status: BluetoothStatus.fail,
count: this._count,
countLimit: this._countLimit,
update: StateUpdate.connection,
error: err.toString()
} as State;
this._callback(callbackData);
return;
}
this._callback(callbackData);
}
}
private _processStream(): number[] {
//console.time("backgroundDataCollector.connected.collect");
let callbackData: State;
let readStage: number = 0;
let syncBytes: boolean = false;
let checksum: number = 0;
let totalPayloads: number = 0;
let payload: number[] = new Array(0);
let dataRow: number[];
let dataRowLength: number = 0;
let needData: number = this._connectOptions.bufferSize;
/**
* Não usporta o uso de marcação (mark), mas pode ser implementado no proxy.
* let useMark = dataStream.markSupported();
*/
let lastWhile: number;
while (needData > 0) {
//console.time("backgroundDataCollector.connected.collect.while.needData");
// console.log(needData);
lastWhile = this.time;
//console.time("backgroundDataCollector.connected.collect.while.needData.read");
let buffer: any[] = Array.create("byte", needData);
try { this._dataStream.read(buffer); } catch (error) {
this._backgroundDataCollectorStoped = true;
callbackData = {
status: BluetoothStatus.error,
update: StateUpdate.connection,
error: error.toString()
} as State;
this._callback(callbackData);
return;
}
//console.timeEnd("backgroundDataCollector.connected.collect.while.needData.read");
for (let i = 0; i < buffer.length; i++) {
this._idxOffset++;
let tmpValue = removeSignalFromByte(buffer[i]);
if (readStage == PAYLOAD && dataRowLength) {
dataRow.push(tmpValue);
checksum += tmpValue;
dataRowLength--;
if (dataRowLength == 0) readStage++;
} else if (readStage <= SYNC_2 && tmpValue == 0xAA) {
// sync data (0xAA 0xAA)
readStage++;
} else if (readStage == PAYLOAD_LENGHT) {
// sync data ok
// lenght data
dataRowLength = tmpValue;
dataRow = new Array(0);
readStage++;
} else if (readStage == CRC_CHECK) {
// leitura do CRC e conferência;
let crc = tmpValue;
checksum &= 0xFFFFFFFF;
checksum = ~checksum & 0xFFFFFFFF;
checksum = checksum << 24;
crc = crc << 24;
readStage = 0;
dataRowLength = 0;
if (crc != checksum) {
// indicar erro de crc
// em caso error contar os erros para uso futuro indicando problemas na conexão.
this._crcError++;
} else {
totalPayloads++;
// payload.push(...dataRow); // parou de funcionar sem explicação
payload.push.apply(payload, dataRow);
}
// fim crc_check
}
// java.lang.Thread.sleep(this._connectOptions.sleepTimerRead);
} // fim loop coleta payload
// verifica se precisa de mais dados
switch (readStage) {
case PAYLOAD:
// console.log("payload");
needData = dataRowLength + 1;
break;
case SYNC_2:
// console.log("sync_2");
needData = 5;
break;
case PAYLOAD_LENGHT:
// console.log("payload lenght");
needData = 4;
break;
default:
// console.log("finalizou");
needData = 0;
break;
}
// fim verifica se precisa de mais dados
// console.timeEnd("backgroundDataCollector.connected.collect.while.needData");
}// fim while
return payload;
}
private _processPayLoad(payload: number[]) {
let callbackData: State;
let excode = 0;
for (let i = 0; i < payload.length; i++) {
// console.time("backgroundDataCollector.connected.collect.for.needData.processPayload");
let tmpValue: number = payload[i];
if (tmpValue == 0x80 && excode == 0) {
// readed payload code
// em formato raw
// lê dois bytes e transforma em um valor de 16 bits
let lenght: number = payload[++i];
// console.time("backgroundDataCollector.connected.collect.for.needData.processPayload");
if (this._goodSignal) for (let x = lenght; x > 0; x -= 2) {
this._Count += this._CountStep;
const Raw: Point = {
idx: this._Count,
value: convertTwoBytesToWord(payload[++i], payload[++i])
} as Point;
this._RawData.push(Raw);
}
else {
i += lenght;
}
} else if (tmpValue == 0x86 && excode == 0) {
// readed payload code
// last RR Interval
// lê dois bytes e transforma em um valor de 16 bits
let lenght: number = payload[++i];
if (this._goodSignal) {
this._lastRRInterval = convertTwoBytesToWord(payload[++i], payload[++i]);
callbackData = {
rrInterval: this._lastRRInterval,
data: this._RawData,
connection: true,
status: BluetoothStatus.ok,
update: StateUpdate.rr_interval
} as State;
// ajustar o tempo da próxima chamada
// console.time("backgroundDataCollector.while.needData.processPayload.callback");
this._callback(callbackData);
} else {
i += lenght;
}
// console.timeEnd("backgroundDataCollector.while.needData.processPayload.callback");
this._RawData = new Array(0);
} else if (tmpValue == 0x01 && excode == 0) {
// Battery level
this._lastBatteryLevel = payload[++i];
callbackData = {
BatteryLevel: this._lastBatteryLevel,
data: this._RawData,
connection: true,
status: BluetoothStatus.ok,
update: StateUpdate.battery_level
} as State;
// ajustar o tempo da próxima chamada
this._callback(callbackData);
this._RawData = new Array<Point>(0);
} else if (tmpValue == 0x03 && excode == 0) {
// readed payload code
// heart rate
if (this._goodSignal) {
this._lastHeartRate = payload[++i];
callbackData = {
signal: this._lastSignal,
heartRate: this._lastHeartRate,
data: this._RawData,
connection: true,
status: BluetoothStatus.ok,
update: StateUpdate.heart_rate
} as State;
// ajustar o tempo da próxima chamada
this._callback(callbackData);
}
else {
i++;
}
this._RawData = new Array<Point>(0);
} else if (tmpValue == 0x02 && excode == 0) {
// readed payload code
// signal quality
this._lastSignal = payload[++i];
const time: Date = new Date();
this._goodSignal = this._lastSignal >= this._connectOptions.signal.level;
if (this._goodSignal) {
if (this._lastStart == null) {
this._lastStart = time;
callbackData = {
Started: this._lastStart,
status: BluetoothStatus.connected,
update: StateUpdate._started
} as State;
this._callback(callbackData);
}
if (this._lastGoodSignalTime == null) this._lastGoodSignalTime = time;
this._lastBadSignalTime = null;
this._lastBadSignalTimeOut = 0;
this._lastGoodSignalTimeRun = time.getTime() - this._lastGoodSignalTime.getTime();
this._lastGoodSignalTime = time;
} else {
if (this._lastBadSignalTime == null) this._lastBadSignalTime = time;
this._lastGoodSignalTime = null;
this._lastGoodSignalTimeRun = 0;
this._lastBadSignalTimeOut = time.getTime() - this._lastBadSignalTime.getTime();
this._lastBadSignalTime = time;
}
myTrace.write("Qualidade do Sinal: " + this._lastSignal + " Tempo: " + this._lastGoodSignalTime);
callbackData = {
signal: this._lastSignal,
data: this._RawData,
connection: true,
status: BluetoothStatus.ok,
update: StateUpdate.signal
} as State;
// ajustar o tempo da próxima chamada
this._callback(callbackData);
this._RawData = new Array<Point>(0);
} else if (tmpValue == 0x08 && excode == 0) {
i++;
console.log("Codigo 0x08: 0x%d", payload[i].toString(16));
} else if (tmpValue == 0x84 && excode == 0) {
let lenght = payload[++i];
let s = "Codigo 0x%d comprimento: 0x%d ";
for (let x = lenght; x > 0; --x) {
s = s + " | 0x" + payload[++i].toString(16);
}
s += " |";
console.log(s, tmpValue.toString(16), lenght.toString(16));
} else if (tmpValue == 0x85 && excode == 0) {
let lenght = payload[++i];
let s = "Codigo 0x%d comprimento: 0x%d ";
for (let x = lenght; x > 0; --x) {
s = s + " | 0x" + payload[++i].toString(16);
}
s += " |";
console.log(s, tmpValue.toString(16), lenght.toString(16));
} else if (tmpValue == 0x55) {
// readed payload extracode
excode++;
myTrace.write("Extra Code!");
} else if (tmpValue == 0) {
excode = 0;
} else {
console.log();
if (tmpValue => 0x0080) {
let lenght = payload[++i];
let s = "Codigo 0x%d comprimento: 0x%d ";
for (let x = lenght; x > 0; --x) {
s = s + " | " + payload[++i].toString();
}
s += " |";
console.log(s, tmpValue.toString(16), lenght.toString(16));
} else {
++i;
}
}
// finalizaou a leitura do payload;
// console.timeEnd("backgroundDataCollector.connected.collect.for.needData.processPayload");
} // finaliza processamento payload
}
}
// point
valuesPush(model: ViewModel, data: Point[]) {
model.values.push(...data);
//TODO verificar uma nova abordagem que ao invez de mover para esquerda o gráfico,
// ao chegar ao final pula para o inicio sobreescrevedno o existente.
if (model.values.length > manager.UIOptions.length) {
model.values.splice(0, model.values.length - manager.uIOptions.length);
}
}
class Point {
value: number;
idx: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment