stm32f4-emu 0.1.0 → 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/README.md +37 -9
- package/cli.mjs +152 -0
- package/index.mjs +9 -1
- package/mcp/server.mjs +20 -0
- package/package.json +36 -5
- package/site/app.js +239 -45
- package/site/emulator.js +417 -16
- package/site/firmware.js +11 -0
- package/site/index.html +508 -148
- package/site/loaders.js +13 -7
- package/site/remote-emu.js +407 -0
- package/site/vendor/stm32_periph_wasm.d.ts +55 -0
- package/site/vendor/stm32_periph_wasm.js +87 -0
- package/site/vendor/stm32_periph_wasm_bg.wasm +0 -0
- package/site/vendor/stm32_periph_wasm_bg.wasm.d.ts +8 -0
- package/site/ws-bridge.mjs +409 -0
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
# STM32F4 Emulator
|
|
1
|
+
# STM32F4 Emulator (`stm32f4-emu`)
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/stm32f4-emu)
|
|
4
|
+
[](https://www.npmjs.com/package/stm32f4-emu)
|
|
5
|
+
[](LICENSE)
|
|
3
6
|
[](https://github.com/danish9661/stm32F4-emulator/actions/workflows/ci.yml)
|
|
4
7
|
[](https://github.com/danish9661/stm32F4-emulator/actions/workflows/pages.yml)
|
|
5
8
|
|
|
@@ -72,6 +75,10 @@ npm run serve # then open http://127.0.0.1:8123
|
|
|
72
75
|
# node end-to-end flow test (boot -> DHCP -> TCP -> HTTP, 2 rounds)
|
|
73
76
|
npm test # == node site/test_flow.mjs
|
|
74
77
|
|
|
78
|
+
# websocket bridge: headless Node serves the emulator, browser is a thin UI
|
|
79
|
+
npm run bridge -- blinky/blinky.bin --port 8234
|
|
80
|
+
# then open http://127.0.0.1:8123?bridge=ws://127.0.0.1:8234
|
|
81
|
+
|
|
75
82
|
# gateway-backed run: firmware talks to a REAL network stack (gVisor)
|
|
76
83
|
cd stm32-periph-wasm/pkg
|
|
77
84
|
node cli.mjs ../../eth_http/eth_http.bin 10000000 \
|
|
@@ -126,6 +133,29 @@ as a library stays dependency-free; only MCP users need
|
|
|
126
133
|
`npm i @modelcontextprotocol/sdk zod`. See [docs/mcp.md](docs/mcp.md) for
|
|
127
134
|
the tool reference and client config.
|
|
128
135
|
|
|
136
|
+
## WebSocket bridge (headless Node ↔ browser UI)
|
|
137
|
+
|
|
138
|
+
A binary WebSocket protocol so the browser console can drive the emulator
|
|
139
|
+
running headlessly in Node — all WASM execution stays in Node, the browser
|
|
140
|
+
is a thin UI. Zero impact on the existing local WASM path:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# 1. Start the bridge in Node (serves the emulator over WS on port 8234)
|
|
144
|
+
node site/ws-bridge.mjs eth_http/eth_http.bin --port 8234
|
|
145
|
+
|
|
146
|
+
# 2. Open the browser console with the bridge URL param
|
|
147
|
+
open "http://127.0.0.1:8123/?bridge=ws://127.0.0.1:8234"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The `RemoteEmu` adapter (`site/remote-emu.js`) is a drop-in replacement
|
|
151
|
+
for the local `emu` object — same `step()`/`drainUart()`/`read32()` API,
|
|
152
|
+
all proxied over binary WebSocket. Device stubs (OLED/TFT/etc.) run in
|
|
153
|
+
Node and are not visible to browser JS. Without `?fw=`, the page boots
|
|
154
|
+
whatever firmware the bridge was started with; with `?fw=blinky&bridge=ws://…`,
|
|
155
|
+
the browser sends the firmware image over the bridge.
|
|
156
|
+
|
|
157
|
+
See AGENTS.md §20 for the full binary protocol reference.
|
|
158
|
+
|
|
129
159
|
## Firmwares
|
|
130
160
|
|
|
131
161
|
| Firmware | What it does | Success marker |
|
|
@@ -246,6 +276,7 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
246
276
|
|
|
247
277
|
## Documentation
|
|
248
278
|
|
|
279
|
+
- [site/about.html](site/about.html) — in-repo About page: what it is, architecture, featured firmwares, and how to use it (CLI / browser / Node API / MCP).
|
|
249
280
|
- [docs/architecture.md](docs/architecture.md) — how the emulator is put
|
|
250
281
|
together (CPU, peripheral model, drivers, ETH flow, interrupts).
|
|
251
282
|
- [docs/peripherals.md](docs/peripherals.md) — all 33 peripherals and the
|
|
@@ -262,12 +293,9 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
262
293
|
- [docs/progress-and-future.md](docs/progress-and-future.md) — status,
|
|
263
294
|
known limitations, roadmap.
|
|
264
295
|
|
|
265
|
-
## License
|
|
266
|
-
|
|
267
|
-
GPL-3.0-only. See [LICENSE](LICENSE).
|
|
296
|
+
## License & Credits
|
|
268
297
|
|
|
269
|
-
|
|
270
|
-
[
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
package are new work built on that base.*
|
|
298
|
+
- **License**: GPL-3.0-only. See [LICENSE](LICENSE).
|
|
299
|
+
- **Unicorn CPU Core**: Powered by [Unicorn.js](https://github.com/AlexAltea/unicorn.js) by [Alex Altea](https://github.com/AlexAltea) (WASM/JS port of the [Unicorn Engine](https://www.unicorn-engine.org/) CPU emulator, derived from QEMU, licensed under GPLv2).
|
|
300
|
+
- **Heritage**: Fork and continuation of [nviennot/stm32-emulator](https://github.com/nviennot/stm32-emulator) (native SDL 3D printer emulator by Nicolas Viennot). The headless WASM peripheral model, networking stack, browser demo, virtual components API, MCP server, and npm package are new work built on that base.
|
|
301
|
+
- **DOOM**: Ported using [doomgeneric](https://github.com/ozkl/doomgeneric) by Ozkan Sezgin.
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// stm32f4-emu — headless CLI to load and run an STM32F4 firmware image.
|
|
3
|
+
//
|
|
4
|
+
// stm32f4-emu <firmware> [--inst N] [--format auto|bin|hex|elf] [--verbose]
|
|
5
|
+
// [--lowpower]
|
|
6
|
+
//
|
|
7
|
+
// Loads a .bin/.elf/.hex firmware, boots it in the emulator, and streams the
|
|
8
|
+
// guest UART to stdout. Peripheral register accesses are traced to stderr when
|
|
9
|
+
// --verbose is given. With --lowpower, the core halts on WFI/WFE and the
|
|
10
|
+
// emulator advances the virtual RTC until a wakeup source (e.g. an RTC alarm)
|
|
11
|
+
// fires — so firmware that enters STOP (e.g. deep_sleep_demo.bin) runs.
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { dirname, resolve } from 'node:path';
|
|
16
|
+
import * as bindings from './site/vendor/stm32_periph_wasm.js';
|
|
17
|
+
import { createEmulator } from './site/emulator.js';
|
|
18
|
+
import { parseIntelHex, parseElf } from './site/loaders.js';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const unicornFactory = require('./site/vendor/unicorn_arm.cjs');
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const svdXml = readFileSync(resolve(__dirname, 'site/vendor/stm32f407.svd'), 'utf8');
|
|
24
|
+
const wasmBytes = new Uint8Array(readFileSync(resolve(__dirname, 'site/vendor/stm32_periph_wasm_bg.wasm')));
|
|
25
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf8'));
|
|
26
|
+
|
|
27
|
+
const USAGE = `stm32f4-emu — run an STM32F4 firmware image in the emulator
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
stm32f4-emu <firmware> [options]
|
|
31
|
+
|
|
32
|
+
Arguments:
|
|
33
|
+
<firmware> path to a firmware image (.bin, .elf, or .hex)
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-n, --inst <N> instruction budget to run (default 20000000)
|
|
37
|
+
-f, --format <fmt> firmware format: auto|bin|hex|elf (default auto)
|
|
38
|
+
-v, --verbose trace peripheral register reads/writes to stderr
|
|
39
|
+
-l, --lowpower halt on WFI/WFE and advance the virtual RTC until wakeup
|
|
40
|
+
-h, --help show this help
|
|
41
|
+
-V, --version show version
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
stm32f4-emu build/firmware.bin
|
|
45
|
+
stm32f4-emu app.elf --inst 5000000 --verbose
|
|
46
|
+
stm32f4-emu deep_sleep_demo.bin --lowpower
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
function fail(msg) {
|
|
50
|
+
process.stderr.write(msg + '\n\n' + USAGE);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function detectFormat(buf, format) {
|
|
55
|
+
if (format !== 'auto') return format;
|
|
56
|
+
if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) return 'elf';
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (i < buf.length && (buf[i] === 0x20 || buf[i] === 0x09 || buf[i] === 0x0D || buf[i] === 0x0A)) i++;
|
|
59
|
+
if (buf[i] === 0x3A) return 'hex'; // ':'
|
|
60
|
+
return 'bin';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
let firmwarePath = null;
|
|
66
|
+
let inst = 20000000;
|
|
67
|
+
let format = 'auto';
|
|
68
|
+
let verbose = false;
|
|
69
|
+
let lowpower = false;
|
|
70
|
+
for (let i = 0; i < args.length; i++) {
|
|
71
|
+
const a = args[i];
|
|
72
|
+
if (a === '-h' || a === '--help') { process.stdout.write(USAGE); process.exit(0); }
|
|
73
|
+
if (a === '-V' || a === '--version') { process.stdout.write(`stm32f4-emu ${pkg.version}\n`); process.exit(0); }
|
|
74
|
+
if (a === '-v' || a === '--verbose') { verbose = true; continue; }
|
|
75
|
+
if (a === '-l' || a === '--lowpower') { lowpower = true; continue; }
|
|
76
|
+
if (a === '-n' || a === '--inst') {
|
|
77
|
+
const v = args[++i];
|
|
78
|
+
inst = Number(v);
|
|
79
|
+
if (!Number.isFinite(inst) || inst <= 0) fail(`invalid --inst value: ${v}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (a === '-f' || a === '--format') {
|
|
83
|
+
format = args[++i];
|
|
84
|
+
if (!['auto', 'bin', 'hex', 'elf'].includes(format)) fail(`invalid --format: ${format}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (a.startsWith('-')) fail(`unknown option: ${a}`);
|
|
88
|
+
if (firmwarePath) fail('multiple firmware paths given');
|
|
89
|
+
firmwarePath = a;
|
|
90
|
+
}
|
|
91
|
+
if (!firmwarePath) fail('no firmware path given');
|
|
92
|
+
|
|
93
|
+
let raw;
|
|
94
|
+
try {
|
|
95
|
+
raw = new Uint8Array(readFileSync(firmwarePath));
|
|
96
|
+
} catch (e) {
|
|
97
|
+
fail(`cannot read firmware '${firmwarePath}': ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const fmt = detectFormat(raw, format);
|
|
101
|
+
let firmware, extra_mem = [];
|
|
102
|
+
try {
|
|
103
|
+
if (fmt === 'elf') {
|
|
104
|
+
const elf = parseElf(raw);
|
|
105
|
+
if (!elf.flash) fail('ELF contains no FLASH segment (expected a segment at 0x08000000)');
|
|
106
|
+
firmware = elf.flash;
|
|
107
|
+
extra_mem = elf.extraMem || [];
|
|
108
|
+
} else if (fmt === 'hex') {
|
|
109
|
+
const hex = parseIntelHex(Buffer.from(raw).toString('latin1'));
|
|
110
|
+
if (!hex.flash) fail('Intel HEX contains no FLASH records (expected data in the 0x08000000 range)');
|
|
111
|
+
firmware = hex.flash;
|
|
112
|
+
} else {
|
|
113
|
+
firmware = raw;
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
fail(`failed to parse ${fmt} firmware: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let emu;
|
|
120
|
+
try {
|
|
121
|
+
emu = await createEmulator({
|
|
122
|
+
firmware, bindings, unicorn: unicornFactory, svdXml, wasmInit: wasmBytes,
|
|
123
|
+
extra_mem, verbose, lowpower,
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
fail(`emulator failed to load firmware: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const STEP = 100000;
|
|
130
|
+
let remaining = inst;
|
|
131
|
+
let lastInst = 0;
|
|
132
|
+
try {
|
|
133
|
+
while (remaining > 0) {
|
|
134
|
+
const take = Math.min(STEP, remaining);
|
|
135
|
+
const r = emu.step(take);
|
|
136
|
+
remaining -= (r.instCount - lastInst);
|
|
137
|
+
lastInst = r.instCount;
|
|
138
|
+
const u = emu.drainUart();
|
|
139
|
+
if (u && u.length) process.stdout.write(u.toString());
|
|
140
|
+
if (r.stopped) break;
|
|
141
|
+
}
|
|
142
|
+
const tail = emu.drainUart();
|
|
143
|
+
if (tail && tail.length) process.stdout.write(tail.toString());
|
|
144
|
+
} finally {
|
|
145
|
+
try { emu.close(); } catch {}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
main().catch((e) => {
|
|
150
|
+
process.stderr.write(`stm32f4-emu: ${e.message}\n`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
package/index.mjs
CHANGED
|
@@ -8,6 +8,14 @@ import { createEmulator } from './site/emulator.js';
|
|
|
8
8
|
import { createNetSim } from './site/netsim.js';
|
|
9
9
|
import { FIRMWARES } from './site/firmware.js';
|
|
10
10
|
import { LED, Button, Pwm, I2cRegisterDevice, Potentiometer } from './site/components.js';
|
|
11
|
+
import { STM32F4, GPIOPin, USART, DMAStream } from './site/stm32f4.js';
|
|
12
|
+
|
|
13
|
+
// Node consumers get a one-call setup that injects the bundled assets. The
|
|
14
|
+
// `firmware` option is optional (defer to loadBin/loadHex/loadELF after
|
|
15
|
+
// create), matching the rp2040js / avr8js ergonomics.
|
|
16
|
+
STM32F4.create = (opts = {}) => STM32F4._create({
|
|
17
|
+
bindings, unicorn: unicornFactory, svdXml, wasmInit: wasmBytes, ...opts,
|
|
18
|
+
});
|
|
11
19
|
|
|
12
20
|
const require = createRequire(import.meta.url);
|
|
13
21
|
const unicornFactory = require('./site/vendor/unicorn_arm.cjs');
|
|
@@ -36,4 +44,4 @@ export async function createSTM32F407(opts = {}) {
|
|
|
36
44
|
return createEmulator({ ...opts, firmware: bin, bindings, unicorn: unicornFactory, svdXml, wasmInit: wasmBytes });
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
export { createEmulator, createNetSim, FIRMWARES, bindings, unicornFactory, svdXml, LED, Button, Pwm, I2cRegisterDevice, Potentiometer };
|
|
47
|
+
export { createEmulator, createNetSim, FIRMWARES, bindings, unicornFactory, svdXml, LED, Button, Pwm, I2cRegisterDevice, Potentiometer, STM32F4, GPIOPin, USART, DMAStream };
|
package/mcp/server.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
// MCP server exposing the STM32F407 emulator as tools an MCP client
|
|
3
4
|
// (Claude Code / Claude Desktop / any MCP host) can drive: load firmware,
|
|
4
5
|
// step execution, read/write UART, poke and watch GPIO pins, inject ADC
|
|
@@ -31,6 +32,25 @@ try {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
let session = null; // { emu, firmware, components: Map }
|
|
35
|
+
|
|
36
|
+
// ── CLI surface for the bin: stm32f4-mcp --help / --version ──
|
|
37
|
+
// Printed before the stdio MCP transport starts (stdout is the JSON-RPC
|
|
38
|
+
// channel once running, so we must exit before connecting).
|
|
39
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
'stm32f4-mcp — MCP server exposing the STM32F407 emulator.\n\n' +
|
|
42
|
+
'Usage: stm32f4-mcp [--help] [--version]\n\n' +
|
|
43
|
+
'Speaks MCP over stdio (JSON-RPC). Connect it from an MCP client such\n' +
|
|
44
|
+
'as Claude Code or Claude Desktop. Optional peer deps:\n' +
|
|
45
|
+
' npm install @modelcontextprotocol/sdk zod\n'
|
|
46
|
+
);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
if (process.argv.includes('--version') || process.argv.includes('-V')) {
|
|
50
|
+
const ver = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
51
|
+
process.stdout.write(`stm32f4-mcp ${ver}\n`);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
34
54
|
let componentSeq = 0;
|
|
35
55
|
|
|
36
56
|
const text = (s) => ({ content: [{ type: 'text', text: typeof s === 'string' ? s : JSON.stringify(s, null, 2) }] });
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stm32f4-emu",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "STM32F407 emulator: Unicorn (WASM) CPU + Rust peripheral model. Runs real Cortex-M4 firmware in Node or the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"bin": {
|
|
8
|
-
"stm32f4-
|
|
8
|
+
"stm32f4-emu": "./cli.mjs",
|
|
9
|
+
"stm32f4-mcp": "./mcp/server.mjs",
|
|
10
|
+
"stm32f4-bridge": "./site/ws-bridge.mjs"
|
|
9
11
|
},
|
|
10
12
|
"exports": {
|
|
11
13
|
".": "./index.mjs",
|
|
@@ -14,12 +16,16 @@
|
|
|
14
16
|
"./netsim": "./site/netsim.js",
|
|
15
17
|
"./firmwares": "./site/firmware.js",
|
|
16
18
|
"./loaders": "./site/loaders.js",
|
|
19
|
+
"./stm32f4": "./site/stm32f4.js",
|
|
17
20
|
"./vendor": "./site/vendor/stm32_periph_wasm.js",
|
|
18
21
|
"./vendor/unicorn": "./site/vendor/unicorn_arm.cjs",
|
|
22
|
+
"./remote": "./site/remote-emu.js",
|
|
23
|
+
"./ws-bridge": "./site/ws-bridge.mjs",
|
|
19
24
|
"./site": "./site/index.html"
|
|
20
25
|
},
|
|
21
26
|
"files": [
|
|
22
27
|
"index.mjs",
|
|
28
|
+
"cli.mjs",
|
|
23
29
|
"README.md",
|
|
24
30
|
"site/emulator.js",
|
|
25
31
|
"site/components.js",
|
|
@@ -29,6 +35,8 @@
|
|
|
29
35
|
"site/loaders.js",
|
|
30
36
|
"site/index.html",
|
|
31
37
|
"site/app.js",
|
|
38
|
+
"site/remote-emu.js",
|
|
39
|
+
"site/ws-bridge.mjs",
|
|
32
40
|
"site/vendor/"
|
|
33
41
|
],
|
|
34
42
|
"keywords": [
|
|
@@ -55,29 +63,52 @@
|
|
|
55
63
|
"node": ">=20"
|
|
56
64
|
},
|
|
57
65
|
"scripts": {
|
|
58
|
-
"test": "node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs",
|
|
66
|
+
"test": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs && node site/test_qspi_cdp.mjs && node site/test_browser.mjs",
|
|
67
|
+
"test:unit": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs",
|
|
68
|
+
"test:bridge": "node site/test_ws_bridge.mjs",
|
|
69
|
+
"test:bridge:browser": "node site/test_bridge_cdp.mjs",
|
|
59
70
|
"test:flow": "node site/test_flow.mjs",
|
|
60
71
|
"test:blinky": "node site/test_blinky.mjs",
|
|
61
72
|
"test:rx": "node site/test_rx_interrupt.mjs",
|
|
62
73
|
"test:multi": "node site/test_multi_instance.mjs",
|
|
74
|
+
"test:can": "node site/test_candemo.mjs",
|
|
75
|
+
"test:caninject": "node site/test_can_inject.mjs",
|
|
76
|
+
"test:watchdog": "node site/test_watchdog.mjs",
|
|
77
|
+
"test:wwdg": "node site/test_wwdg.mjs",
|
|
78
|
+
"test:wwdgwin": "node site/test_wwdg_window.mjs",
|
|
79
|
+
"test:timcap": "node site/test_tim_capture.mjs",
|
|
80
|
+
"test:lowpower": "node site/test_lowpower.mjs",
|
|
81
|
+
"test:qspi": "node site/test_qspi.mjs",
|
|
82
|
+
"test:qspi:browser": "node site/test_qspi_cdp.mjs",
|
|
83
|
+
"test:browser": "node site/test_browser.mjs",
|
|
84
|
+
"test:edge": "node site/test_edge_cases.mjs",
|
|
63
85
|
"test:fsmc": "node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/test_fsmc_dcmi.mjs",
|
|
64
86
|
"test:components": "node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs",
|
|
65
87
|
"test:mcp": "node mcp/test_mcp.mjs",
|
|
66
88
|
"mcp": "node mcp/server.mjs",
|
|
89
|
+
"bridge": "node site/ws-bridge.mjs",
|
|
67
90
|
"firmwares": "node tools/make_firmware.mjs",
|
|
68
91
|
"serve": "python3 -m http.server 8123 --directory site",
|
|
69
92
|
"prepack": "node tools/make_firmware.mjs"
|
|
70
93
|
},
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"ws": "^8.21.3"
|
|
96
|
+
},
|
|
71
97
|
"peerDependencies": {
|
|
72
98
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
73
99
|
"zod": "^3.25 || ^4.0"
|
|
74
100
|
},
|
|
75
101
|
"peerDependenciesMeta": {
|
|
76
|
-
"@modelcontextprotocol/sdk": {
|
|
77
|
-
|
|
102
|
+
"@modelcontextprotocol/sdk": {
|
|
103
|
+
"optional": true
|
|
104
|
+
},
|
|
105
|
+
"zod": {
|
|
106
|
+
"optional": true
|
|
107
|
+
}
|
|
78
108
|
},
|
|
79
109
|
"devDependencies": {
|
|
80
110
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
111
|
+
"playwright": "^1.62.1",
|
|
81
112
|
"zod": "^3.25 || ^4.0"
|
|
82
113
|
}
|
|
83
114
|
}
|