1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-24 00:35:26 +03:00
betaflight-configurator/src/js/cordova_chromeapi.js
Mark Haslinghuis 545ec324c0 Fix tcp boot
2020-11-17 05:20:49 +01:00

406 lines
16 KiB
JavaScript

'use strict';
const chromeCallbackWithError = function(message, callback) {
let err;
if (typeof message === 'string') {
err = { 'message' : message };
} else {
err = message;
}
if (typeof callback !== 'function') {
console.error(err.message);
return;
}
try {
if (typeof chrome.runtime !== 'undefined') {
chrome.runtime.lastError = err;
} else {
console.error(err.message);
}
callback.apply(null, Array.prototype.slice.call(arguments, 2));
} finally {
if (typeof chrome.runtime !== 'undefined') {
delete chrome.runtime.lastError;
}
}
};
const chromeCallbackWithSuccess = function(argument, callback) {
if (typeof callback === 'function') {
if (typeof argument === 'undefined') {
callback();
} else {
callback(argument);
}
}
};
const removeItemOfAnArray = async function (array, item) {
for (let i = (array.length - 1); i >= 0; i--) {
if (array[i] === item) {
return array.splice(i, 1);
}
}
return array;
};
const chromeapiSerial = {
logHeader: 'SERIAL (adapted from Cordova): ',
connection: {
connectionId: 1, // Only one connection possible
paused: false,
persistent: false,
name,
bufferSize: 4096,
receiveTimeout: 0,
sendTimeout: 0,
bitrate: 9600,
dataBits: 'eight',
parityBit: 'no',
stopBits: 'one',
ctsFlowControl: false,
},
setConnectionOptions: function(ConnectionOptions) {
if (ConnectionOptions.persistent) {
this.connection.persistent = ConnectionOptions.persistent;
}
if (ConnectionOptions.name) {
this.connection.name = ConnectionOptions.name;
}
if (ConnectionOptions.bufferSize) {
this.connection.bufferSize = ConnectionOptions.bufferSize;
}
if (ConnectionOptions.receiveTimeout) {
this.connection.receiveTimeout = ConnectionOptions.receiveTimeout;
}
if (ConnectionOptions.sendTimeout) {
this.connection.sendTimeout = ConnectionOptions.sendTimeout;
}
if (ConnectionOptions.bitrate) {
this.connection.bitrate = ConnectionOptions.bitrate;
}
if (ConnectionOptions.dataBits) {
this.connection.dataBits = ConnectionOptions.dataBits;
}
if (ConnectionOptions.parityBit) {
this.connection.parityBit = ConnectionOptions.parityBit;
}
if (ConnectionOptions.stopBits) {
this.connection.stopBits = ConnectionOptions.stopBits;
}
if (ConnectionOptions.ctsFlowControl) {
this.connection.ctsFlowControl = ConnectionOptions.ctsFlowControl;
}
},
getCordovaSerialConnectionOptions: function() {
let dataBits, stopBits, parityBit;
if (this.connection.dataBits === 'seven') {
dataBits = 7;
} else {
dataBits = 8;
}
if (this.connection.stopBits === 'two') {
stopBits = 2;
} else {
stopBits = 1;
}
if (this.connection.parityBit === 'odd') {
parityBit = 0;
} else if (this.connection.parityBit === 'even') {
parityBit = 1;
}
return {
baudRate: this.connection.bitrate,
dataBits: dataBits,
stopBits: stopBits,
parity: parityBit,
sleepOnPause: false,
};
},
// Chrome serial API methods
getDevices: async function(callback) {
const self = this;
cordova.plugins.usbevent.listDevices(function(list) {
const devices = [];
if (list.devices !== undefined) {
let count = 0;
list.devices.forEach(device => {
count++;
devices.push({
path: `${device.vendorId}/${device.productId}`,
vendorId: device.vendorId,
productId: device.productId,
displayName: `${device.vendorId}/${device.productId}`,
});
if (count === list.devices.length) {
if (callback) {
callback(devices);
}
}
});
} else {
if (callback) {
callback(devices);
}
}
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
},
connect: function(path, ConnectionOptions, callback) {
const self = this;
if (typeof ConnectionOptions !== 'undefined') {
self.setConnectionOptions(ConnectionOptions);
}
const pathSplit = path.split('/');
if (pathSplit.length === 2) {
const vid = parseInt(pathSplit[0]);
const pid = parseInt(pathSplit[1]);
console.log(`${self.logHeader}request permission (vid=${vid} / pid=${pid})`);
cordova_serial.requestPermission({vid: vid, pid: pid}, function() {
const options = self.getCordovaSerialConnectionOptions();
cordova_serial.open(options, function () {
cordova_serial.registerReadCallback(function (data) {
const info = {
connectionId: self.connection.connectionId,
data: data,
};
self.onReceive.receiveData(info);
}, function () {
console.warn(`${self.logHeader}failed to register read callback`);
});
chromeCallbackWithSuccess(self.connection, callback);
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
} else {
chromeCallbackWithError(`${self.logHeader} invalid vendor id / product id`, callback);
}
},
disconnect: function(connectionId, callback) {
const self = this;
cordova_serial.close(function () {
chromeCallbackWithSuccess(true, callback);
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback(false));
});
},
setPaused: function(connectionId, paused, callback) {
this.connection.paused = paused; // Change connectionInfo but don't pause the connection
chromeCallbackWithSuccess(undefined, callback);
},
getInfo: function(callback) {
chromeCallbackWithSuccess(this.connection, callback);
},
send: function(connectionId, data, callback) {
const string = Array.prototype.map.call(new Uint8Array(data), x => (`00${x.toString(16)}`).slice(-2)).join('');
cordova_serial.writeHex(string, function () {
chromeCallbackWithSuccess({
bytesSent: string.length >> 1,
}, callback);
}, function(error) {
const info = {
bytesSent: 0,
error: 'undefined',
};
chrome.serial.onReceiveError.receiveError(info);
chromeCallbackWithError(`SERIAL (adapted from Cordova): ${error}`, callback(info));
});
},
// update: function() { },
// getConnections: function() { },
// flush: function() { },
// setBreak: function() { },
// clearBreak: function() { },
onReceive: {
listeners: [],
addListener: function(functionReference) {
this.listeners.push(functionReference);
},
removeListener: async function(functionReference) {
this.listeners = await removeItemOfAnArray(this.listeners, functionReference);
},
receiveData: function(data) {
if (data.data.byteLength > 0) {
for (let i = (this.listeners.length - 1); i >= 0; i--) {
this.listeners[i](data);
}
}
},
},
onReceiveError: {
listeners: [],
addListener: function(functionReference) {
this.listeners.push(functionReference);
},
removeListener: async function(functionReference) {
this.listeners = await removeItemOfAnArray(this.listeners, functionReference);
},
receiveError: function(error) {
for (let i = (this.listeners.length - 1); i >= 0; i--) {
this.listeners[i](error);
}
},
},
};
const chromeapiFilesystem = {
logHeader: 'FILESYSTEM (adapted from Cordova): ',
savedEntries: [],
getFileExtension: function(fileName) {
const re = /(?:\.([^.]+))?$/;
return re.exec(fileName)[1];
},
// Chrome fileSystem API methods
getDisplayPath: function(entry, callback) {
chromeCallbackWithSuccess(entry.fullPath, callback);
},
getWritableEntry: function(entry, callback) {
// Entry returned by chooseEntry method is writable on Android
chromeCallbackWithSuccess(entry, callback);
},
isWritableEntry: function(entry, callback) {
// Entry returned by chooseEntry method is writable on Android
chromeCallbackWithSuccess(true, callback);
},
chooseEntryOpenFile: function(options, callback) {
const self = this;
fileChooser.open(function(uri) {
window.resolveLocalFileSystemURL(uri, function(entry) {
if (options.accepts && options.accepts[0].extensions && options.accepts[0].extensions && options.accepts[0].extensions.length > 0) {
self.getDisplayPath(entry, function(fileName) {
const extension = self.getFileExtension(fileName);
if (options.accepts[0].extensions.indexOf(extension) > -1) {
chromeCallbackWithSuccess(entry, callback);
} else {
navigator.notification.alert('Invalid file extension', function() {
chromeCallbackWithError(`${self.logHeader}file opened has an incorrect extension`, callback);
}, 'Choose a file', 'Ok');
}
});
} else {
console.log('no extensions : any type of file accepted');
chromeCallbackWithSuccess(entry, callback);
}
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
},
chooseEntrySaveFile: function(options, callback) {
const self = this;
if (!options.suggestedName) {
options.suggestedName = 'newfile';
}
const extension = self.getFileExtension(options.suggestedName);
const folder = 'Betaflight configurator';
navigator.notification.prompt(i18n.getMessage('dialogFileNameDescription', {
folder: folder,
}), function(res) {
if (res.buttonIndex === 1) {
const newExtension = self.getFileExtension(res.input1);
let fileName = res.input1;
if (newExtension === undefined) {
fileName += `.${extension}`;
}
window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory, function(rootEntry) {
rootEntry.getDirectory(folder, { create: true }, function(directoryEntry) {
directoryEntry.getFile(fileName, { create: false }, function(fileEntry) {
console.log(fileEntry);
navigator.notification.confirm(i18n.getMessage('dialogFileAlreadyExistsDescription'), function(resp) {
if (resp === 1) {
chromeCallbackWithSuccess(fileEntry, callback);
} else {
chromeCallbackWithError(`${self.logHeader}Canceled: file already exists`, callback);
}
}, i18n.getMessage('dialogFileAlreadyExistsTitle'), [i18n.getMessage('yes'), i18n.getMessage('cancel')]);
}, function() {
directoryEntry.getFile(fileName, { create: true }, function(fileEntry) {
chromeCallbackWithSuccess(fileEntry, callback);
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
});
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
}, function(error) {
chromeCallbackWithError(self.logHeader+error, callback);
});
} else {
chromeCallbackWithError(`${self.logHeader}Canceled: no file name`, callback);
}
}, i18n.getMessage('dialogFileNameTitle'), [i18n.getMessage('initialSetupButtonSave'), i18n.getMessage('cancel')], options.suggestedName);
},
chooseEntry: function(options, callback) {
const self = this;
if (typeof options === 'undefined' || typeof options.type === 'undefined') {
self.chooseEntryOpenFile(options, callback);
} else if (options.type === 'openDirectory') {
// not supported yet
console.warn('chrome.fileSystem.chooseEntry: options.type = openDirectory not supported yet');
chromeCallbackWithSuccess(undefined, callback);
} else if (options.type === 'openWritableFile') {
// Entry returned by chooseEntry method is writable on Android
self.chooseEntryOpenFile(options, callback);
} else if (options.type === 'saveFile') {
self.chooseEntrySaveFile(options, callback);
} else {
self.chooseEntryOpenFile(options, callback);
}
},
restoreEntry: function(id, callback) {
this.isRestorable(id, function(isRestorable) {
if (isRestorable) {
chromeCallbackWithSuccess(this.savedEntries[id], callback);
} else {
chromeCallbackWithError(`${self.logHeader}This entry can't be restored`, callback);
}
});
},
isRestorable: function(id, callback) {
if (typeof this.savedEntries[id] !== 'undefined') {
chromeCallbackWithSuccess(true, callback);
} else {
chromeCallbackWithSuccess(false, callback);
}
},
retainEntry: function(entry) {
const id = this.savedEntries.length;
if (id >= 500) {
for (let i=0 ; i<500 ; i++) {
if (i < 499) {
this.savedEntries[i] = this.savedEntries[i+1];
} else {
this.savedEntries[i] = entry;
}
}
return 499;
} else {
this.savedEntries[id] = entry;
return id;
}
},
/**requestFileSystem: function(options, callback) { },
getVolumeList: function(callback) { },*/
};
const cordovaChromeapi = {
init: function(callback) {
chrome.serial = chromeapiSerial;
chrome.fileSystem = chromeapiFilesystem;
if (callback) {
callback();
}
},
};