first commit

This commit is contained in:
2025-03-20 21:52:59 +02:00
commit e8f1264971
21 changed files with 5913 additions and 0 deletions

107
src/Serial.ts Normal file
View File

@ -0,0 +1,107 @@
import { ReadableWebToNodeStream } from 'readable-web-to-node-stream'
export type Port = {
readable: ReadableStream
writable: WritableStream
open: (options: SerialOptions) => Promise<void>
close: () => Promise<void>
getInfo: () => { usbProductId: number; usbVendorId: number }
}
export type PortFilters = { filters?: { usbVendorId: number; usbProductId: number }[] }
export type NavigatorSerial = {
requestPort: (optns: PortFilters) => Port
getPorts: () => Promise<Port[]>
}
export type SerialOptions = {
baudRate?: number
dataBits?: number
stopBits?: number
parity?: string
bufferSize?: number
rtscts?: boolean
xon?: boolean
xoff?: boolean
xany?: boolean
}
declare global {
interface Window {
serial: Serial
}
interface Navigator {
serial: NavigatorSerial
}
}
declare global {
interface Window {
serial2: Serial
}
interface Navigator {
serial: NavigatorSerial
}
}
export class Serial {
port?: Port
reader?: NodeJS.ReadableStream
writer?: WritableStreamDefaultWriter
async close() {
if (this.reader) {
const reader = this.reader
this.reader = undefined
// @ts-ignore
// this is specific to the "readable-web-to-node-stream" library
await reader.reader.cancel()
// await this.reader.close() // this blocks if uploading failed
}
if (this.writer) {
const writer = this.writer
this.writer = undefined
await writer.close()
}
if (this.port) {
const port = this.port
this.port = undefined
await port.close()
}
}
async connectWithPaired(options: SerialOptions) {
const [port] = await navigator.serial.getPorts()
if (!port) throw new Error('no paired')
return this._connect(options, port)
}
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 serial = new Serial()
export default serial

102
src/index.ts Normal file
View File

@ -0,0 +1,102 @@
import serial, { PortFilters } from './Serial'
import async from 'async'
import * as intel_hex from 'intel-hex'
import Stk500 from 'stk500'
const { version } = require('../package.json')
type Board = {
signature: Buffer
pageSize: number
timeout: number
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: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
} as Board,
}
const noop = (callback: () => void) => callback()
console.log("Arduino Web Uploader Version:", version)
export async function upload(
board: Board,
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 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
const percent = Math.round((100 * sent) / total)
onProgress(percent)
}
console.log(what, sent, total, hex.length, board.pageSize)
}
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),
stk500.verifySignature.bind(stk500, serialStream, board.signature, board.timeout),
stk500.setOptions.bind(stk500, serialStream, {}, board.timeout),
stk500.enterProgrammingMode.bind(stk500, serialStream, board.timeout),
stk500.upload.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
!verify ? noop : stk500.verify.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
stk500.exitProgrammingMode.bind(stk500, serialStream, board.timeout),
])
} finally {
serial.close()
}
}
export default upload

44
src/nano.ts Normal file
View File

@ -0,0 +1,44 @@
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")
})
})
})

30
src/test.ts Normal file
View File

@ -0,0 +1,30 @@
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
}
progressEl.innerHTML = 'Done!'
console.log("Upload successful!\nEnvious? here's how https://github.com/dbuezas/arduino-web-uploader")
})
})
})