mirror of
https://github.com/silenty4ng/k5web
synced 2024-11-14 14:33:23 +00:00
354 lines
15 KiB
JavaScript
354 lines
15 KiB
JavaScript
//The different hardware we support + their specific data/configs
|
|
const table = {
|
|
0x0403: {"FTDI": {
|
|
0x6001: "FT232R",
|
|
0x6010: "FT2232H",
|
|
0x6011: "FT4232H",
|
|
0x6014: "FT232H",
|
|
0x6015: "FT231X", // same ID for FT230X, FT231X, FT234XD
|
|
}},
|
|
0x1a86: {"Quinheng": {
|
|
0x7523: "CH340",
|
|
0x5523: "CH341A",
|
|
}},
|
|
0x10c4: {"Silicon Labs": {
|
|
0xea60: "CP210x", // same ID for CP2101, CP2103, CP2104, CP2109
|
|
0xea70: "CP2105",
|
|
0xea71: "CP2108",
|
|
}},
|
|
0x067b: {"Prolific": {
|
|
0x2303: "PL2303"
|
|
}}
|
|
}
|
|
|
|
const config = {
|
|
"DEBUG" : true,
|
|
"DEFAULT_BAUD_RATE" : 38400,
|
|
"BAUD_RATES" : [600,1200,2400,4800,9600,14400,19200,38400,57600,115200,230400], // highest is 300 0000 limited by the BAUD_RATE_MAX_BPS
|
|
//CH34x --> https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c <-- we have used the linux driver and made into a webUSB driver
|
|
// plus --> https://github.com/felHR85/UsbSerial/tree/master/usbserial/src/main/java/com/felhr/usbserial <--
|
|
"CH340": {
|
|
"REQUEST_READ_VERSION": 0x5F,
|
|
"REQUEST_READ_REGISTRY": 0x95,
|
|
"REQUEST_WRITE_REGISTRY": 0x9A,
|
|
"REQUEST_SERIAL_INITIATION": 0xA1,
|
|
"REG_SERIAL": 0xC29C,
|
|
"REG_MODEM_CTRL": 0xA4,
|
|
"REG_MODEM_VALUE_OFF": 0xFF,
|
|
"REG_MODEM_VALUE_ON": 0xDF,
|
|
"REG_MODEM_VALUE_CALL": 0x9F,
|
|
"REG_BAUD_FACTOR": 0x1312,
|
|
"REG_BAUD_OFFSET": 0x0F2C,
|
|
"REG_BAUD_LOW": 0x2518,
|
|
"REG_CONTROL_STATUS": 0x2727,
|
|
"BAUD_RATE": {
|
|
600: {"FACTOR": 0x6481, "OFFSET": 0x76},
|
|
1200: {"FACTOR": 0xB281, "OFFSET": 0x3B},
|
|
2400: {"FACTOR": 0xD981, "OFFSET": 0x1E},
|
|
4800: {"FACTOR": 0x6482, "OFFSET": 0x0F},
|
|
9600: {"FACTOR": 0xB282, "OFFSET": 0x08},
|
|
14400: {"FACTOR": 0xd980, "OFFSET": 0xEB},
|
|
19200: {"FACTOR": 0xD982, "OFFSET": 0x07},
|
|
38400: {"FACTOR": 0x6483, "OFFSET": null},
|
|
57600: {"FACTOR": 0x9883, "OFFSET": null},
|
|
115200: {"FACTOR": 0xCC83, "OFFSET": null},
|
|
230500: {"FACTOR": 0xE683, "OFFSET": null},
|
|
}
|
|
}
|
|
}
|
|
|
|
const serial = {};
|
|
let device = {};
|
|
let port;
|
|
|
|
(function() {
|
|
'use strict';
|
|
serial.getPorts = function() {
|
|
return navigator.usb.getDevices().then(devices => {
|
|
return devices.map(device => new serial.Port(device));
|
|
});
|
|
};
|
|
|
|
serial.requestPort = function() {
|
|
let supportedHardware = [];
|
|
//This one create the filter of hardware based on the hardware table
|
|
Object.keys(table).map(vendorId => {
|
|
Object.keys(table[vendorId]).map(vendorName => {
|
|
Object.keys(table[vendorId][vendorName]).map(productId => {
|
|
supportedHardware.push({
|
|
"vendorId": vendorId,
|
|
"productId": productId
|
|
})
|
|
})
|
|
})});
|
|
//device contains the "device descriptor" (see USB standard), add as a new device to be able to control
|
|
return navigator.usb.requestDevice({ 'filters': supportedHardware }).then(
|
|
device => new serial.Port(device)
|
|
);
|
|
}
|
|
|
|
//set it to the active device..
|
|
serial.Port = function(device) {
|
|
this.device_ = device;
|
|
};
|
|
|
|
//here's the config + read loop is taking place....
|
|
serial.Port.prototype.connect = function() {
|
|
//this is the read loop on whatever port is currently used... it will repeat itself
|
|
let readLoop = () => {
|
|
this.device_.transferIn(this.endpointIn_, this.endpointInPacketSize_).then(result => {
|
|
readLoop();
|
|
if(sessionStorage.getItem('webusb')){
|
|
sessionStorage.setItem('webusb', sessionStorage.getItem('webusb') + ',' + (new Uint8Array(result.data.buffer)).toString())
|
|
}else{
|
|
sessionStorage.setItem('webusb', (new Uint8Array(result.data.buffer)).toString())
|
|
}
|
|
}, error => {
|
|
this.onReceiveError(error);
|
|
});
|
|
};
|
|
|
|
return this.device_.open()
|
|
.then(() => {
|
|
//first we get some GUI stuff populated, we use "device" for that... serial and port are used for the configuration elsewhere
|
|
device.hostName = port.device_.productName;
|
|
device.vendorName = Object.keys(table[port.device_.vendorId])[0];
|
|
device.chip = table[port.device_.vendorId][device.vendorName][port.device_.productId];
|
|
device.serialNumber = port.device_.serialNumber;
|
|
device.manufacturerName = port.device_.manufacturerName;
|
|
//1: we set an configuration (configuration descriptor in the USB standard)
|
|
if (this.device_.configuration === null) {
|
|
return this.device_.selectConfiguration(1);
|
|
}
|
|
})
|
|
.then(() => {
|
|
//2: we set what endpoints for data we will use, we use only "bulk" transfer and thus we parse their addresses
|
|
let configInterfaces = this.device_.configuration.interfaces;
|
|
configInterfaces.forEach(element => {
|
|
element.alternates.forEach(elementalt => {
|
|
if (elementalt.interfaceClass === 0xff) {
|
|
this.interfaceNumber_ = element.interfaceNumber;
|
|
elementalt.endpoints.forEach(elementendpoint => {
|
|
//This part here get the bulk in and out endpoints programmatically
|
|
if (elementendpoint.direction === "out" && elementendpoint.type === "bulk") {
|
|
this.endpointOut_ = elementendpoint.endpointNumber;
|
|
this.endpointOutPacketSize_ = elementendpoint.packetSize;
|
|
}
|
|
if (elementendpoint.direction === "in" && elementendpoint.type === "bulk") {
|
|
this.endpointIn_ = elementendpoint.endpointNumber;
|
|
this.endpointInPacketSize_ = elementendpoint.packetSize;
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
})
|
|
//3: we claim this interface and select the alternative interface
|
|
.then(() => this.device_.claimInterface(this.interfaceNumber_))
|
|
.then(() => this.device_.selectAlternateInterface(this.interfaceNumber_, 0))
|
|
//4: we configure in and out transmissions, based on detected hardware
|
|
.then(() => serial[device.chip](this))
|
|
//5: we start the loop
|
|
.then(() => {
|
|
//console.log(this);
|
|
readLoop();
|
|
})
|
|
};
|
|
|
|
//upon disconnect, what to do
|
|
serial.Port.prototype.disconnect = async function() {
|
|
await serial[device.chip](this).DISCONNECT;
|
|
};
|
|
|
|
//send data, what to do
|
|
serial.Port.prototype.send = function(data) {
|
|
return this.device_.transferOut(this.endpointOut_, data);
|
|
};
|
|
|
|
serial.controlledTransfer = async function (object, direction, type, recipient, request, value = 0, data = new DataView(new ArrayBuffer(0)), index = object.interfaceNumber_) {
|
|
direction = direction.charAt(0).toUpperCase() + direction.slice(1);
|
|
type = type.toLowerCase();
|
|
recipient = recipient.toLowerCase();
|
|
if (data.byteLength === 0 && direction === "In") {
|
|
// we set how many bits we want back for an "in"
|
|
// so set data = 0....N in the call otherwise it will default to 0
|
|
data = 0;
|
|
}
|
|
return await object.device_["controlTransfer" + direction]({
|
|
'requestType': type,
|
|
'recipient': recipient,
|
|
'request': request,
|
|
'value': value,
|
|
'index': index
|
|
}, data)
|
|
.then(res => {
|
|
if (config.DEBUG) {
|
|
//debugger; // remove comment for extra debugging tools
|
|
console.log(res);
|
|
}
|
|
if (res.status !== "ok") {
|
|
let errorRequest = `
|
|
controlTransfer` + direction + `
|
|
'requestType': ` + type + `,
|
|
'recipient': ` + recipient + `,
|
|
'request': 0x` + request.toString(16) + `,
|
|
'value': 0x` + value.toString(16) + `,
|
|
'index': 0x` + index.toString(16) + `
|
|
}`;
|
|
console.warn("error!", errorRequest, data) // add more here
|
|
}
|
|
if (res.data !== undefined && res.data.buffer !== undefined) {
|
|
return res.data.buffer;
|
|
}
|
|
return null;
|
|
});
|
|
};
|
|
|
|
// you can really use any numerical value since JS treat them the same:
|
|
// dec = 15 // dec will be set to 15
|
|
// bin = 0b1111; // bin will be set to 15
|
|
// oct = 0o17; // oct will be set to 15
|
|
// oxx = 017; // oxx will be set to 15
|
|
// hex = 0xF; // hex will be set to 15
|
|
// note: bB oO xX are all valid
|
|
serial.hexToDataView = function (number) {
|
|
if (number === 0) {
|
|
let array = new Uint8Array([0]);
|
|
return new DataView(array.buffer)
|
|
}
|
|
let hexString = number.toString(16);
|
|
// split the string into pairs of octets
|
|
let pairs = hexString.match(/[\dA-F]{2}/gi);
|
|
// convert the octets to integers
|
|
let integers = pairs.map(function(s) {
|
|
return parseInt(s, 16);
|
|
});
|
|
let array = new Uint8Array(integers);
|
|
return new DataView(array.buffer);
|
|
}
|
|
|
|
// you can give this method a string like "00 AA F2 01 23" or "0x00 0xAA 0xF2 0x01 0x23" and it will turn it into a DataView for the webUSB API transfer data
|
|
serial.hexStringArrayToDataView = function (hexString) {
|
|
// remove the leading 0x (if any)
|
|
hexString = hexString.replace(/^0x/, '');
|
|
// split the string into pairs of octets
|
|
let pairs = hexString.split(/ /);
|
|
// convert the octets to integers
|
|
let integers = pairs.map(function(s) {
|
|
return parseInt(s, 16);
|
|
});
|
|
let array = new Uint8Array(integers);
|
|
return new DataView(array.buffer);
|
|
}
|
|
|
|
serial.arrayBufferToHex = function (arrayBuffer) {
|
|
let hex = "0x0" + Array.prototype.map.call(new Uint8Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
|
|
return parseInt(hex);
|
|
}
|
|
|
|
// these are the hardware specific initialization procedures...
|
|
serial["CH340"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
let data = serial.hexToDataView(0); // null data
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_SERIAL_INITIATION, config.CH340.REG_SERIAL, data, 0xB2B9) // first request...
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_ON);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_CALL);
|
|
let r = await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x0706, 2);
|
|
r = serial.arrayBufferToHex(r);
|
|
if (r < 0) {
|
|
// we have an error
|
|
return;
|
|
}
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_FACTOR, data, 0xB282);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_OFFSET, data, 0x0008);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_LOW, data, 0x00C3);
|
|
r = await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x0706, 2);
|
|
r = serial.arrayBufferToHex(r);
|
|
if (r < 0) {
|
|
// we have an error
|
|
return;
|
|
}
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
|
|
await serial["CH340"].setBaudRate(obj, baudRate);
|
|
|
|
// now what? all the control transfers came back "ok"?
|
|
}
|
|
|
|
serial["CH340"].setBaudRate = async function (obj, baudRate) {
|
|
let data = serial.hexToDataView(0);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_FACTOR, data, config.CH340.BAUD_RATE[baudRate].FACTOR);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_OFFSET, data, config.CH340.BAUD_RATE[baudRate].OFFSET);
|
|
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
|
|
}
|
|
|
|
serial["CH340"].DISCONNECT = async function (obj) {
|
|
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_OFF);
|
|
}
|
|
|
|
serial["CP210x"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["CP2105"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["CP2108"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["PL2303"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["FT2232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["FT4232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["FT232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
|
|
serial["FT231X"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
|
|
|
|
}
|
|
})();
|
|
|
|
//GUI function "connect"
|
|
function connect() {
|
|
port.connect().then(() => {
|
|
document.getElementById('editor').value = "connected to: " + device.hostName + "\nvendor name: " + device.vendorName + "\nchip type: " + device.chip;
|
|
port.onReceive = data => {
|
|
console.log(data);
|
|
document.getElementById('output').value += new TextDecoder().decode(data);
|
|
}
|
|
port.onReceiveError = error => {
|
|
//console.error(error);
|
|
port.disconnect();
|
|
};
|
|
});
|
|
}
|
|
|
|
//GUI function "disconnect"
|
|
function disconnect() {
|
|
port.disconnect();
|
|
}
|
|
|
|
//GUI function "send"
|
|
function send(string) {
|
|
console.log("sending to serial:" + string.length);
|
|
if (string.length === 0)
|
|
return;
|
|
console.log("sending to serial: [" + string +"]\n");
|
|
|
|
let data = new TextEncoder('utf-8').encode(string);
|
|
console.log(data);
|
|
if (port) {
|
|
port.send(data);
|
|
}
|
|
}
|