You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
10 KiB
JavaScript
300 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const ConnectionType = {
|
|
Serial: 0,
|
|
TCP: 1,
|
|
UDP: 2,
|
|
BLE: 3
|
|
}
|
|
|
|
class Connection {
|
|
|
|
constructor() {
|
|
this._connectionId = false;
|
|
this._openRequested = false;
|
|
this._openCanceled = false;
|
|
this._bitrate = 0;
|
|
this._bytesReceived = 0;
|
|
this._bytesSent = 0;
|
|
this._transmitting = false;
|
|
this._outputBuffer = [];
|
|
this._onReceiveListeners = [];
|
|
this._onReceiveErrorListeners = [];
|
|
|
|
if (this.constructor === Connection) {
|
|
throw new TypeError("Abstract class, cannot be instanced.");
|
|
}
|
|
|
|
if (this.connectImplementation === Connection.prototype.connectImplementation) {
|
|
throw new TypeError("connectImplementation is an abstract member and not implemented.")
|
|
}
|
|
|
|
if (this.disconnectImplementation === Connection.prototype.disconnectImplementation) {
|
|
throw new TypeError("disconnectImplementation is an abstract member and not implemented.")
|
|
}
|
|
|
|
if (this.addOnReceiveCallback === Connection.prototype.addOnReceiveCallback) {
|
|
throw new TypeError("addOnReceiveCallback is an abstract member and not implemented.")
|
|
}
|
|
|
|
if (this.removeOnReceiveCallback === Connection.prototype.removeOnReceiveCallback) {
|
|
throw new TypeError("removeOnReceiveCallback is an abstract member and not implemented.")
|
|
}
|
|
|
|
if (this.addOnReceiveErrorCallback === Connection.prototype.addOnReceiveErrorCallback) {
|
|
throw new TypeError("addOnReceiveErrorCallback is an abstract member and not implemented.")
|
|
}
|
|
|
|
if (this.removeOnReceiveErrorCallback === Connection.prototype.removeOnReceiveErrorCallback) {
|
|
throw new TypeError("removeOnReceiveErrorCallback is an abstract member and not implemented.")
|
|
}
|
|
}
|
|
|
|
get connectionId() {
|
|
return this._connectionId;
|
|
}
|
|
|
|
get bitrate() {
|
|
return this._bitrate;
|
|
}
|
|
|
|
get type() {
|
|
switch (this.constructor.name) {
|
|
case ConnectionSerial.name:
|
|
return ConnectionType.Serial;
|
|
case ConnectionTcp.name:
|
|
return ConnectionType.TCP;
|
|
case ConnectionUdp.name:
|
|
return ConnectionType.UDP;
|
|
case ConnectionBle.name:
|
|
return ConnectionType.BLE;
|
|
}
|
|
}
|
|
|
|
static create(type) {
|
|
if (Connection.instance && (Connection.instance.type == type || Connection.instance.connectionId)){
|
|
return Connection.instance;
|
|
}
|
|
|
|
switch (type) {
|
|
case ConnectionType.BLE:
|
|
Connection.instance = new ConnectionBle();
|
|
break;
|
|
case ConnectionType.TCP:
|
|
Connection.instance = new ConnectionTcp();
|
|
break;
|
|
case ConnectionType.UDP:
|
|
Connection.instance = new ConnectionUdp();
|
|
break;
|
|
default:
|
|
case ConnectionType.Serial:
|
|
Connection.instance = new ConnectionSerial();
|
|
break;
|
|
}
|
|
return Connection.instance;
|
|
};
|
|
|
|
connectImplementation(path, options, callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
connect(path, options, callback) {
|
|
this._openRequested = true;
|
|
this._failed = 0;
|
|
this.connectImplementation(path, options, connectionInfo => {
|
|
if (connectionInfo && !this._openCanceled) {
|
|
this._connectionId = connectionInfo.connectionId;
|
|
this._bitrate = connectionInfo.bitrate;
|
|
this._bytesReceived = 0;
|
|
this._bytesSent = 0;
|
|
this._openRequested = false;
|
|
|
|
this.addOnReceiveListener((info) => {
|
|
this._bytesReceived += info.data.byteLength;
|
|
});
|
|
|
|
console.log('Connection opened with ID: ' + connectionInfo.connectionId + ', Baud: ' + connectionInfo.bitrate);
|
|
|
|
if (callback) {
|
|
callback(connectionInfo);
|
|
}
|
|
} else if (connectionInfo && this.openCanceled) {
|
|
// connection opened, but this connect sequence was canceled
|
|
// we will disconnect without triggering any callbacks
|
|
this._connectionId = connectionInfo.connectionId;
|
|
console.log('Connection opened with ID: ' + connectionInfo.connectionId + ', but request was canceled, disconnecting');
|
|
|
|
// some bluetooth dongles/dongle drivers really doesn't like to be closed instantly, adding a small delay
|
|
setTimeout(() => {
|
|
this._openRequested = false;
|
|
this._openCanceled = false;
|
|
this.disconnect(() => {
|
|
if (callback) {
|
|
callback(false);
|
|
}
|
|
});
|
|
}, 150);
|
|
} else if (this._openCanceled) {
|
|
// connection didn't open and sequence was canceled, so we will do nothing
|
|
console.log('Connection didn\'t open and request was canceled');
|
|
this._openRequested = false;
|
|
this._openCanceled = false;
|
|
if (callback) {
|
|
callback(false);
|
|
}
|
|
} else {
|
|
this._openRequested = false;
|
|
console.log('Failed to open');
|
|
googleAnalytics.sendException('FailedToOpen', false);
|
|
if (callback) {
|
|
callback(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
disconnectImplementation(callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
disconnect(callback) {
|
|
if (this._connectionId) {
|
|
this.emptyOutputBuffer();
|
|
this.removeAllListeners();
|
|
|
|
this.disconnectImplementation(result => {
|
|
this.checkChromeLastError();
|
|
|
|
if (result) {
|
|
console.log('Connection with ID: ' + this._connectionId + ' closed, Sent: ' + this._bytesSent + ' bytes, Received: ' + this._bytesReceived + ' bytes');
|
|
} else {
|
|
console.log('Failed to close connection with ID: ' + this._connectionId + ' closed, Sent: ' + this._bytesSent + ' bytes, Received: ' + this._bytesReceived + ' bytes');
|
|
googleAnalytics.sendException('Connection: FailedToClose', false);
|
|
}
|
|
|
|
this._connectionId = false;
|
|
if (callback) {
|
|
callback(result);
|
|
}
|
|
});
|
|
} else {
|
|
this._openCanceled = true;
|
|
}
|
|
}
|
|
|
|
sendImplementation(data, callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
send(data, callback) {
|
|
this._outputBuffer.push({'data': data, 'callback': callback});
|
|
|
|
var send = () => {
|
|
// store inside separate variables in case array gets destroyed
|
|
var data = this._outputBuffer[0].data,
|
|
callback = this._outputBuffer[0].callback;
|
|
|
|
this.sendImplementation(data, sendInfo => {
|
|
// track sent bytes for statistics
|
|
this._bytesSent += sendInfo.bytesSent;
|
|
|
|
// fire callback
|
|
if (callback) {
|
|
callback(sendInfo);
|
|
}
|
|
|
|
// remove data for current transmission form the buffer
|
|
this._outputBuffer.shift();
|
|
|
|
// if there is any data in the queue fire send immediately, otherwise stop trasmitting
|
|
if (this._outputBuffer.length) {
|
|
// keep the buffer withing reasonable limits
|
|
if (this._outputBuffer.length > 100) {
|
|
var counter = 0;
|
|
|
|
while (this._outputBuffer.length > 100) {
|
|
this._outputBuffer.pop();
|
|
counter++;
|
|
}
|
|
|
|
console.log('Send buffer overflowing, dropped: ' + counter + ' entries');
|
|
}
|
|
send();
|
|
} else {
|
|
this._transmitting = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!this._transmitting) {
|
|
this._transmitting = true;
|
|
send();
|
|
}
|
|
}
|
|
|
|
abort() {
|
|
if (GUI.connected_to || GUI.connecting_to) {
|
|
$('a.connect').trigger('click');
|
|
} else {
|
|
this.disconnect();
|
|
}
|
|
}
|
|
|
|
checkChromeLastError() {
|
|
if (chrome.runtime.lastError) {
|
|
console.error(chrome.runtime.lastError.message);
|
|
}
|
|
}
|
|
|
|
addOnReceiveCallback(callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
removeOnReceiveCallback(callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
addOnReceiveListener(callback) {
|
|
this._onReceiveListeners.push(callback);
|
|
this.addOnReceiveCallback(callback)
|
|
}
|
|
|
|
addOnReceiveErrorCallback(callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
removeOnReceiveErrorCallback(callback) {
|
|
throw new TypeError("Abstract method");
|
|
}
|
|
|
|
addOnReceiveErrorListener(callback) {
|
|
this._onReceiveErrorListeners.push(callback);
|
|
this.addOnReceiveErrorCallback(callback)
|
|
}
|
|
|
|
removeAllListeners() {
|
|
this._onReceiveListeners.forEach(listener => this.removeOnReceiveCallback(listener));
|
|
this._onReceiveListeners = [];
|
|
|
|
this._onReceiveErrorListeners.forEach(listener => this.removeOnReceiveErrorCallback(listener));
|
|
this._onReceiveErrorListeners = [];
|
|
}
|
|
|
|
emptyOutputBuffer() {
|
|
this._outputBuffer = [];
|
|
this._transmitting = false;
|
|
}
|
|
|
|
/**
|
|
* Default timeout values
|
|
* @returns {number} [ms]
|
|
*/
|
|
getTimeout() {
|
|
if (this._bitrate >= 57600) {
|
|
return 3000;
|
|
} else {
|
|
return 4000;
|
|
}
|
|
}
|
|
}
|