removed everything not nano-related, rewrote the logic of serial connection so the user is not prompted to connect 2 times when using the old bootloader

This commit is contained in:
Oleksiy H
2025-11-09 15:23:46 +02:00
parent 5888eee2db
commit 0c0b6d852f
14 changed files with 1302 additions and 1503 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2348
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{
"name": "web-arduino-uploader",
"name": "arduino-nano-web-uploader",
"version": "1.1.2",
"main": "dist/index.ts",
"types": "dist/index.d.ts",
"license": "MIT",
"author": {
"name": "David Buezas",
"email": "david.buezas@gmail.com",
"url": "https://github.com/dbuezas/arduino-web-uploader/"
"name": "Oleksiy Hrachov",
"email": "oleksiy.h@proton.me",
"url": "https://git.pinkduck.xyz/oleksiy/arduino-NANO-web-uploader"
},
"scripts": {
"prepublish": "npm run build",
@ -23,8 +23,8 @@
"process": "^0.11.10",
"stream-browserify": "^3.0.0",
"typescript": "^4.0.3",
"webpack": "^5.2.0",
"webpack-cli": "^4.1.0"
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"async": "^3.2.0",

View File

@ -44,6 +44,50 @@ export class Serial {
port?: Port
reader?: NodeJS.ReadableStream
writer?: WritableStreamDefaultWriter
private rwStream?: NodeJS.ReadWriteStream
// ADD THIS METHOD
async requestPort(portFilters: PortFilters = {}): Promise<Port> {
const port = await navigator.serial.requestPort(portFilters)
return port
}
// ADD THIS METHOD
async openPort(port: Port, options: SerialOptions): Promise<void> {
const fullOptions = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: 'none',
bufferSize: 255,
rtscts: false,
xon: false,
xoff: false,
...options,
}
if (this.port) await this.close()
this.port = port
await this.port.open(fullOptions)
this.reader = new ReadableWebToNodeStream(this.port.readable)
this.writer = this.port.writable.getWriter()
// Create and store the stream
const rwStream = (this.reader as unknown) as NodeJS.ReadWriteStream
// @ts-ignore
rwStream.write = (buffer: string | Uint8Array, onDone: (err: Error | null | undefined) => void) => {
this.writer!.write(buffer).then(() => onDone(null), onDone)
return true
}
this.rwStream = rwStream
}
// ADD THIS METHOD
getStream(): NodeJS.ReadWriteStream {
if (!this.rwStream) throw new Error('Not connected')
return this.rwStream
}
async close() {
if (this.reader) {
@ -68,38 +112,13 @@ export class Serial {
async connectWithPaired(options: SerialOptions) {
const [port] = await navigator.serial.getPorts()
if (!port) throw new Error('no paired')
return this._connect(options, port)
await this.openPort(port, options)
return this.getStream()
}
async connect(options: SerialOptions, portFilters: PortFilters = {}) {
const port = await navigator.serial.requestPort(portFilters)
return this._connect(options, port)
}
async _connect(options: SerialOptions, port: Port) {
options = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: 'none',
bufferSize: 255,
rtscts: false,
xon: false,
xoff: false,
...options,
}
if (this.port) await this.close()
this.port = port
await this.port.open(options)
this.reader = new ReadableWebToNodeStream(this.port.readable)
this.writer = this.port.writable.getWriter()
// next I'm faking a NodeJS.ReadWriteStream
const rwStream = (this.reader as unknown) as NodeJS.ReadWriteStream
// @ts-ignore
rwStream.write = (buffer: string | Uint8Array, onDone: (err: Error | null | undefined) => void) => {
this.writer!.write(buffer).then(() => onDone(null), onDone)
return true
}
return rwStream
const port = await this.requestPort(portFilters)
await this.openPort(port, options)
return this.getStream()
}
}
const serial = new Serial()

View File

@ -11,39 +11,15 @@ type Board = {
baudRate: number
use_8_bit_addresseses?: boolean
}
export const boards = {
avr4809: {
signature: Buffer.from([0x1e, 0x96, 0x51]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
use_8_bit_addresseses: true,
} as Board,
lgt8f328p: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 57600,
} as Board,
nanoOldBootloader: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 57600,
} as Board,
nano: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
} as Board,
uno: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
} as Board,
proMini: {
nanoNewBootloader: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
@ -53,26 +29,22 @@ export const boards = {
const noop = (callback: () => void) => callback()
console.log("Arduino Web Uploader Version:", version)
export async function upload(
//console.log("Nano Web Uploader Version:", version)
async function attemptUpload(
serialStream: any,
hex: Buffer,
board: Board,
hexFileHref: string,
onProgress: (percentage: number) => void,
verify = false,
portFilters: PortFilters = {},
verify: boolean,
onProgress: (percentage: number) => void
) {
try {
const text = await fetch(hexFileHref)
.then((response) => response.text())
let { data: hex } = intel_hex.parse(text)
const serialStream = await serial.connect({ baudRate: board.baudRate }, portFilters)
onProgress(0)
const stk500 = new Stk500()
let sent = 0
let total = hex.length / board.pageSize
if (verify) total *= 2
stk500.log = (what: string) => {
if (what === 'page done' || what === 'verify done') {
sent += 1
@ -83,7 +55,6 @@ export async function upload(
}
await async.series([
// send two dummy syncs like avrdude does
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
@ -94,6 +65,35 @@ export async function upload(
!verify ? noop : stk500.verify.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
stk500.exitProgrammingMode.bind(stk500, serialStream, board.timeout),
])
}
export async function upload(
hexFileHref: string,
onProgress: (percentage: number) => void,
verify = false,
portFilters: PortFilters = {},
) {
try {
const text = await fetch(hexFileHref)
.then((response) => response.text())
let { data: hex } = intel_hex.parse(text)
const port = await serial.requestPort(portFilters)
try {
await serial.openPort(port, { baudRate: boards.nanoNewBootloader.baudRate })
const serialStream = serial.getStream()
await attemptUpload(serialStream, hex, boards.nanoNewBootloader, verify, onProgress)
} catch (error) {
await serial.close()
await new Promise(resolve => setTimeout(resolve, 500))
await serial.openPort(port, { baudRate: boards.nanoOldBootloader.baudRate })
const serialStream = serial.getStream()
await attemptUpload(serialStream, hex, boards.nanoOldBootloader, verify, onProgress)
}
} finally {
serial.close()
}

View File

@ -1,44 +0,0 @@
import { upload, boards } from '.'
import { PortFilters } from './Serial'
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[arduino-uploader]').forEach((el) => {
el.addEventListener('click', async () => {
if (!navigator.serial) return alert('Please enable the Web Serial API first: https://web.dev/serial/#use')
const hexHref = el.getAttribute('hex-href')
const board = el.getAttribute('board')
const verify = el.hasAttribute('verify')
const progressEl = el.querySelector('.upload-progress')
const onProgress = (progress: number) => {
progressEl.innerHTML = `${progress}%`
}
var Status = '';
let portFilters = {} as PortFilters
try {
portFilters = { filters: JSON.parse(el.getAttribute('port-filters')) || [] }
} catch (e) { }
try {
await upload(boards['nano'], hexHref, onProgress, verify, portFilters)
} catch (e) {
Status = 'Error: can\'t connect to the module. Please see troubleshooting section.<br /><code>' + e + '</code>'
//alert(e)
//throw e
}
if (Status.includes('Error: ')) {
Status = ''
try {
await upload(boards['nanoOldBootloader'], hexHref, onProgress, verify, portFilters)
} catch (e) {
Status = 'Error: can\'t connect to the module. Please see troubleshooting section.<br /><code>Old Bootloader: ' + e + '</code>'
//alert(e)
//throw e //Error: Sending 3020: receiveData timeout after 400ms
}
}
if (Status == '') {
Status = 'Done!'
}
progressEl.innerHTML = Status
console.log("Upload successful!\nEnvious? here's how https://github.com/dbuezas/arduino-web-uploader")
})
})
})

View File

@ -1,30 +1,31 @@
import { upload, boards } from './'
import { upload, boards } from '.'
import { PortFilters } from './Serial'
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[arduino-uploader]').forEach((el) => {
el.addEventListener('click', async () => {
if (!navigator.serial) return alert('Please enable the Web Serial API first: https://web.dev/serial/#use')
const hexHref = el.getAttribute('hex-href')
const board = el.getAttribute('board')
const verify = el.hasAttribute('verify')
const progressEl = el.querySelector('.upload-progress')
const onProgress = (progress: number) => {
progressEl.innerHTML = `${progress}%`
}
let portFilters = {} as PortFilters
try {
portFilters = { filters: JSON.parse(el.getAttribute('port-filters')) || [] }
} catch (e) { }
try {
await upload(boards[board], hexHref, onProgress, verify, portFilters)
} catch (e) {
progressEl.innerHTML = 'Error!'
alert(e)
throw e
}
await upload(hexHref, onProgress, verify, portFilters)
progressEl.innerHTML = 'Done!'
console.log("Upload successful!\nEnvious? here's how https://github.com/dbuezas/arduino-web-uploader")
} catch (e) {
progressEl.innerHTML = 'Error: can\'t connect to the module. Please see troubleshooting section.<br/><code>' + e + '</code>'
}
})
})
})

View File

@ -1,51 +0,0 @@
:100200000C9450010C9462010C9462010C946201F4
:100210000C9462010C9462010C9462010C946201D2
:100220000C9462010C9462010C9462010C946201C2
:100230000C9462010C9462010C9462010C946201B2
:100240000C9462010C9462010C9462010C946201A2
:100250000C9462010C9462010C9462010C94620192
:100260000C9462010C94D4010C9462010C94620110
:100270000C9462010C9462010C9462010C94620172
:100280000C9462010C9462010C9462010C94620162
:100290000C9462010C9462010C9462010C94620152
:1002A00011241FBECFEFCDBFDFE3DEBF28E2A0E009
:1002B000B8E201C01D92A430B207E1F70E94FC0130
:1002C0000C9482020C9400019091000497FF0AC0E4
:1002D000811104C080E880930604089580E880932B
:1002E000050408959FB7F894811107C08091170401
:1002F000877F809317049FBF0895809117048860BB
:10030000F8CF8FB7F89420910028309101284091C0
:10031000022850910328E091AA0AF091AB0A90912B
:10032000A60A90FF08C02F5F3F4F4F4F5F4FE091ED
:10033000AA0AF091AB0A8FBFA8EEB3E00E94680250
:1003400024E0F695E7952A95E1F76E0F7F1F811D52
:10035000911D0895CF92DF92EF92FF920E9481014A
:100360006B017C0120E4C20E22E4D21E2FE0E21ECB
:10037000F11C80E4C81682E4D8068FE0E806F10498
:1003800038F40E948101C616D706E806F906C8F3BC
:100390000E9481016C157D058E059F05C8F3FF90B5
:1003A000EF90DF90CF9008951F920F920FB60F92AB
:1003B00011248F939F93AF93BF93809100289091C6
:1003C0000128A0910228B09103280196A11DB11D1A
:1003D0008093002890930128A0930228B0930328CB
:1003E00081E08093A60ABF91AF919F918F910F906A
:1003F0000FBE0F901F90189588ED90E084BF9093EA
:10040000610080910206866080930206809100065A
:10041000816080930006809102068F7C80658093C6
:1004200002061092E20582E08093E40581E0809369
:10043000030A8EEF8093270A8093260A80E8809330
:100440002D0A80932B0A8093290A80932C0A80938B
:100450002A0A8093280A8BE08093000A87E0809321
:10046000E505E0E8FAE037E02EEF90E885E031833B
:100470002487958780837096E03C4AE0F407B9F7BB
:100480001092A10A2FE73EE32093AC0A3093AD0A05
:100490008091A50A81608093A50A1092A00A80919C
:1004A000A00A81608093A00A789490930104C0E030
:1004B000D0E081E00E9464010E94AA0180E00E94D5
:1004C00064010E94AA012097A1F30E940000F1CFCD
:1004D0000E947302A59F900DB49F900DA49F800D64
:1004E000911D11240895A29FB001B39FC001A39F45
:1004F000700D811D1124911DB29F700D811D11245D
:08050000911D0895F894FFCF4E
:0400000300000200F7
:00000001FF

View File

@ -1,62 +0,0 @@
:100000000C946F000C9481000C9481000C9481007E
:100010000C9481000C9481000C9481000C9481005C
:100020000C9481000C9481000C9481000C9481004C
:100030000C9481000C9481000C9481000C9481003C
:100040000C9438010C9481000C9481000C94810074
:100050000C9481000C9481000C9481000C9481001C
:100060000C9481000C9481000C9400000C9400000E
:100070000C9400000C9400000000000024002700F5
:100080002A002D0000000000250028002B002E0073
:10009000040404040404040402020202020203032E
:1000A000030303030505050505050503010204080F
:1000B00010204080010204081020010204081020D2
:1000C0000208010410204040000000085002010016
:1000D00000030407000000000000000000001124DD
:1000E0001FBECFEFD8E0DEBFCDBF21E0A0E0B1E082
:1000F00001C01D92A930B207E1F70E947E010C9465
:10010000E3010C940000E5EDF0E02491E9EBF0E070
:100110003491EDE9F0E09491992361F12223C9F043
:1001200021502B30B0F4E22FF0E0E756FF4F0C9453
:10013000DD01BF00C300A400BB00A900A900C600E8
:10014000CC00D000D600DA00209180002F772093D9
:100150008000E92FF0E0EE0FFF1FEC57FF4FA59155
:10016000B4919FB7F894EC91811128C030953E234B
:100170003C939FBF0895209180002F7DE8CF24B548
:100180002F7724BDE6CF24B52F7DFBCF2091B00083
:100190002F772093B000DDCF2091B0002F7DF9CFD5
:1001A000209190002F7720939000D3CF2091900042
:1001B0002F7DF9CF20919000277FF5CF3E2BD8CF10
:1001C0003FB7F8948091050190910601A091070135
:1001D000B091080126B5A89B05C02F3F19F00196E4
:1001E000A11DB11D3FBFBA2FA92F982F8827BC0191
:1001F000CD01620F711D811D911D660F771F881F34
:10020000991F08958F929F92AF92BF92CF92DF92E3
:10021000EF92FF920E94E0004B015C0188EEC82E35
:1002200083E0D82EE12CF12C0E94E00068197909B6
:100230008A099B09683E734081059105A8F321E076
:10024000C21AD108E108F10888EE880E83E0981EF2
:10025000A11CB11CC114D104E104F10429F7FF90E1
:10026000EF90DF90CF90BF90AF909F908F900895C8
:100270001F920F920FB60F9211242F933F938F93DB
:100280009F93AF93BF938091010190910201A09140
:100290000301B09104013091000120E4230F2D37B8
:1002A00028F023EC230F0196A11DB11D209300011E
:1002B0008093010190930201A0930301B093040184
:1002C0008091050190910601A0910701B09108016C
:1002D0000196A11DB11D8093050190930601A09385
:1002E0000701B0930801BF91AF919F918F913F910A
:1002F0002F910F900FBE0F901F9018958091F200D4
:1003000080618EBB90E89093F2008EB38093F200F0
:100310009093610081E0809361009093610010925E
:100320006100789494B5926094BD94B5916094BD49
:1003300023E025BD80936E0092E09093810080932E
:1003400080009091B10094609093B1008093B000D0
:10035000209391008093900084E880937A0010921B
:10036000C100E9EBF0E02491EDE9F0E0849188230D
:1003700099F090E0880F991FFC01E858FF4FA59174
:10038000B491FC01EC57FF4F859194918FB7F8948D
:10039000EC91E22BEC938FBFC0E0D0E081E00E94B3
:1003A00083000E94020180E00E9483000E940201FB
:1003B0002097A1F30E940000F1CFEE0FFF1F0590E0
:0A03C000F491E02D0994F894FFCFAA
:00000001FF

View File

@ -1,59 +0,0 @@
:100000000C945C000C946E000C946E000C946E00CA
:100010000C946E000C946E000C946E000C946E00A8
:100020000C946E000C946E000C946E000C946E0098
:100030000C946E000C946E000C946E000C946E0088
:100040000C9413010C946E000C946E000C946E00D2
:100050000C946E000C946E000C946E000C946E0068
:100060000C946E000C946E00000000002400270029
:100070002A0000000000250028002B0004040404CE
:100080000404040402020202020203030303030342
:10009000010204081020408001020408102001021F
:1000A00004081020000000080002010000030407FB
:1000B000000000000000000011241FBECFEFD8E0B8
:1000C000DEBFCDBF21E0A0E0B1E001C01D92A930AC
:1000D000B207E1F70E945D010C94CC010C94000082
:1000E000E1EBF0E02491EDE9F0E09491E9E8F0E053
:1000F000E491EE23C9F0222339F0233001F1A8F472
:10010000213019F1223029F1F0E0EE0FFF1FEE58F7
:10011000FF4FA591B4912FB7F894EC91811126C0AF
:1001200090959E239C932FBF08952730A9F02830E7
:10013000C9F0243049F7209180002F7D03C0209121
:1001400080002F7720938000DFCF24B52F7724BD48
:10015000DBCF24B52F7DFBCF2091B0002F772093EC
:10016000B000D2CF2091B0002F7DF9CF9E2BDACFF7
:100170003FB7F8948091050190910601A091070185
:10018000B091080126B5A89B05C02F3F19F0019634
:10019000A11DB11D3FBFBA2FA92F982F8827BC01E1
:1001A000CD01620F711D811D911D42E0660F771F09
:1001B000881F991F4A95D1F708958F929F92AF9209
:1001C000BF92CF92DF92EF92FF920E94B8004B0154
:1001D0005C0188EEC82E83E0D82EE12CF12C0E9421
:1001E000B800681979098A099B09683E734081053E
:1001F0009105A8F321E0C21AD108E108F10888EEC0
:10020000880E83E0981EA11CB11CC114D104E10426
:10021000F10429F7FF90EF90DF90CF90BF90AF905F
:100220009F908F9008951F920F920FB60F921124F6
:100230002F933F938F939F93AF93BF93809101012F
:1002400090910201A0910301B0910401309100014D
:1002500023E0230F2D3758F50196A11DB11D2093E2
:1002600000018093010190930201A0930301B093D8
:1002700004018091050190910601A0910701B091C0
:1002800008010196A11DB11D8093050190930601FF
:10029000A0930701B0930801BF91AF919F918F91F7
:1002A0003F912F910F900FBE0F901F90189526E849
:1002B000230F0296A11DB11DD2CF789484B5826020
:1002C00084BD84B5816084BD85B5826085BD85B5FA
:1002D000816085BD80916E00816080936E00109278
:1002E00081008091810082608093810080918100F3
:1002F0008160809381008091800081608093800084
:100300008091B10084608093B1008091B0008160E1
:100310008093B00080917A00846080937A0080910D
:100320007A00826080937A0080917A008160809365
:100330007A0080917A00806880937A001092C100E0
:10034000EDE9F0E02491E9E8F0E08491882399F068
:1003500090E0880F991FFC01E859FF4FA591B491D7
:10036000FC01EE58FF4F859194918FB7F894EC9172
:10037000E22BEC938FBFC0E0D0E081E00E947000E0
:100380000E94DD0080E00E9470000E94DD00209746
:0C039000A1F30E940000F1CFF894FFCF11
:00000001FF

File diff suppressed because one or more lines are too long

View File

@ -2,23 +2,10 @@ const webpack = require('webpack')
const path = require('path')
module.exports = [{
entry: './dist/test.js',
entry: './dist/nanoUploader.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'test'),
},
resolve: { alias: { stream: 'stream-browserify', buffer: 'buffer', process: 'process/browser' } },
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process',
}),
],
}, {
entry: './dist/nano.js',
output: {
filename: 'nano.js',
path: path.resolve(__dirname, 'nano'),
filename: 'nano-uploader.js',
path: path.resolve(__dirname, 'nano-uploader'),
},
resolve: { alias: { stream: 'stream-browserify', buffer: 'buffer', process: 'process/browser' } },
plugins: [