feat: force web usb

This commit is contained in:
2026-02-11 18:32:06 +01:00
parent 5e4283a462
commit dee754c015
5 changed files with 119 additions and 86 deletions

View File

@@ -12,7 +12,7 @@
themeFromSourceColor,
} from "@material/material-color-utilities";
import { canAutoConnect, getViablePorts } from "$lib/serial/device";
import { initSerial } from "$lib/serial/connection";
import { initSerial, serialObject } from "$lib/serial/connection";
import type { LayoutData } from "./$types";
import { browser } from "$app/environment";
import "tippy.js/animations/shift-away.css";
@@ -74,8 +74,12 @@
webManifestLink = await initPwa();
}
if (browser && $userPreferences.autoConnect && (await canAutoConnect())) {
const [port] = await getViablePorts();
if (
browser &&
$userPreferences.autoConnect &&
(await canAutoConnect($serialObject))
) {
const [port] = await getViablePorts($serialObject);
await initSerial(port!, true);
}

View File

@@ -1,21 +1,27 @@
<script lang="ts">
import LL from "$i18n/i18n-svelte";
import { preference, userPreferences } from "$lib/preferences";
import { initSerial } from "$lib/serial/connection";
import {
forceWebUSB,
initSerial,
serialObject,
} from "$lib/serial/connection";
import {
getPortName,
PORT_FILTERS,
type SerialPortLike,
} from "$lib/serial/device";
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
import { onMount } from "svelte";
import { persistentWritable } from "$lib/storage";
import { browser } from "$app/environment";
let ports = $state<SerialPort[]>([]);
let element: HTMLDivElement | undefined = $state();
let supportsWebSerial = browser && "serial" in navigator;
let supportsWebUSB = browser && "usb" in navigator;
onMount(() => {
refreshPorts();
$effect(() => {
refreshPorts($serialObject);
});
let hasDiscoveredAutoConnect = persistentWritable(
@@ -29,8 +35,8 @@
}
});
async function refreshPorts() {
ports = await navigator.serial.getPorts();
async function refreshPorts(serial: Serial) {
ports = await serial.getPorts();
}
async function connect(port: SerialPortLike, withSync: boolean) {
@@ -46,13 +52,13 @@
element?.closest<HTMLElement>("[popover]")?.hidePopover();
}
async function connectDevice(event: MouseEvent) {
const port = await navigator.serial.requestPort({
async function connectDevice(event: MouseEvent, serial: Serial) {
const port = await serial.requestPort({
filters: event.shiftKey ? [] : [...PORT_FILTERS.values()],
});
if (!port) return;
closePopover();
refreshPorts();
refreshPorts(serial);
connect(port, true);
}
</script>
@@ -60,55 +66,79 @@
<div
bind:this={element}
class="device-list"
onmouseenter={() => refreshPorts()}
onmouseenter={() => refreshPorts($serialObject)}
role="region"
>
{#if ports.length === 1}
{#if supportsWebSerial || supportsWebUSB}
<fieldset class:promote={!$hasDiscoveredAutoConnect}>
<label
><input type="checkbox" use:preference={"autoConnect"} />
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
</label>
{#if ports.length === 1}
<label
><input type="checkbox" use:preference={"autoConnect"} />
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
</label>
<label
><input type="checkbox" use:preference={"backup"} />
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
<label
><input type="checkbox" use:preference={"backup"} />
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
</label>
{/if}
<label title="You can try this if you have trouble with the connection."
><input
type="checkbox"
disabled={!supportsWebSerial}
checked={!supportsWebSerial || $forceWebUSB}
onchange={(event) => {
$forceWebUSB = (event.target as HTMLInputElement).checked;
}}
/>
<div class="title">WebUSB Fallback</div>
</label>
</fieldset>
{/if}
{#if ports.length !== 0}
<h4>Recent Devices</h4>
<div class="devices">
<!--
{#if ports.length !== 0}
<h4>Recent Devices</h4>
<div class="devices">
<!--
<div class="device">
<button onclick={connectCC0}> CC0</button>
</div>-->
{#each ports as port}
<div class="device">
<button
onclick={(event) => {
connect(port, !event.shiftKey);
}}
>
{getPortName(port)}</button
>
<button
class="error"
onclick={() => {
port.forget();
refreshPorts();
}}><span class="icon">visibility_off</span> Hide</button
>
</div>
{/each}
{#each ports as port}
<div class="device">
<button
onclick={(event) => {
connect(port, !event.shiftKey);
}}
>
{getPortName(port)}</button
>
<button
class="error"
onclick={() => {
port.forget();
refreshPorts($serialObject);
}}><span class="icon">visibility_off</span> Hide</button
>
</div>
{/each}
</div>
{/if}
<div class="pair">
<button
onclick={(event) => connectDevice(event, $serialObject)}
class="primary"><span class="icon">add</span>Connect</button
>
<!--<a href="/ccos/zero_wasm/"><span class="icon">add</span>Virtual Device</a>-->
</div>
{#if !supportsWebSerial}
<p>Browser with limited support detected.</p>
{/if}
{:else}
<p><b>Your browser is missing support for critical features.</b></p>
<p>
Please use a Chromium-based browser such as Chrome, Edge or Chromium
instead.
</p>
{/if}
<div class="pair">
<button onclick={connectDevice} class="primary"
><span class="icon">add</span>Connect</button
>
<!--<a href="/ccos/zero_wasm/"><span class="icon">add</span>Virtual Device</a>-->
</div>
</div>
<style lang="scss">

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { downloadBackup } from "$lib/backup/backup";
import { initSerial, serialPort } from "$lib/serial/connection";
import { initSerial, serialObject, serialPort } from "$lib/serial/connection";
import { fade, slide } from "svelte/transition";
import { lt as semverLt } from "semver";
import type { LoaderOptions, ESPLoader } from "esptool-js";
@@ -95,9 +95,9 @@
}
}
async function connect() {
async function connect(serial: Serial) {
try {
const port = await navigator.serial.requestPort();
const port = await serial.requestPort();
await initSerial(port!, true);
step = 1;
} catch (e) {
@@ -138,9 +138,9 @@
step = 4;
}
async function espBootloader() {
async function espBootloader(serial: Serial) {
$serialPort?.forget();
const port = await navigator.serial.requestPort();
const port = await serial.requestPort();
port.open({ baudRate: 1200 });
}
@@ -172,8 +172,8 @@
return espLoader;
}
async function flashImages() {
const port = await navigator.serial.requestPort();
async function flashImages(serial: Serial) {
const port = await serial.requestPort();
try {
const esptool = data.meta.update.esptool!;
espLoader = await connectEsp(port);
@@ -202,8 +202,8 @@
}
}
async function eraseSPI() {
const port = await navigator.serial.requestPort();
async function eraseSPI(serial: Serial) {
const port = await serial.requestPort();
try {
console.log(data.meta);
const spiFlash = data.meta.spiFlash!;
@@ -314,7 +314,7 @@
<section>
<ol>
<li>
<button class="inline-button" onclick={connect}
<button class="inline-button" onclick={() => connect($serialObject)}
><span class="icon">usb</span>Connect</button
>
your device
@@ -367,17 +367,17 @@
</p>
<div class="esp-buttons">
<button onclick={espBootloader}
<button onclick={() => espBootloader($serialObject)}
><span class="icon">memory</span>ESP Bootloader</button
>
<button onclick={flashImages}
<button onclick={() => flashImages($serialObject)}
><span class="icon">developer_board</span>Flash Images</button
>
<label
><input type="checkbox" id="erase" bind:checked={eraseAll} />Erase
All</label
>
<button onclick={eraseSPI}
<button onclick={() => eraseSPI($serialObject)}
><span class="icon">developer_board</span>Erase SPI Flash</button
>
</div>