first commit
This commit is contained in:
107
src/Serial.ts
Normal file
107
src/Serial.ts
Normal 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
102
src/index.ts
Normal 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
44
src/nano.ts
Normal 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
30
src/test.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user