Update: 在线版调整web usb

This commit is contained in:
王立帮
2025-01-20 00:20:29 +08:00
parent 8d52915859
commit eb4fa79529
8 changed files with 333 additions and 3 deletions

View File

@@ -239,6 +239,9 @@ class App extends Component {
id: 'command-burn-btn',
displayText: Msg.Lang['nav.btn.burn'],
preconditionFn: () => {
if (!goog.isElectron && !goog.hasSocketServer && Serial.type !== 'serialport') {
return false;
}
return SELECTED_BOARD?.nav?.burn;
},
callback: () => BU.initBurn(),

View File

@@ -1639,6 +1639,7 @@
"Mixly.Env",
"Mixly.Web.SerialPort",
"Mixly.Web.USB",
"Mixly.Web.USBMini",
"Mixly.Web.HID"
],
"provide": [
@@ -1656,6 +1657,17 @@
"Mixly.Web.SerialPort"
]
},
{
"path": "/web/usb-mini.js",
"require": [
"Mixly.Serial",
"Mixly.Registry",
"Mixly.Web"
],
"provide": [
"Mixly.Web.USBMini"
]
},
{
"path": "/web/usb.js",
"require": [

View File

@@ -603,6 +603,7 @@ BU.uploadByUSB = async (portName) => {
resize: false,
closeBtn: 0,
success: async function (layero, index) {
$('#mixly-loader-btn').hide();
try {
let prevPercent = 0;
await partialFlashing.flashAsync(new BoardId(0x9900), FSWrapper, progress => {
@@ -636,6 +637,10 @@ BU.uploadByUSB = async (portName) => {
}
BU.burning = false;
BU.uploading = false;
},
end: function () {
$('#mixly-loader-btn').css('display', 'inline-block');
$('#mixly-loader-div').css('display', 'none');
}
});
}
@@ -653,7 +658,7 @@ BU.uploadWithAmpy = (portName) => {
const editor = mainWorkspace.getEditorsManager().getActive();
let useBuffer = true, dataLength = 256;
if (BOARD.web.com === 'usb') {
useBuffer = false;
useBuffer = true;
dataLength = 64;
} else if (BOARD.web.com === 'hid') {
useBuffer = true;
@@ -667,6 +672,7 @@ BU.uploadWithAmpy = (portName) => {
resize: false,
closeBtn: 0,
success: async function (layero, index) {
$('#mixly-loader-btn').hide();
const serial = new Serial(portName);
const ampy = new Ampy(serial, useBuffer, dataLength);
const code = editor.getCode();
@@ -727,6 +733,10 @@ BU.uploadWithAmpy = (portName) => {
}
BU.burning = false;
BU.uploading = false;
},
end: function () {
$('#mixly-loader-btn').css('display', 'inline-block');
$('#mixly-loader-div').css('display', 'none');
}
});
}

View File

@@ -14,6 +14,7 @@ const {
class WebHID extends Serial {
static {
this.type = 'hid';
this.portToNameRegistry = new Registry();
this.nameToPortRegistry = new Registry();

View File

@@ -4,6 +4,7 @@ goog.require('Mixly.Config');
goog.require('Mixly.Env');
goog.require('Mixly.Web.SerialPort');
goog.require('Mixly.Web.USB');
goog.require('Mixly.Web.USBMini');
goog.require('Mixly.Web.HID');
goog.provide('Mixly.Web.Serial');
@@ -12,6 +13,7 @@ const { Config, Env, Web } = Mixly;
const {
SerialPort,
USB,
USBMini,
HID
} = Web;
@@ -25,7 +27,11 @@ if (goog.platform() === 'win32' && goog.fullPlatform() !== 'win10') {
} else if (BOARD?.web?.devices?.serial) {
Device = SerialPort;
} else if (BOARD?.web?.devices?.usb) {
Device = USB;
if (['BBC micro:bit', 'Mithon CC'].includes(BOARD.boardType)) {
Device = USB;
} else {
Device = USBMini;
}
}
} else if (goog.platform() === 'android') {
Device = USB;
@@ -33,7 +39,11 @@ if (goog.platform() === 'win32' && goog.fullPlatform() !== 'win10') {
if (BOARD?.web?.devices?.serial) {
Device = SerialPort;
} else if (BOARD?.web?.devices?.usb) {
Device = USB;
if (['BBC micro:bit', 'Mithon CC'].includes(BOARD.boardType)) {
Device = USB;
} else {
Device = USBMini;
}
} else if (BOARD?.web?.devices?.hid) {
Device = HID;
}

View File

@@ -14,6 +14,7 @@ const {
class WebSerialPort extends Serial {
static {
this.type = 'serialport';
this.portToNameRegistry = new Registry();
this.nameToPortRegistry = new Registry();

View File

@@ -0,0 +1,292 @@
goog.loadJs('web', () => {
goog.require('Mixly.Serial');
goog.require('Mixly.Registry');
goog.require('Mixly.Web');
goog.provide('Mixly.Web.USBMini');
const {
Serial,
Registry,
Web
} = Mixly;
class USBMini extends Serial {
static {
this.type = 'usb';
this.portToNameRegistry = new Registry();
this.serialNumberToNameRegistry = new Registry();
this.nameToPortRegistry = new Registry();
this.getConfig = function () {
return Serial.getConfig();
}
this.getSelectedPortName = function () {
return Serial.getSelectedPortName();
}
this.getCurrentPortsName = function () {
return Serial.getCurrentPortsName();
}
this.refreshPorts = function () {
let portsName = [];
for (let name of this.nameToPortRegistry.keys()) {
portsName.push({ name });
}
Serial.renderSelectBox(portsName);
}
this.requestPort = async function () {
const device = await navigator.usb.requestDevice({
filters: []
});
this.addPort(device);
this.refreshPorts();
}
this.getPort = function (name) {
return this.nameToPortRegistry.getItem(name);
}
this.addPort = function (device) {
if (this.portToNameRegistry.hasKey(device)) {
return;
}
const { serialNumber } = device;
let name = this.serialNumberToNameRegistry.getItem(serialNumber);
if (!name) {
for (let i = 1; i <= 20; i++) {
name = `usb${i}`;
if (this.nameToPortRegistry.hasKey(name)) {
continue;
}
break;
}
this.serialNumberToNameRegistry.register(serialNumber, name);
}
this.portToNameRegistry.register(device, name);
this.nameToPortRegistry.register(name, device);
}
this.removePort = function (device) {
if (!this.portToNameRegistry.hasKey(device)) {
return;
}
const name = this.portToNameRegistry.getItem(device);
if (!name) {
return;
}
this.portToNameRegistry.unregister(device);
this.nameToPortRegistry.unregister(name);
}
this.addEventsListener = function () {
navigator?.usb?.addEventListener('connect', (event) => {
this.addPort(event.device);
this.refreshPorts();
});
navigator?.usb?.addEventListener('disconnect', (event) => {
event.device.onclose && event.device.onclose();
this.removePort(event.device);
this.refreshPorts();
});
}
this.init = function () {
navigator?.usb?.getDevices().then((devices) => {
for (let device of devices) {
this.addPort(device);
}
});
this.addEventsListener();
}
}
#device_ = null;
#keepReading_ = null;
#reader_ = null;
#serialPolling_ = false;
#stringTemp_ = '';
#defaultClass_ = 0xFF;
#defaultConfiguration_ = 1;
#endpointIn_ = null;
#endpointOut_ = null;
#interfaceNumber_ = 0;
constructor(port) {
super(port);
}
#addEventsListener_() {
this.#addReadEventListener_();
}
#addReadEventListener_() {
this.#reader_ = this.#startSerialRead_();
this.#device_.onclose = () => {
if (!this.isOpened()) {
return;
}
super.close();
this.#stringTemp_ = '';
this.onClose(1);
}
}
async #read_() {
let result;
if (this.#endpointIn_) {
result = await this.#device_.transferIn(this.#endpointIn_, 64);
} else {
result = await this.#device_.controlTransferIn({
requestType: 'class',
recipient: 'interface',
request: 0x01,
value: 0x100,
index: this.#interfaceNumber_
}, 64);
}
return result?.data;
}
async #write_(data) {
if (this.#endpointOut_) {
await this.#device_.transferOut(this.#endpointOut_, data);
} else {
await this.#device_.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x09,
value: 0x200,
index: this.#interfaceNumber_
}, data);
}
}
async #startSerialRead_(serialDelay = 1) {
this.#serialPolling_ = true;
try {
while (this.#serialPolling_ ) {
const data = await this.#read_();
if (data !== undefined) {
const numberArray = Array.prototype.slice.call(new Uint8Array(data.buffer));
this.onBuffer(numberArray);
}
await new Promise(resolve => setTimeout(resolve, serialDelay));
}
} catch (_) {}
}
async open(baud) {
const portsName = Serial.getCurrentPortsName();
const currentPortName = this.getPortName();
if (!portsName.includes(currentPortName)) {
throw new Error('无可用串口');
}
if (this.isOpened()) {
return;
}
baud = baud ?? this.getBaudRate();
this.#device_ = USBMini.getPort(currentPortName);
await this.#device_.open();
await this.#device_.selectConfiguration(this.#defaultConfiguration_);
const interfaces = this.#device_.configuration.interfaces.filter(iface => {
return iface.alternates[0].interfaceClass === this.#defaultClass_;
});
let selectedInterface = interfaces.find(iface => iface.alternates[0].endpoints.length > 0);
if (!selectedInterface) {
selectedInterface = interfaces[0];
}
this.#interfaceNumber_ = selectedInterface.interfaceNumber;
const { endpoints } = selectedInterface.alternates[0];
for (const endpoint of endpoints) {
if (endpoint.direction === 'in') {
this.#endpointIn_ = endpoint.endpointNumber;
} else if (endpoint.direction === 'out') {
this.#endpointOut_ = endpoint.endpointNumber;
}
}
await this.#device_.claimInterface(this.#interfaceNumber_);
await this.setBaudRate(baud);
super.open(baud);
this.onOpen();
this.#addEventsListener_();
}
async close() {
if (!this.isOpened()) {
return;
}
this.#serialPolling_ = false;
super.close();
await this.#device_.close();
if (this.#reader_) {
await this.#reader_;
}
this.#device_ = null;
this.onClose(1);
}
async setBaudRate(baud) {
if (!this.isOpened() || this.getBaudRate() === baud) {
return;
}
await super.setBaudRate(baud);
}
async sendString(str) {
const buffer = this.encode(str);
return this.sendBuffer(buffer);
}
async sendBuffer(buffer) {
if (typeof buffer.unshift === 'function') {
// buffer.unshift(buffer.length);
buffer = new Uint8Array(buffer).buffer;
}
return this.#write_(buffer);
}
async setDTRAndRTS(dtr, rts) {
if (!this.isOpened()
|| (this.getDTR() === dtr && this.getRTS() === rts)) {
return;
}
await super.setDTRAndRTS(dtr, rts);
}
async setDTR(dtr) {
return this.setDTRAndRTS(dtr, this.getRTS());
}
async setRTS(rts) {
return this.setDTRAndRTS(this.getDTR(), rts);
}
onBuffer(buffer) {
super.onBuffer(buffer);
for (let i = 0; i < buffer.length; i++) {
super.onByte(buffer[i]);
}
const string = this.decodeBuffer(buffer);
if (!string) {
return;
}
for (let char of string) {
super.onChar(char);
if (['\r', '\n'].includes(char)) {
super.onString(this.#stringTemp_);
this.#stringTemp_ = '';
} else {
this.#stringTemp_ += char;
}
}
}
}
Web.USBMini = USBMini;
});

View File

@@ -14,6 +14,7 @@ const {
class USB extends Serial {
static {
this.type = 'usb';
this.portToNameRegistry = new Registry();
this.serialNumberToNameRegistry = new Registry();
this.nameToPortRegistry = new Registry();