webserial-flasher 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/autoDetect.d.ts +24 -0
- package/dist/autoDetect.d.ts.map +1 -0
- package/dist/autoDetect.js +66 -0
- package/dist/autoDetect.js.map +1 -0
- package/dist/boards/database.d.ts +17 -0
- package/dist/boards/database.d.ts.map +1 -0
- package/dist/boards/database.js +957 -0
- package/dist/boards/database.js.map +1 -0
- package/dist/core/constants.d.ts +44 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +56 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/errors.d.ts +45 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +92 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/types.d.ts +138 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/avr109/programmer.d.ts +78 -0
- package/dist/protocol/avr109/programmer.d.ts.map +1 -0
- package/dist/protocol/avr109/programmer.js +324 -0
- package/dist/protocol/avr109/programmer.js.map +1 -0
- package/dist/protocol/hexParser.d.ts +12 -0
- package/dist/protocol/hexParser.d.ts.map +1 -0
- package/dist/protocol/hexParser.js +133 -0
- package/dist/protocol/hexParser.js.map +1 -0
- package/dist/protocol/picoboot/constants.d.ts +65 -0
- package/dist/protocol/picoboot/constants.d.ts.map +1 -0
- package/dist/protocol/picoboot/constants.js +80 -0
- package/dist/protocol/picoboot/constants.js.map +1 -0
- package/dist/protocol/picoboot/programmer.d.ts +73 -0
- package/dist/protocol/picoboot/programmer.d.ts.map +1 -0
- package/dist/protocol/picoboot/programmer.js +278 -0
- package/dist/protocol/picoboot/programmer.js.map +1 -0
- package/dist/protocol/picoboot/uf2.d.ts +51 -0
- package/dist/protocol/picoboot/uf2.d.ts.map +1 -0
- package/dist/protocol/picoboot/uf2.js +119 -0
- package/dist/protocol/picoboot/uf2.js.map +1 -0
- package/dist/protocol/receiveData.d.ts +3 -0
- package/dist/protocol/receiveData.d.ts.map +1 -0
- package/dist/protocol/receiveData.js +72 -0
- package/dist/protocol/receiveData.js.map +1 -0
- package/dist/protocol/sendCommand.d.ts +14 -0
- package/dist/protocol/sendCommand.d.ts.map +1 -0
- package/dist/protocol/sendCommand.js +48 -0
- package/dist/protocol/sendCommand.js.map +1 -0
- package/dist/protocol/stk500v2/constants.d.ts +57 -0
- package/dist/protocol/stk500v2/constants.d.ts.map +1 -0
- package/dist/protocol/stk500v2/constants.js +62 -0
- package/dist/protocol/stk500v2/constants.js.map +1 -0
- package/dist/protocol/stk500v2/frame.d.ts +14 -0
- package/dist/protocol/stk500v2/frame.d.ts.map +1 -0
- package/dist/protocol/stk500v2/frame.js +116 -0
- package/dist/protocol/stk500v2/frame.js.map +1 -0
- package/dist/protocol/stk500v2/programmer.d.ts +92 -0
- package/dist/protocol/stk500v2/programmer.d.ts.map +1 -0
- package/dist/protocol/stk500v2/programmer.js +482 -0
- package/dist/protocol/stk500v2/programmer.js.map +1 -0
- package/dist/protocol/updi/constants.d.ts +107 -0
- package/dist/protocol/updi/constants.d.ts.map +1 -0
- package/dist/protocol/updi/constants.js +130 -0
- package/dist/protocol/updi/constants.js.map +1 -0
- package/dist/protocol/updi/link.d.ts +82 -0
- package/dist/protocol/updi/link.d.ts.map +1 -0
- package/dist/protocol/updi/link.js +241 -0
- package/dist/protocol/updi/link.js.map +1 -0
- package/dist/protocol/updi/programmer.d.ts +89 -0
- package/dist/protocol/updi/programmer.d.ts.map +1 -0
- package/dist/protocol/updi/programmer.js +359 -0
- package/dist/protocol/updi/programmer.js.map +1 -0
- package/dist/stk500.d.ts +101 -0
- package/dist/stk500.d.ts.map +1 -0
- package/dist/stk500.js +426 -0
- package/dist/stk500.js.map +1 -0
- package/dist/transport/IPicobootTransport.d.ts +25 -0
- package/dist/transport/IPicobootTransport.d.ts.map +1 -0
- package/dist/transport/IPicobootTransport.js +10 -0
- package/dist/transport/IPicobootTransport.js.map +1 -0
- package/dist/transport/ITransport.d.ts +33 -0
- package/dist/transport/ITransport.d.ts.map +1 -0
- package/dist/transport/ITransport.js +4 -0
- package/dist/transport/ITransport.js.map +1 -0
- package/dist/transport/NodeSerialTransport.d.ts +35 -0
- package/dist/transport/NodeSerialTransport.d.ts.map +1 -0
- package/dist/transport/NodeSerialTransport.js +102 -0
- package/dist/transport/NodeSerialTransport.js.map +1 -0
- package/dist/transport/NodeUSBTransport.d.ts +24 -0
- package/dist/transport/NodeUSBTransport.d.ts.map +1 -0
- package/dist/transport/NodeUSBTransport.js +146 -0
- package/dist/transport/NodeUSBTransport.js.map +1 -0
- package/dist/transport/WebSerialTransport.d.ts +63 -0
- package/dist/transport/WebSerialTransport.d.ts.map +1 -0
- package/dist/transport/WebSerialTransport.js +159 -0
- package/dist/transport/WebSerialTransport.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Node.js transport adapter for webserial-flasher.
|
|
2
|
+
// Works with the `serialport` npm package (https://serialport.io).
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// import { SerialPort } from 'serialport';
|
|
6
|
+
// const port = new SerialPort({ path: '/dev/ttyACM0', baudRate: 115200, autoOpen: false });
|
|
7
|
+
// await port.open(); // or pass openImmediately:true
|
|
8
|
+
// const transport = new NodeSerialTransport(port);
|
|
9
|
+
// const stk = new STK500(transport, board);
|
|
10
|
+
// await stk.bootload(hex);
|
|
11
|
+
// await transport.close();
|
|
12
|
+
export class NodeSerialTransport {
|
|
13
|
+
constructor(port) {
|
|
14
|
+
this.port = port;
|
|
15
|
+
// Map user handlers → wrapped handlers so removeListener works correctly
|
|
16
|
+
this.handlerMap = new Map();
|
|
17
|
+
}
|
|
18
|
+
async write(data) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
this.port.write(data, (err) => {
|
|
21
|
+
if (err)
|
|
22
|
+
reject(err);
|
|
23
|
+
else
|
|
24
|
+
resolve();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
on(_event, handler) {
|
|
29
|
+
const wrapped = (buf) => handler(new Uint8Array(buf));
|
|
30
|
+
this.handlerMap.set(handler, wrapped);
|
|
31
|
+
this.port.on('data', wrapped);
|
|
32
|
+
}
|
|
33
|
+
off(_event, handler) {
|
|
34
|
+
const wrapped = this.handlerMap.get(handler);
|
|
35
|
+
if (wrapped) {
|
|
36
|
+
this.port.removeListener('data', wrapped);
|
|
37
|
+
this.handlerMap.delete(handler);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async setSignals(opts) {
|
|
41
|
+
if (!this.port.set)
|
|
42
|
+
return; // Port implementation doesn't support signal control
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
this.port.set(opts, (err) => {
|
|
45
|
+
if (err)
|
|
46
|
+
reject(err);
|
|
47
|
+
else
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Send a BREAK condition for UPDI.
|
|
54
|
+
*
|
|
55
|
+
* Uses hardware SET{brk} if the port supports it (serialport >= 10).
|
|
56
|
+
* Holds TX low for ~12 ms (≥ 12 bit-times at any supported baud rate).
|
|
57
|
+
*/
|
|
58
|
+
async sendBreak() {
|
|
59
|
+
if (!this.port.set) {
|
|
60
|
+
throw new Error('NodeSerialTransport: sendBreak() requires port.set() ' +
|
|
61
|
+
'(not available on this port implementation)');
|
|
62
|
+
}
|
|
63
|
+
await new Promise((resolve, reject) => {
|
|
64
|
+
this.port.set({ brk: true }, (err) => {
|
|
65
|
+
if (err)
|
|
66
|
+
reject(err);
|
|
67
|
+
else
|
|
68
|
+
resolve();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
await new Promise((r) => setTimeout(r, 12)); // ≥12 ms = ≥12 bit-times @ 1 Mbaud
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
this.port.set({ brk: false }, (err) => {
|
|
74
|
+
if (err)
|
|
75
|
+
reject(err);
|
|
76
|
+
else
|
|
77
|
+
resolve();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
await new Promise((r) => setTimeout(r, 2)); // settling
|
|
81
|
+
}
|
|
82
|
+
async close() {
|
|
83
|
+
this.handlerMap.clear();
|
|
84
|
+
if (this.port.drain) {
|
|
85
|
+
await new Promise((resolve) => {
|
|
86
|
+
this.port.drain((err) => {
|
|
87
|
+
if (err) { /* ignore drain errors on close */ }
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (this.port.close) {
|
|
93
|
+
await new Promise((resolve) => {
|
|
94
|
+
this.port.close((err) => {
|
|
95
|
+
if (err) { /* ignore */ }
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=NodeSerialTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSerialTransport.js","sourceRoot":"","sources":["../../src/transport/NodeSerialTransport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IPicobootTransport } from './IPicobootTransport.js';
|
|
2
|
+
export declare class NodeUSBTransport implements IPicobootTransport {
|
|
3
|
+
private readonly device;
|
|
4
|
+
private readonly iface;
|
|
5
|
+
private readonly outEp;
|
|
6
|
+
private readonly inEp;
|
|
7
|
+
private constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Find the first RP2040 / RP2350 in BOOTSEL mode, claim the PICOBOOT
|
|
10
|
+
* interface, and return a ready-to-use transport instance.
|
|
11
|
+
*
|
|
12
|
+
* @param vid USB Vendor ID (default: 0x2E8A — Raspberry Pi)
|
|
13
|
+
* @param pid USB Product ID (default: 0x0003 — RP2040 USBBOOT)
|
|
14
|
+
* @throws STK500ProtocolError if the 'usb' package is missing or no device found
|
|
15
|
+
*/
|
|
16
|
+
static open(vid?: number, pid?: number): Promise<NodeUSBTransport>;
|
|
17
|
+
sendCommand(cmd: Uint8Array): Promise<void>;
|
|
18
|
+
receiveBytes(maxLength: number): Promise<Uint8Array>;
|
|
19
|
+
sendBytes(data: Uint8Array): Promise<void>;
|
|
20
|
+
resetInterface(): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
private _transferOut;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=NodeUSBTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeUSBTransport.d.ts","sourceRoot":"","sources":["../../src/transport/NodeUSBTransport.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,qBAAa,gBAAiB,YAAW,kBAAkB;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAElC,OAAO;IASP;;;;;;;OAOG;WACU,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoElE,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAWpD,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,OAAO,CAAC,YAAY;CAQrB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Node.js USB transport for the PICOBOOT protocol.
|
|
2
|
+
// Requires the 'usb' npm package: npm install usb
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// const transport = await NodeUSBTransport.open();
|
|
6
|
+
// const pico = new PicoBoot(transport);
|
|
7
|
+
// await pico.bootload(uf2Data);
|
|
8
|
+
// await transport.close();
|
|
9
|
+
import { PICOBOOT_INTERFACE } from '../protocol/picoboot/constants.js';
|
|
10
|
+
import { STK500ProtocolError } from '../core/errors.js';
|
|
11
|
+
export class NodeUSBTransport {
|
|
12
|
+
constructor(device, iface, outEp, inEp) {
|
|
13
|
+
this.device = device;
|
|
14
|
+
this.iface = iface;
|
|
15
|
+
this.outEp = outEp;
|
|
16
|
+
this.inEp = inEp;
|
|
17
|
+
}
|
|
18
|
+
// ── Factory ────────────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Find the first RP2040 / RP2350 in BOOTSEL mode, claim the PICOBOOT
|
|
21
|
+
* interface, and return a ready-to-use transport instance.
|
|
22
|
+
*
|
|
23
|
+
* @param vid USB Vendor ID (default: 0x2E8A — Raspberry Pi)
|
|
24
|
+
* @param pid USB Product ID (default: 0x0003 — RP2040 USBBOOT)
|
|
25
|
+
* @throws STK500ProtocolError if the 'usb' package is missing or no device found
|
|
26
|
+
*/
|
|
27
|
+
static async open(vid = 0x2E8A, pid = 0x0003) {
|
|
28
|
+
// Dynamic import: 'usb' is an optional peer dependency.
|
|
29
|
+
// The new Function wrapper prevents TypeScript from resolving 'usb' statically,
|
|
30
|
+
// avoiding build errors when the package is not installed.
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
let usbModule;
|
|
33
|
+
try {
|
|
34
|
+
// eslint-disable-next-line no-new-func
|
|
35
|
+
usbModule = await (new Function('m', 'return import(m)'))('usb');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new STK500ProtocolError("Package 'usb' is not installed. Run: npm install usb");
|
|
39
|
+
}
|
|
40
|
+
const device = (usbModule.findByIds ?? usbModule.default?.findByIds)?.(vid, pid);
|
|
41
|
+
if (!device) {
|
|
42
|
+
throw new STK500ProtocolError(`No device found in BOOTSEL mode ` +
|
|
43
|
+
`(VID=0x${vid.toString(16).toUpperCase()}, PID=0x${pid.toString(16).toUpperCase()}). ` +
|
|
44
|
+
'Hold the BOOTSEL button while connecting USB.');
|
|
45
|
+
}
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
const dev = device;
|
|
48
|
+
dev.open();
|
|
49
|
+
// Interface 1 = PICOBOOT (vendor class 0xFF).
|
|
50
|
+
// If the device only exposes 1 interface (MSD without PICOBOOT), that is interface 0.
|
|
51
|
+
const ifaceCount = dev.interfaces?.length ?? 0;
|
|
52
|
+
const ifaceIndex = ifaceCount >= 2 ? PICOBOOT_INTERFACE : 0;
|
|
53
|
+
const iface = dev.interface(ifaceIndex);
|
|
54
|
+
// Detach kernel driver on Linux / macOS if necessary
|
|
55
|
+
try {
|
|
56
|
+
if (iface.isKernelDriverActive?.()) {
|
|
57
|
+
iface.detachKernelDriver();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Not available on Windows — silently ignore
|
|
62
|
+
}
|
|
63
|
+
iface.claim();
|
|
64
|
+
// Discover OUT and IN bulk endpoints dynamically (endpoint addresses vary
|
|
65
|
+
// between bootrom revisions)
|
|
66
|
+
let outEp = null;
|
|
67
|
+
let inEp = null;
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
for (const ep of iface.endpoints) {
|
|
70
|
+
if (ep.direction === 'out')
|
|
71
|
+
outEp = ep;
|
|
72
|
+
else if (ep.direction === 'in')
|
|
73
|
+
inEp = ep;
|
|
74
|
+
}
|
|
75
|
+
if (!outEp || !inEp) {
|
|
76
|
+
try {
|
|
77
|
+
dev.close();
|
|
78
|
+
}
|
|
79
|
+
catch { /* ignore */ }
|
|
80
|
+
throw new STK500ProtocolError('PICOBOOT bulk endpoints not found. ' +
|
|
81
|
+
'Is the device in BOOTSEL mode?');
|
|
82
|
+
}
|
|
83
|
+
return new NodeUSBTransport(dev, iface, outEp, inEp);
|
|
84
|
+
}
|
|
85
|
+
// ── IPicobootTransport ─────────────────────────────────────────────────────
|
|
86
|
+
async sendCommand(cmd) {
|
|
87
|
+
return this._transferOut(cmd);
|
|
88
|
+
}
|
|
89
|
+
async receiveBytes(maxLength) {
|
|
90
|
+
if (maxLength === 0)
|
|
91
|
+
return new Uint8Array(0);
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
+
this.inEp.transfer(maxLength, (err, data) => {
|
|
95
|
+
if (err)
|
|
96
|
+
reject(err);
|
|
97
|
+
else
|
|
98
|
+
resolve(data ? new Uint8Array(data) : new Uint8Array(0));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async sendBytes(data) {
|
|
103
|
+
return this._transferOut(data);
|
|
104
|
+
}
|
|
105
|
+
async resetInterface() {
|
|
106
|
+
// PICOBOOT_RESET control transfer (bmRequestType=0x41, bRequest=0x41)
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
const dev = this.device;
|
|
109
|
+
const iface = this.iface;
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
dev.controlTransfer(0x41, // bmRequestType: host→device, class, interface
|
|
112
|
+
0x41, // bRequest: PICOBOOT_RESET
|
|
113
|
+
0, // wValue
|
|
114
|
+
iface.interfaceNumber ?? 1, // wIndex
|
|
115
|
+
Buffer.alloc(0), (err) => { if (err)
|
|
116
|
+
reject(err);
|
|
117
|
+
else
|
|
118
|
+
resolve(); });
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async close() {
|
|
122
|
+
try {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
+
await new Promise((res) => this.iface.release(true, () => res()));
|
|
125
|
+
}
|
|
126
|
+
catch { /* ignore */ }
|
|
127
|
+
try {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
this.device.close();
|
|
130
|
+
}
|
|
131
|
+
catch { /* ignore */ }
|
|
132
|
+
}
|
|
133
|
+
// ── Private ────────────────────────────────────────────────────────────────
|
|
134
|
+
_transferOut(data) {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
this.outEp.transfer(Buffer.from(data), (err) => {
|
|
138
|
+
if (err)
|
|
139
|
+
reject(err);
|
|
140
|
+
else
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=NodeUSBTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeUSBTransport.js","sourceRoot":"","sources":["../../src/transport/NodeUSBTransport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ISTKTransport, SerialSignals } from './ITransport.js';
|
|
2
|
+
export interface WebSerialPortFilter {
|
|
3
|
+
usbVendorId?: number;
|
|
4
|
+
usbProductId?: number;
|
|
5
|
+
}
|
|
6
|
+
type WebSerialPort = any;
|
|
7
|
+
/** Options passed to WebSerial port.open() */
|
|
8
|
+
export interface WebSerialOpenOptions {
|
|
9
|
+
/** Baud rate (required) */
|
|
10
|
+
baudRate: number;
|
|
11
|
+
/** Data bits (default: 8) */
|
|
12
|
+
dataBits?: 7 | 8;
|
|
13
|
+
/** Stop bits (default: 1) — use 2 for UPDI */
|
|
14
|
+
stopBits?: 1 | 2;
|
|
15
|
+
/** Parity (default: 'none') — use 'even' for UPDI */
|
|
16
|
+
parity?: 'none' | 'even' | 'odd';
|
|
17
|
+
/** Flow control (default: 'none') */
|
|
18
|
+
flowControl?: 'none' | 'hardware';
|
|
19
|
+
}
|
|
20
|
+
export declare class WebSerialTransport implements ISTKTransport {
|
|
21
|
+
private readonly port;
|
|
22
|
+
private writer;
|
|
23
|
+
private reader;
|
|
24
|
+
private readLoopActive;
|
|
25
|
+
private readonly listeners;
|
|
26
|
+
private lastOpenOptions;
|
|
27
|
+
constructor(port: WebSerialPort);
|
|
28
|
+
/** Check whether WebSerial is available in the current environment */
|
|
29
|
+
static isSupported(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Show the browser's port-picker dialog and return a transport wrapping the selected port.
|
|
32
|
+
* @param filters - Optional USB vendor/product ID filters to narrow the list shown to the user
|
|
33
|
+
*/
|
|
34
|
+
static requestPort(filters?: WebSerialPortFilter[]): Promise<WebSerialTransport>;
|
|
35
|
+
/**
|
|
36
|
+
* Return transports for all previously-approved ports (no dialog).
|
|
37
|
+
* Useful for re-connecting without prompting the user again.
|
|
38
|
+
*/
|
|
39
|
+
static getPorts(): Promise<WebSerialTransport[]>;
|
|
40
|
+
/** Open the serial port at the given baud rate with optional serial settings */
|
|
41
|
+
open(baudRate: number, options?: Partial<WebSerialOpenOptions>): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Send a BREAK condition to reset the UPDI state machine.
|
|
44
|
+
*
|
|
45
|
+
* Implementation: close the port, reopen at 300 baud 8N1, transmit 0x00
|
|
46
|
+
* (which produces a ~33 ms break period relative to 115200 baud), then
|
|
47
|
+
* reopen at the original settings.
|
|
48
|
+
*
|
|
49
|
+
* The current port must have been opened with open() before calling this.
|
|
50
|
+
*/
|
|
51
|
+
sendBreak(): Promise<void>;
|
|
52
|
+
/** Internal helper: close writer, reader, and port without clearing lastOpenOptions */
|
|
53
|
+
private _closePort;
|
|
54
|
+
/** Set DTR/RTS hardware signals to trigger Arduino bootloader entry */
|
|
55
|
+
setSignals(opts: SerialSignals): Promise<void>;
|
|
56
|
+
write(data: Uint8Array): Promise<void>;
|
|
57
|
+
on(_event: 'data', handler: (data: Uint8Array) => void): void;
|
|
58
|
+
off(_event: 'data', handler: (data: Uint8Array) => void): void;
|
|
59
|
+
private startReadLoop;
|
|
60
|
+
close(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=WebSerialTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSerialTransport.d.ts","sourceRoot":"","sources":["../../src/transport/WebSerialTransport.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,KAAK,aAAa,GAAG,GAAG,CAAC;AAEzB,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACjC,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CACnC;AAED,qBAAa,kBAAmB,YAAW,aAAa;IAO1C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,MAAM,CAAwD;IACtE,OAAO,CAAC,MAAM,CAAwD;IACtE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,OAAO,CAAC,eAAe,CAAqC;gBAE/B,IAAI,EAAE,aAAa;IAEhD,sEAAsE;IACtE,MAAM,CAAC,WAAW,IAAI,OAAO;IAI7B;;;OAGG;WACU,WAAW,CACtB,OAAO,GAAE,mBAAmB,EAAO,GAClC,OAAO,CAAC,kBAAkB,CAAC;IAW9B;;;OAGG;WACU,QAAQ,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAOtD,gFAAgF;IAC1E,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpF;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,uFAAuF;YACzE,UAAU;IAYxB,uEAAuE;IACjE,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAI7D,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAI9D,OAAO,CAAC,aAAa;IAiBf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Browser WebSerial API transport for webserial-flasher.
|
|
2
|
+
// Works in Chrome 89+ and Edge 89+. Not available in Firefox or Safari.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// const transport = await WebSerialTransport.requestPort([{ usbVendorId: 0x2341 }]);
|
|
6
|
+
// await transport.open(115200);
|
|
7
|
+
// const stk = new STK500(transport, board);
|
|
8
|
+
// await stk.bootload(hex);
|
|
9
|
+
// await transport.close();
|
|
10
|
+
export class WebSerialTransport {
|
|
11
|
+
constructor(port) {
|
|
12
|
+
this.port = port;
|
|
13
|
+
this.writer = null;
|
|
14
|
+
this.reader = null;
|
|
15
|
+
this.readLoopActive = false;
|
|
16
|
+
this.listeners = new Set();
|
|
17
|
+
this.lastOpenOptions = null;
|
|
18
|
+
}
|
|
19
|
+
/** Check whether WebSerial is available in the current environment */
|
|
20
|
+
static isSupported() {
|
|
21
|
+
return typeof navigator !== 'undefined' && 'serial' in navigator;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Show the browser's port-picker dialog and return a transport wrapping the selected port.
|
|
25
|
+
* @param filters - Optional USB vendor/product ID filters to narrow the list shown to the user
|
|
26
|
+
*/
|
|
27
|
+
static async requestPort(filters = []) {
|
|
28
|
+
if (!WebSerialTransport.isSupported()) {
|
|
29
|
+
throw new Error('WebSerial API is not supported. Use Chrome 89+ or Edge 89+.');
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const port = await navigator.serial.requestPort({ filters });
|
|
33
|
+
return new WebSerialTransport(port);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Return transports for all previously-approved ports (no dialog).
|
|
37
|
+
* Useful for re-connecting without prompting the user again.
|
|
38
|
+
*/
|
|
39
|
+
static async getPorts() {
|
|
40
|
+
if (!WebSerialTransport.isSupported())
|
|
41
|
+
return [];
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
const ports = await navigator.serial.getPorts();
|
|
44
|
+
return ports.map((p) => new WebSerialTransport(p));
|
|
45
|
+
}
|
|
46
|
+
/** Open the serial port at the given baud rate with optional serial settings */
|
|
47
|
+
async open(baudRate, options) {
|
|
48
|
+
const opts = {
|
|
49
|
+
baudRate,
|
|
50
|
+
dataBits: options?.dataBits ?? 8,
|
|
51
|
+
stopBits: options?.stopBits ?? 1,
|
|
52
|
+
parity: options?.parity ?? 'none',
|
|
53
|
+
flowControl: options?.flowControl ?? 'none',
|
|
54
|
+
};
|
|
55
|
+
this.lastOpenOptions = opts;
|
|
56
|
+
await this.port.open(opts);
|
|
57
|
+
this.writer = this.port.writable.getWriter();
|
|
58
|
+
this.reader = this.port.readable.getReader();
|
|
59
|
+
this.readLoopActive = true;
|
|
60
|
+
this.startReadLoop();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Send a BREAK condition to reset the UPDI state machine.
|
|
64
|
+
*
|
|
65
|
+
* Implementation: close the port, reopen at 300 baud 8N1, transmit 0x00
|
|
66
|
+
* (which produces a ~33 ms break period relative to 115200 baud), then
|
|
67
|
+
* reopen at the original settings.
|
|
68
|
+
*
|
|
69
|
+
* The current port must have been opened with open() before calling this.
|
|
70
|
+
*/
|
|
71
|
+
async sendBreak() {
|
|
72
|
+
if (!this.lastOpenOptions) {
|
|
73
|
+
throw new Error('WebSerialTransport: call open() before sendBreak()');
|
|
74
|
+
}
|
|
75
|
+
const originalOptions = { ...this.lastOpenOptions };
|
|
76
|
+
// 1. Tear down current connection
|
|
77
|
+
await this._closePort();
|
|
78
|
+
// 2. Reopen at 300 baud to generate a BREAK pulse
|
|
79
|
+
await this.port.open({ baudRate: 300, dataBits: 8, stopBits: 1, parity: 'none', flowControl: 'none' });
|
|
80
|
+
const breakWriter = this.port.writable.getWriter();
|
|
81
|
+
await breakWriter.write(new Uint8Array([0x00])); // 0x00 at 300 baud ≈ 33 ms low
|
|
82
|
+
await new Promise((r) => setTimeout(r, 50)); // ensure byte clocks out
|
|
83
|
+
breakWriter.releaseLock();
|
|
84
|
+
await this.port.close();
|
|
85
|
+
// 3. Reopen at original settings
|
|
86
|
+
await this.port.open(originalOptions);
|
|
87
|
+
this.writer = this.port.writable.getWriter();
|
|
88
|
+
this.reader = this.port.readable.getReader();
|
|
89
|
+
this.readLoopActive = true;
|
|
90
|
+
this.startReadLoop();
|
|
91
|
+
}
|
|
92
|
+
/** Internal helper: close writer, reader, and port without clearing lastOpenOptions */
|
|
93
|
+
async _closePort() {
|
|
94
|
+
this.readLoopActive = false;
|
|
95
|
+
this.listeners.clear();
|
|
96
|
+
try {
|
|
97
|
+
await this.reader?.cancel();
|
|
98
|
+
}
|
|
99
|
+
catch { /* ignore */ }
|
|
100
|
+
try {
|
|
101
|
+
this.reader?.releaseLock();
|
|
102
|
+
}
|
|
103
|
+
catch { /* ignore */ }
|
|
104
|
+
try {
|
|
105
|
+
await this.writer?.abort();
|
|
106
|
+
}
|
|
107
|
+
catch { /* ignore */ }
|
|
108
|
+
try {
|
|
109
|
+
this.writer?.releaseLock();
|
|
110
|
+
}
|
|
111
|
+
catch { /* ignore */ }
|
|
112
|
+
try {
|
|
113
|
+
await this.port.close();
|
|
114
|
+
}
|
|
115
|
+
catch { /* ignore */ }
|
|
116
|
+
this.reader = null;
|
|
117
|
+
this.writer = null;
|
|
118
|
+
}
|
|
119
|
+
/** Set DTR/RTS hardware signals to trigger Arduino bootloader entry */
|
|
120
|
+
async setSignals(opts) {
|
|
121
|
+
// WebSerialPort.setSignals is defined in the WebSerial spec
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
await this.port.setSignals(opts);
|
|
124
|
+
}
|
|
125
|
+
async write(data) {
|
|
126
|
+
if (!this.writer)
|
|
127
|
+
throw new Error('Transport not open — call open() first');
|
|
128
|
+
await this.writer.write(data);
|
|
129
|
+
}
|
|
130
|
+
on(_event, handler) {
|
|
131
|
+
this.listeners.add(handler);
|
|
132
|
+
}
|
|
133
|
+
off(_event, handler) {
|
|
134
|
+
this.listeners.delete(handler);
|
|
135
|
+
}
|
|
136
|
+
startReadLoop() {
|
|
137
|
+
void (async () => {
|
|
138
|
+
while (this.readLoopActive) {
|
|
139
|
+
try {
|
|
140
|
+
const { value, done } = await this.reader.read();
|
|
141
|
+
if (done || !this.readLoopActive)
|
|
142
|
+
break;
|
|
143
|
+
if (value && value.byteLength > 0) {
|
|
144
|
+
this.listeners.forEach((fn) => fn(value));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Port closed externally or connection lost — stop loop silently
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
}
|
|
154
|
+
async close() {
|
|
155
|
+
this.lastOpenOptions = null;
|
|
156
|
+
await this._closePort();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=WebSerialTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSerialTransport.js","sourceRoot":"","sources":["../../src/transport/WebSerialTransport.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webserial-flasher",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Multi-protocol firmware flasher — STK500v1, STK500v2, AVR109, UPDI, PICOBOOT (RP2040) — ESM, TypeScript, Node.js + WebSerial browser support.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"lint": "eslint src",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "tsx --test test/unit/*.test.ts test/integration/*.test.ts",
|
|
24
|
+
"test:unit": "tsx --test test/unit/*.test.ts",
|
|
25
|
+
"test:integration": "tsx --test test/integration/*.test.ts",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/vielang/webserial-flasher.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/vielang/webserial-flasher/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/vielang/webserial-flasher#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"arduino",
|
|
38
|
+
"stk500",
|
|
39
|
+
"stk500v2",
|
|
40
|
+
"avr109",
|
|
41
|
+
"updi",
|
|
42
|
+
"picoboot",
|
|
43
|
+
"avr",
|
|
44
|
+
"attiny",
|
|
45
|
+
"atmega",
|
|
46
|
+
"rp2040",
|
|
47
|
+
"raspberry-pi-pico",
|
|
48
|
+
"avrdude",
|
|
49
|
+
"typescript",
|
|
50
|
+
"esm",
|
|
51
|
+
"webserial",
|
|
52
|
+
"webusb",
|
|
53
|
+
"firmware",
|
|
54
|
+
"flasher"
|
|
55
|
+
],
|
|
56
|
+
"author": "Vie Lang <chongnguoithuong@gmail.com>",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"usb": ">=2.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"usb": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@eslint/js": "^9.16.0",
|
|
71
|
+
"@types/node": "^22.10.1",
|
|
72
|
+
"eslint": "^9.16.0",
|
|
73
|
+
"globals": "^15.13.0",
|
|
74
|
+
"serialport": "^12.0.0",
|
|
75
|
+
"tsx": "^4.19.2",
|
|
76
|
+
"typescript": "^5.7.2",
|
|
77
|
+
"typescript-eslint": "^8.18.0"
|
|
78
|
+
}
|
|
79
|
+
}
|