-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a2ba870
Showing
12 changed files
with
1,715 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Build avrdude with Emscripten | ||
|
||
on: | ||
push: | ||
tags: | ||
- v* | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v2 | ||
|
||
- name: Install dependencies | ||
run: sudo apt update && sudo apt install gcc-avr avr-libc freeglut3-dev arduino-core-avr | ||
|
||
- name: Setup NodeJS | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20.x | ||
registry-url: 'https://registry.npmjs.org' | ||
|
||
- name: Build SimAVR | ||
run: yarn build:simavr | ||
|
||
- name: Install Dependencies | ||
run: yarn install --frozen-lockfile | ||
|
||
- name: Build NPM Module | ||
run: yarn build | ||
|
||
- name: Publish to NPM | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} | ||
run: npm publish --access public |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.idea | ||
build | ||
build_assets | ||
node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Playwright Arduino | ||
|
||
Mocks the WebSerial API to test Arduino Uploaders Playwright | ||
|
||
## Usage | ||
|
||
Install the package with `yarn add -D @leaphy-robotics/playwright-arduino` or using NPM `npm i --save-dev @leaphy-robotics/playwright-arduino`. | ||
|
||
``js | ||
import { test, expect } from '@playwright/test'; | ||
import setup from '@leaphy-robotics/playwright-arduino'; | ||
|
||
test('test', async ({ page }) => { | ||
await setup(page); | ||
|
||
// Your test code | ||
... | ||
}); | ||
`` | ||
|
||
## Development | ||
|
||
### Building simulator | ||
This step is required to be performed at least once `yarn build:simavr` | ||
|
||
### Watching package | ||
You can watch for changes and automatically recompile the NPM Module using `yarn watch` | ||
|
||
### Using local package | ||
Link the module using `yarn link`, now use it in your (test) project using `yarn link @leaphy-robotics/playwright-arduino` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
set -e | ||
rm -rf build_assets build | ||
|
||
BUILD_PATH=$(realpath build) | ||
mkdir build_assets build && cd build_assets | ||
|
||
git clone https://github.com/buserror/simavr | ||
cd simavr | ||
|
||
make -j8 | ||
cd examples/board_simduino | ||
|
||
EXECUTABLE=$(find . | grep simduino.elf) | ||
cp ATmegaBOOT_168_atmega328.ihex "${EXECUTABLE}" "${BUILD_PATH}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"name": "@leaphy-robotics/playwright-arduino", | ||
"version": "1.0.0", | ||
"license": "LGPL-3.0-only", | ||
"main": "./dist/index.js", | ||
"type": "module", | ||
"types": "./dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"build" | ||
], | ||
"scripts": { | ||
"build": "tsup src", | ||
"watch": "tsup --watch src", | ||
"build:simavr": "./build.sh" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.12.7", | ||
"@types/serialport": "^8.0.5", | ||
"@types/w3c-web-serial": "^1.0.6", | ||
"playwright": "^1.43.0", | ||
"tsup": "^8.0.2", | ||
"tsx": "^4.7.2", | ||
"typescript": "^5.4.5" | ||
}, | ||
"dependencies": { | ||
"serialport": "^12.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { spawn } from 'child_process' | ||
import { ChildProcess } from "node:child_process"; | ||
|
||
class Board { | ||
private process: ChildProcess | ||
public port = '/tmp/simavr-uart0' | ||
|
||
constructor() { | ||
this.process = spawn('./simduino.elf', { | ||
cwd: `${import.meta.dirname}/../build` | ||
}) | ||
} | ||
|
||
public stop() { | ||
this.process.kill() | ||
} | ||
} | ||
|
||
export default Board |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import {SerialPort} from "serialport"; | ||
import {Page} from "playwright"; | ||
import Board from "./board.ts"; | ||
import {randomUUID} from "node:crypto"; | ||
import {clearTimeout} from "node:timers"; | ||
import * as fs from "node:fs"; | ||
|
||
type CallbackEvent = { | ||
resolve: (value: unknown) => void, | ||
type: string, | ||
args: any[] | ||
} | ||
|
||
declare module globalThis { | ||
let requestSerial: (options: SerialPortRequestOptions) => Promise<string>, | ||
openSerial: (id: string, options: SerialOptions) => Promise<void|Error>, | ||
readPort: (id: string) => Promise<number[]|Error>, | ||
readCallback: (data: number[]) => void, | ||
writePort: (id: string, data: number[]) => Promise<void|Error>, | ||
closePort: (id: string) => Promise<void|Error>, | ||
setSignals: (id: string, signals: SerialOutputSignals) => Promise<void|Error>, | ||
getPorts: () => Promise<string[]>, | ||
onDone: Record<string, (value: any) => void>, | ||
reader: ReadableStreamDefaultReader<CallbackEvent> | ||
} | ||
|
||
const ports: Record<string, SerialPort|null> = {} | ||
export default async function setup(page: Page) { | ||
const arduino = new Board() | ||
|
||
const methods: Record<string, (...args: any[]) => Promise<any>> = { | ||
async requestSerial(_page: Page, _options: SerialPortRequestOptions) { | ||
const id = randomUUID() | ||
ports[id] = null | ||
|
||
return String(id) | ||
}, | ||
async openSerial(_page: Page, id: string, options: SerialOptions) { | ||
if (ports[id]) throw new DOMException('Port already open!') | ||
|
||
return new Promise<void|Error>(resolve => { | ||
ports[id] = new SerialPort({ | ||
baudRate: options.baudRate, | ||
path: arduino.port, | ||
dataBits: 8, | ||
parity: 'none', | ||
stopBits: 1, | ||
}, err => { | ||
if (err) return resolve(err) | ||
resolve() | ||
}) | ||
}) | ||
}, | ||
async readPort(page: Page, id: string) { | ||
if (!ports[id]) throw new Error(`User read request to undefined port: ${id}`) | ||
|
||
const port = ports[id] as SerialPort | ||
try { | ||
let buffer: number[]|null = null | ||
let timeout: NodeJS.Timeout|null | ||
port.on('data', async (data: Buffer) => { | ||
if (!buffer) buffer = [] | ||
buffer.push(...Array.from(data.values())) | ||
|
||
if (timeout) clearTimeout(timeout) | ||
timeout = setTimeout(async () => { | ||
const copy = buffer | ||
buffer = null | ||
|
||
await page.evaluate((result) => { | ||
if (!result) return | ||
globalThis.readCallback(result) | ||
}, copy) | ||
}, 25) | ||
}) | ||
} catch (e) { | ||
return e | ||
} | ||
}, | ||
async writePort(_page: Page, id: string, data: number[]) { | ||
if (!ports[id]) throw new Error(`User write request to undefined port: ${id}`) | ||
|
||
const port = ports[id] as SerialPort | ||
return new Promise<void|Error>(resolve => { | ||
port.write(Buffer.from(data), err => { | ||
if (err) return resolve(err) | ||
resolve() | ||
}) | ||
}) | ||
}, | ||
async closePort(_page: Page, id: string) { | ||
if (!ports[id]) throw new Error(`User close request to undefined port: ${id}`) | ||
|
||
const port = ports[id] as SerialPort | ||
ports[id] = null | ||
return new Promise<void|Error>(resolve => port.close(err => { | ||
if (err) return resolve(err) | ||
resolve() | ||
})) | ||
}, | ||
setSignals(_page: Page, id: string, signals: SerialOutputSignals) { | ||
if (!ports[id]) throw new Error(`User setSignals request to undefined port: ${id}`) | ||
|
||
const port = ports[id] as SerialPort | ||
return new Promise<void|Error>(resolve => { | ||
port.set({ | ||
dtr: signals.dataTerminalReady, | ||
rts: signals.requestToSend, | ||
brk: signals.break | ||
}, () => { | ||
resolve() | ||
}) | ||
}) | ||
}, | ||
async getPorts(_page: Page) { | ||
return Array.from(Object.keys(ports)) | ||
} | ||
} | ||
|
||
await Promise.all(Object.entries(methods).map(async ([type, implementation]) => { | ||
await page.exposeFunction(type, (...args: any[]) => implementation(page, ...args)) | ||
})) | ||
|
||
await page.route('**/avrdude-worker.js', async route => { | ||
const response = await route.fetch(); | ||
const script = await response.text(); | ||
await route.fulfill({ response, body: `${fs.readFileSync(`${import.meta.dirname}/page.js`)}\n\n${script}` }); | ||
}); | ||
|
||
await page.addInitScript({ | ||
path: `${import.meta.dirname}/page.js` | ||
}) | ||
|
||
page.on('worker', async worker => { | ||
let open = true | ||
worker.on('close', () => open = false) | ||
while (open) { | ||
try { | ||
const action = await worker.evaluate(async () => { | ||
if (!globalThis.reader) return | ||
|
||
const {value, done} = await globalThis.reader.read() | ||
if (done || !value) return | ||
|
||
const execution = crypto.randomUUID() | ||
globalThis.onDone[execution] = value.resolve | ||
return { | ||
execution, | ||
type: value.type, | ||
args: value.args | ||
} | ||
}) | ||
|
||
if (!action) continue | ||
if (!methods[action.type]) continue | ||
|
||
methods[action.type](worker, ...action.args).then(async result => { | ||
await worker.evaluate(async ({ execution, result }) => { | ||
globalThis.onDone[execution](result) | ||
}, { | ||
execution: action.execution, | ||
result | ||
}) | ||
}) | ||
} catch { /* Once the worker has closed it will throw */ } | ||
} | ||
}) | ||
|
||
} |
Oops, something went wrong.