@typecad/framework-zephyr 1.0.0-alpha.8
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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/async/timer-polyfill.d.ts +10 -0
- package/dist/async/timer-polyfill.js +95 -0
- package/dist/chips/controllers.d.ts +21 -0
- package/dist/chips/controllers.js +66 -0
- package/dist/chips/esp32.d.ts +2 -0
- package/dist/chips/esp32.js +59 -0
- package/dist/chips/esp32s3.d.ts +2 -0
- package/dist/chips/esp32s3.js +54 -0
- package/dist/chips/index.d.ts +15 -0
- package/dist/chips/index.js +45 -0
- package/dist/chips/resolve.d.ts +9 -0
- package/dist/chips/resolve.js +121 -0
- package/dist/chips/types.d.ts +154 -0
- package/dist/chips/types.js +14 -0
- package/dist/chips/xiao-ble.d.ts +2 -0
- package/dist/chips/xiao-ble.js +61 -0
- package/dist/debug-codegen.d.ts +33 -0
- package/dist/debug-codegen.js +162 -0
- package/dist/display/gfx.d.ts +17 -0
- package/dist/display/gfx.js +171 -0
- package/dist/display/index.d.ts +20 -0
- package/dist/display/index.js +53 -0
- package/dist/display/profiles.d.ts +25 -0
- package/dist/display/profiles.js +41 -0
- package/dist/display/touch-adapter.d.ts +9 -0
- package/dist/display/touch-adapter.js +95 -0
- package/dist/display/ui-adapter.d.ts +21 -0
- package/dist/display/ui-adapter.js +534 -0
- package/dist/dt-config/kconfig.d.ts +27 -0
- package/dist/dt-config/kconfig.js +283 -0
- package/dist/dt-config/overlay.d.ts +35 -0
- package/dist/dt-config/overlay.js +246 -0
- package/dist/framework.manifest.d.ts +98 -0
- package/dist/framework.manifest.js +525 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -0
- package/dist/lowering/adc.d.ts +19 -0
- package/dist/lowering/adc.js +76 -0
- package/dist/lowering/ble.d.ts +20 -0
- package/dist/lowering/ble.js +419 -0
- package/dist/lowering/board.d.ts +9 -0
- package/dist/lowering/board.js +33 -0
- package/dist/lowering/gpio.d.ts +12 -0
- package/dist/lowering/gpio.js +123 -0
- package/dist/lowering/http.d.ts +23 -0
- package/dist/lowering/http.js +482 -0
- package/dist/lowering/i2c.d.ts +15 -0
- package/dist/lowering/i2c.js +114 -0
- package/dist/lowering/index.d.ts +29 -0
- package/dist/lowering/index.js +93 -0
- package/dist/lowering/interrupts.d.ts +21 -0
- package/dist/lowering/interrupts.js +91 -0
- package/dist/lowering/mqtt.d.ts +19 -0
- package/dist/lowering/mqtt.js +366 -0
- package/dist/lowering/power.d.ts +9 -0
- package/dist/lowering/power.js +64 -0
- package/dist/lowering/preferences.d.ts +23 -0
- package/dist/lowering/preferences.js +345 -0
- package/dist/lowering/pulse.d.ts +7 -0
- package/dist/lowering/pulse.js +82 -0
- package/dist/lowering/pwm.d.ts +15 -0
- package/dist/lowering/pwm.js +59 -0
- package/dist/lowering/random.d.ts +18 -0
- package/dist/lowering/random.js +129 -0
- package/dist/lowering/spi.d.ts +17 -0
- package/dist/lowering/spi.js +113 -0
- package/dist/lowering/timing.d.ts +9 -0
- package/dist/lowering/timing.js +63 -0
- package/dist/lowering/tone.d.ts +10 -0
- package/dist/lowering/tone.js +50 -0
- package/dist/lowering/uart.d.ts +15 -0
- package/dist/lowering/uart.js +102 -0
- package/dist/lowering/util.d.ts +4 -0
- package/dist/lowering/util.js +12 -0
- package/dist/lowering/wdt.d.ts +15 -0
- package/dist/lowering/wdt.js +80 -0
- package/dist/lowering/wifi.d.ts +21 -0
- package/dist/lowering/wifi.js +447 -0
- package/dist/lowering/worker-backing.d.ts +14 -0
- package/dist/lowering/worker-backing.js +79 -0
- package/dist/lowering/worker.d.ts +6 -0
- package/dist/lowering/worker.js +14 -0
- package/dist/strategy.d.ts +168 -0
- package/dist/strategy.js +1094 -0
- package/dist/toolchain/debug-config.d.ts +82 -0
- package/dist/toolchain/debug-config.js +359 -0
- package/dist/toolchain/index.d.ts +73 -0
- package/dist/toolchain/index.js +427 -0
- package/dist/toolchain/scaffold.d.ts +15 -0
- package/dist/toolchain/scaffold.js +196 -0
- package/dist/toolchain/west-discover.d.ts +40 -0
- package/dist/toolchain/west-discover.js +203 -0
- package/dist/toolchain/west-spawn.d.ts +39 -0
- package/dist/toolchain/west-spawn.js +117 -0
- package/package.json +82 -0
- package/src/async/timer-polyfill.ts +107 -0
- package/src/chips/controllers.ts +74 -0
- package/src/chips/esp32.ts +62 -0
- package/src/chips/esp32s3.ts +57 -0
- package/src/chips/index.ts +54 -0
- package/src/chips/resolve.ts +146 -0
- package/src/chips/types.ts +163 -0
- package/src/chips/xiao-ble.ts +64 -0
- package/src/debug-codegen.ts +207 -0
- package/src/display/gfx.ts +190 -0
- package/src/display/index.ts +70 -0
- package/src/display/profiles.ts +63 -0
- package/src/display/touch-adapter.ts +100 -0
- package/src/display/ui-adapter.ts +558 -0
- package/src/dt-config/kconfig.ts +308 -0
- package/src/dt-config/overlay.ts +293 -0
- package/src/framework.manifest.ts +545 -0
- package/src/index.ts +23 -0
- package/src/lowering/adc.ts +102 -0
- package/src/lowering/ble.ts +426 -0
- package/src/lowering/board.ts +38 -0
- package/src/lowering/gpio.ts +148 -0
- package/src/lowering/http.ts +488 -0
- package/src/lowering/i2c.ts +126 -0
- package/src/lowering/index.ts +86 -0
- package/src/lowering/interrupts.ts +114 -0
- package/src/lowering/mqtt.ts +371 -0
- package/src/lowering/power.ts +70 -0
- package/src/lowering/preferences.ts +354 -0
- package/src/lowering/pulse.ts +93 -0
- package/src/lowering/pwm.ts +74 -0
- package/src/lowering/random.ts +135 -0
- package/src/lowering/spi.ts +124 -0
- package/src/lowering/timing.ts +72 -0
- package/src/lowering/tone.ts +60 -0
- package/src/lowering/uart.ts +111 -0
- package/src/lowering/util.ts +12 -0
- package/src/lowering/wdt.ts +84 -0
- package/src/lowering/wifi.ts +452 -0
- package/src/lowering/worker-backing.ts +94 -0
- package/src/lowering/worker.ts +17 -0
- package/src/strategy.ts +1265 -0
- package/src/toolchain/debug-config.ts +399 -0
- package/src/toolchain/index.ts +459 -0
- package/src/toolchain/scaffold.ts +196 -0
- package/src/toolchain/west-discover.ts +233 -0
- package/src/toolchain/west-spawn.ts +141 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A discovered, usable west installation. `mode` tells the caller how to
|
|
3
|
+
* invoke it: 'launcher' = call `westExecutable` directly; 'module' = call
|
|
4
|
+
* `pythonExecutable -m west`.
|
|
5
|
+
*/
|
|
6
|
+
export interface WestInstall {
|
|
7
|
+
mode: 'launcher' | 'module';
|
|
8
|
+
/** Absolute path to a `west` launcher (mode 'launcher') or undefined. */
|
|
9
|
+
westExecutable?: string;
|
|
10
|
+
/** Absolute path to a Python interpreter with west installed (mode 'module'). */
|
|
11
|
+
pythonExecutable?: string;
|
|
12
|
+
/** Absolute path to the Zephyr SDK root (for $ZEPHYR_BASE), if found. */
|
|
13
|
+
zephyrBase?: string;
|
|
14
|
+
/** Which discovery strategy found this install. */
|
|
15
|
+
source: 'path' | 'zephyr-base-venv' | 'well-known' | 'system-python';
|
|
16
|
+
}
|
|
17
|
+
/** True if `dir` looks like a Zephyr SDK root: has CMakeLists.txt and the
|
|
18
|
+
* kernel header. */
|
|
19
|
+
export declare function isZephyrBase(dir: string): boolean;
|
|
20
|
+
export declare function discoverFromPath(): WestInstall | null;
|
|
21
|
+
/** The canonical Zephyr workspace layout puts the venv beside the SDK:
|
|
22
|
+
* <workspace>/{.venv, zephyr}. So ${ZEPHYR_BASE}/../.venv is the venv. */
|
|
23
|
+
export declare function discoverFromZephyrBase(): WestInstall | null;
|
|
24
|
+
/** Candidate Zephyr workspace directories. Each may contain both `.venv/`
|
|
25
|
+
* and `zephyr/` (the SDK). Exported for test injection. */
|
|
26
|
+
export declare function wellKnownWorkspaces(): string[];
|
|
27
|
+
export declare function discoverFromWellKnown(workspaces?: string[]): WestInstall | null;
|
|
28
|
+
/** Candidate system Python interpreters to probe with `-m west`. */
|
|
29
|
+
export declare function systemPythons(): string[];
|
|
30
|
+
export declare function discoverFromSystemPython(pythons?: string[]): WestInstall | null;
|
|
31
|
+
/** Clear the process-local discovery cache (for tests). */
|
|
32
|
+
export declare function resetWestDiscoveryCache(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Try each discovery strategy in order. The first usable install wins.
|
|
35
|
+
* Result is memoized for the process lifetime (west installs don't move).
|
|
36
|
+
*
|
|
37
|
+
* Order: PATH → $ZEPHYR_BASE venv → well-known workspaces → system pythons.
|
|
38
|
+
* Returns null when no usable west install is found.
|
|
39
|
+
*/
|
|
40
|
+
export declare function discoverWest(): WestInstall | null;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west discovery — find a usable `west` (and the Zephyr SDK / ZEPHYR_BASE)
|
|
3
|
+
//
|
|
4
|
+
// west installs into a Python venv that must be activated before `west` is on
|
|
5
|
+
// PATH. We resolve a working invocation WITHOUT requiring the user to have
|
|
6
|
+
// activated the venv, by preferring the robust `<python> -m west` form: it
|
|
7
|
+
// sidesteps shebang-launcher fragility on Windows and works with any venv
|
|
8
|
+
// once we know which Python interpreter has west installed.
|
|
9
|
+
//
|
|
10
|
+
// Discovery cascade (first usable wins):
|
|
11
|
+
// 1. `west` already on PATH (env already activated / global install).
|
|
12
|
+
// 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
|
|
13
|
+
// 3. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
|
|
14
|
+
// venv, etc.
|
|
15
|
+
// 4. System pythons (`python`, `python3`, `py`) via `-m west`.
|
|
16
|
+
//
|
|
17
|
+
// Leaner than the ESP-IDF equivalent: west needs no env sourcing (no 15s
|
|
18
|
+
// export.sh) — only the right interpreter + ZEPHYR_BASE.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
import { existsSync } from 'node:fs';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
const IS_WIN = process.platform === 'win32';
|
|
25
|
+
/** The Python executable name inside a venv's bin/ (POSIX) or Scripts/ (Win). */
|
|
26
|
+
function venvPython(venvDir) {
|
|
27
|
+
return join(venvDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'python.exe' : 'python');
|
|
28
|
+
}
|
|
29
|
+
/** True if `exe` runs `python -m west --version` successfully. */
|
|
30
|
+
function pythonRunsWest(exe) {
|
|
31
|
+
try {
|
|
32
|
+
const r = spawnSync(exe, ['-m', 'west', '--version'], {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
timeout: 15_000,
|
|
35
|
+
windowsHide: true,
|
|
36
|
+
});
|
|
37
|
+
return r.status === 0;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** True if `cmd` runs `west --version` successfully. */
|
|
44
|
+
function westOnPath(cmd) {
|
|
45
|
+
try {
|
|
46
|
+
const r = spawnSync(cmd, ['--version'], {
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
timeout: 15_000,
|
|
49
|
+
shell: IS_WIN,
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
return r.status === 0;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** True if `dir` looks like a Zephyr SDK root: has CMakeLists.txt and the
|
|
59
|
+
* kernel header. */
|
|
60
|
+
export function isZephyrBase(dir) {
|
|
61
|
+
if (!dir)
|
|
62
|
+
return false;
|
|
63
|
+
return (existsSync(join(dir, 'CMakeLists.txt')) &&
|
|
64
|
+
existsSync(join(dir, 'include', 'zephyr', 'kernel.h')));
|
|
65
|
+
}
|
|
66
|
+
// ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
|
|
67
|
+
export function discoverFromPath() {
|
|
68
|
+
const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
|
|
69
|
+
encoding: 'utf8',
|
|
70
|
+
shell: true,
|
|
71
|
+
windowsHide: true,
|
|
72
|
+
});
|
|
73
|
+
if (which.status !== 0)
|
|
74
|
+
return null;
|
|
75
|
+
const lines = (which.stdout ?? '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (!existsSync(line))
|
|
78
|
+
continue;
|
|
79
|
+
if (!westOnPath(line))
|
|
80
|
+
continue;
|
|
81
|
+
return {
|
|
82
|
+
mode: 'launcher',
|
|
83
|
+
westExecutable: line,
|
|
84
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
85
|
+
source: 'path',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
// ── Strategy 2: $ZEPHYR_BASE sibling venv ───────────────────────────────────
|
|
91
|
+
/** The canonical Zephyr workspace layout puts the venv beside the SDK:
|
|
92
|
+
* <workspace>/{.venv, zephyr}. So ${ZEPHYR_BASE}/../.venv is the venv. */
|
|
93
|
+
export function discoverFromZephyrBase() {
|
|
94
|
+
const zb = process.env.ZEPHYR_BASE;
|
|
95
|
+
if (!zb || !isZephyrBase(zb))
|
|
96
|
+
return null;
|
|
97
|
+
const workspaceDir = dirname(zb);
|
|
98
|
+
const venvDir = join(workspaceDir, '.venv');
|
|
99
|
+
const py = venvPython(venvDir);
|
|
100
|
+
if (!existsSync(py) || !pythonRunsWest(py))
|
|
101
|
+
return null;
|
|
102
|
+
return {
|
|
103
|
+
mode: 'module',
|
|
104
|
+
pythonExecutable: py,
|
|
105
|
+
zephyrBase: zb,
|
|
106
|
+
source: 'zephyr-base-venv',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ── Strategy 3: well-known workspace layouts ───────────────────────────────
|
|
110
|
+
/** Candidate Zephyr workspace directories. Each may contain both `.venv/`
|
|
111
|
+
* and `zephyr/` (the SDK). Exported for test injection. */
|
|
112
|
+
export function wellKnownWorkspaces() {
|
|
113
|
+
const home = homedir();
|
|
114
|
+
if (IS_WIN) {
|
|
115
|
+
return [
|
|
116
|
+
join(home, 'zephyrproject'),
|
|
117
|
+
join(home, 'zephyr'),
|
|
118
|
+
'C:\\zephyrproject',
|
|
119
|
+
'C:\\zephyr',
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
return [
|
|
123
|
+
join(home, 'zephyrproject'),
|
|
124
|
+
join(home, 'zephyr'),
|
|
125
|
+
'/opt/zephyrproject',
|
|
126
|
+
'/opt/zephyr',
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
export function discoverFromWellKnown(workspaces = wellKnownWorkspaces()) {
|
|
130
|
+
for (const ws of workspaces) {
|
|
131
|
+
const venvDir = join(ws, '.venv');
|
|
132
|
+
const py = venvPython(venvDir);
|
|
133
|
+
if (!existsSync(py) || !pythonRunsWest(py))
|
|
134
|
+
continue;
|
|
135
|
+
// Resolve ZEPHYR_BASE if the SDK sits beside the venv.
|
|
136
|
+
const zb = join(ws, 'zephyr');
|
|
137
|
+
return {
|
|
138
|
+
mode: 'module',
|
|
139
|
+
pythonExecutable: py,
|
|
140
|
+
zephyrBase: isZephyrBase(zb) ? zb : undefined,
|
|
141
|
+
source: 'well-known',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// ── Strategy 4: system pythons via `-m west` ────────────────────────────────
|
|
147
|
+
/** Candidate system Python interpreters to probe with `-m west`. */
|
|
148
|
+
export function systemPythons() {
|
|
149
|
+
if (IS_WIN)
|
|
150
|
+
return ['py', 'python', 'python3'];
|
|
151
|
+
return ['python3', 'python'];
|
|
152
|
+
}
|
|
153
|
+
export function discoverFromSystemPython(pythons = systemPythons()) {
|
|
154
|
+
for (const py of pythons) {
|
|
155
|
+
if (!pythonRunsWest(py))
|
|
156
|
+
continue;
|
|
157
|
+
return {
|
|
158
|
+
mode: 'module',
|
|
159
|
+
pythonExecutable: py,
|
|
160
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
161
|
+
source: 'system-python',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
// ── Top-level cascade ────────────────────────────────────────────────────────
|
|
167
|
+
let cachedDiscover;
|
|
168
|
+
/** Clear the process-local discovery cache (for tests). */
|
|
169
|
+
export function resetWestDiscoveryCache() {
|
|
170
|
+
cachedDiscover = undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Try each discovery strategy in order. The first usable install wins.
|
|
174
|
+
* Result is memoized for the process lifetime (west installs don't move).
|
|
175
|
+
*
|
|
176
|
+
* Order: PATH → $ZEPHYR_BASE venv → well-known workspaces → system pythons.
|
|
177
|
+
* Returns null when no usable west install is found.
|
|
178
|
+
*/
|
|
179
|
+
export function discoverWest() {
|
|
180
|
+
if (cachedDiscover !== undefined)
|
|
181
|
+
return cachedDiscover;
|
|
182
|
+
const strategies = [
|
|
183
|
+
discoverFromPath,
|
|
184
|
+
discoverFromZephyrBase,
|
|
185
|
+
discoverFromWellKnown,
|
|
186
|
+
discoverFromSystemPython,
|
|
187
|
+
];
|
|
188
|
+
for (const strat of strategies) {
|
|
189
|
+
let install = null;
|
|
190
|
+
try {
|
|
191
|
+
install = strat();
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
install = null;
|
|
195
|
+
}
|
|
196
|
+
if (install) {
|
|
197
|
+
cachedDiscover = install;
|
|
198
|
+
return install;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
cachedDiscover = null;
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SpawnSyncOptions } from 'node:child_process';
|
|
2
|
+
import { type WestInstall } from './west-discover.js';
|
|
3
|
+
export interface WestInvocation {
|
|
4
|
+
/** The command to spawnSync: a `west` launcher or a Python interpreter. */
|
|
5
|
+
command: string;
|
|
6
|
+
/** Args for the command (includes `-m west` when in module mode). */
|
|
7
|
+
args: string[];
|
|
8
|
+
/** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
|
|
9
|
+
* ZEPHYR_BASE when discovered. */
|
|
10
|
+
options: SpawnSyncOptions;
|
|
11
|
+
/** The install the invocation was built from, for surfacing to the user. */
|
|
12
|
+
install: WestInstall;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
|
|
16
|
+
* when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
|
|
17
|
+
* PATH when west was found via a venv Python.
|
|
18
|
+
*
|
|
19
|
+
* The PATH prepend matters: `west flash` shells out to bare runner tools
|
|
20
|
+
* (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
|
|
21
|
+
* PATH. Without the prepend, the user's PATH may surface a *different* tool
|
|
22
|
+
* ahead of the venv's — e.g. an older esptool whose argument spelling is
|
|
23
|
+
* incompatible with the runner. Putting the venv's bin first makes west's
|
|
24
|
+
* delegated subprocesses resolve to the same versions west itself runs under.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildEnv(install: WestInstall): NodeJS.ProcessEnv;
|
|
27
|
+
/**
|
|
28
|
+
* Build a spawn invocation that runs `west <westArgs>`.
|
|
29
|
+
*
|
|
30
|
+
* `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
|
|
31
|
+
* westSpawn preserves them and injects the discovery env. The returned object
|
|
32
|
+
* destructures directly into spawnSync:
|
|
33
|
+
*
|
|
34
|
+
* const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
|
|
35
|
+
* spawnSync(inv.command, inv.args, inv.options);
|
|
36
|
+
*
|
|
37
|
+
* Throws a clear, actionable error when no usable west install is discovered.
|
|
38
|
+
*/
|
|
39
|
+
export declare function westSpawn(westArgs: string[], baseOptions: SpawnSyncOptions): WestInvocation;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west spawn helper — build a spawnSync invocation for `west <args>`
|
|
3
|
+
//
|
|
4
|
+
// Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
|
|
5
|
+
// produce {command, args, env} that the toolchain destructures into spawnSync.
|
|
6
|
+
//
|
|
7
|
+
// Two modes:
|
|
8
|
+
// - 'launcher': spawn the `west` launcher directly (mode 'launcher').
|
|
9
|
+
// - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
|
|
10
|
+
// robust cross-platform form — it activates the venv's
|
|
11
|
+
// interpreter for the one process without sourcing anything.
|
|
12
|
+
//
|
|
13
|
+
// ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
|
|
14
|
+
// (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
|
|
15
|
+
// the project dir regardless.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { discoverWest } from './west-discover.js';
|
|
19
|
+
/**
|
|
20
|
+
* The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
|
|
21
|
+
* west runs under. `pythonExecutable` lives in that directory, so it is its
|
|
22
|
+
* dirname. Returns undefined for launcher-mode installs where no venv is known.
|
|
23
|
+
*/
|
|
24
|
+
function venvBinDir(install) {
|
|
25
|
+
return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
|
|
29
|
+
* when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
|
|
30
|
+
* PATH when west was found via a venv Python.
|
|
31
|
+
*
|
|
32
|
+
* The PATH prepend matters: `west flash` shells out to bare runner tools
|
|
33
|
+
* (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
|
|
34
|
+
* PATH. Without the prepend, the user's PATH may surface a *different* tool
|
|
35
|
+
* ahead of the venv's — e.g. an older esptool whose argument spelling is
|
|
36
|
+
* incompatible with the runner. Putting the venv's bin first makes west's
|
|
37
|
+
* delegated subprocesses resolve to the same versions west itself runs under.
|
|
38
|
+
*/
|
|
39
|
+
export function buildEnv(install) {
|
|
40
|
+
const env = { ...process.env };
|
|
41
|
+
if (install.zephyrBase && !env.ZEPHYR_BASE) {
|
|
42
|
+
env.ZEPHYR_BASE = install.zephyrBase;
|
|
43
|
+
}
|
|
44
|
+
const bin = venvBinDir(install);
|
|
45
|
+
if (bin) {
|
|
46
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
47
|
+
// On Windows the PATH environment variable may be cased as `Path` (the
|
|
48
|
+
// registry-native form, the only one populated when node is launched from
|
|
49
|
+
// PowerShell/cmd) or `PATH` (POSIX form, set by Git Bash). Writing only one
|
|
50
|
+
// casing can leave the other stale/empty, which under PowerShell would drop
|
|
51
|
+
// the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
|
|
52
|
+
// whichever casing is populated and write that same casing back, preserving
|
|
53
|
+
// the full existing value with the venv dir prepended.
|
|
54
|
+
const existing = env.Path ?? env.PATH ?? '';
|
|
55
|
+
const updated = bin + sep + existing;
|
|
56
|
+
if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
|
|
57
|
+
env.Path = updated;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
env.PATH = updated;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return env;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build a spawn invocation that runs `west <westArgs>`.
|
|
67
|
+
*
|
|
68
|
+
* `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
|
|
69
|
+
* westSpawn preserves them and injects the discovery env. The returned object
|
|
70
|
+
* destructures directly into spawnSync:
|
|
71
|
+
*
|
|
72
|
+
* const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
|
|
73
|
+
* spawnSync(inv.command, inv.args, inv.options);
|
|
74
|
+
*
|
|
75
|
+
* Throws a clear, actionable error when no usable west install is discovered.
|
|
76
|
+
*/
|
|
77
|
+
export function westSpawn(westArgs, baseOptions) {
|
|
78
|
+
const install = discoverWest();
|
|
79
|
+
if (!install) {
|
|
80
|
+
throw new Error([
|
|
81
|
+
'west (the Zephyr build tool) was not found.',
|
|
82
|
+
'',
|
|
83
|
+
'cuttlefish looked for it on PATH, in $ZEPHYR_BASE/.venv, in common',
|
|
84
|
+
'Zephyr workspace dirs (~/zephyrproject/.venv), and as a system',
|
|
85
|
+
"Python module (`python -m west`). To fix:",
|
|
86
|
+
'',
|
|
87
|
+
' • Run the typeCAD Zephyr installer (one command, any OS — needs Node ≥18):',
|
|
88
|
+
' node packages/zephyr-installer/install.mjs',
|
|
89
|
+
' then `micromamba activate zephyr` and retry.',
|
|
90
|
+
' • Or activate an existing Zephyr venv in this shell, or',
|
|
91
|
+
' • Or set ZEPHYR_BASE to your Zephyr SDK root (the venv at $ZEPHYR_BASE/../.venv is then used), or',
|
|
92
|
+
' • Or install west into a discoverable Python: pip install west',
|
|
93
|
+
'',
|
|
94
|
+
'See https://docs.zephyrproject.org/latest/develop/getting_started/index.html',
|
|
95
|
+
].join('\n'));
|
|
96
|
+
}
|
|
97
|
+
const env = buildEnv(install);
|
|
98
|
+
// Strip `shell` if present — we pass absolute paths / known commands, and
|
|
99
|
+
// an explicit shell changes arg-quoting semantics on Windows.
|
|
100
|
+
const { shell: _drop, ...optsWithoutShell } = baseOptions;
|
|
101
|
+
if (install.mode === 'launcher' && install.westExecutable) {
|
|
102
|
+
return {
|
|
103
|
+
command: install.westExecutable,
|
|
104
|
+
args: westArgs,
|
|
105
|
+
options: { ...optsWithoutShell, env },
|
|
106
|
+
install,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// module mode: <python> -m west <args>
|
|
110
|
+
const py = install.pythonExecutable;
|
|
111
|
+
return {
|
|
112
|
+
command: py,
|
|
113
|
+
args: ['-m', 'west', ...westArgs],
|
|
114
|
+
options: { ...optsWithoutShell, env },
|
|
115
|
+
install,
|
|
116
|
+
};
|
|
117
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typecad/framework-zephyr",
|
|
3
|
+
"version": "1.0.0-alpha.8",
|
|
4
|
+
"description": "TypeCAD framework package for the Zephyr RTOS — west/CMake build, devicetree-driven GPIO",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./display": {
|
|
14
|
+
"types": "./dist/display/index.d.ts",
|
|
15
|
+
"default": "./dist/display/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./framework.manifest": {
|
|
18
|
+
"types": "./dist/framework.manifest.d.ts",
|
|
19
|
+
"default": "./dist/framework.manifest.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"prepublishOnly": "npm run build",
|
|
29
|
+
"test": "vitest run --root ../.. tests/packages/framework-zephyr/manifest.test.ts",
|
|
30
|
+
"test:coverage": "npm test && tsx ../../scripts/render-framework-coverage.ts",
|
|
31
|
+
"test:hw": "npm exec -- cuttlefish-test",
|
|
32
|
+
"test:hw:basics": "npm exec -- cuttlefish-test tests/01-basics.test.ts",
|
|
33
|
+
"test:hw:gpio": "npm exec -- cuttlefish-test tests/40-gpio.test.ts",
|
|
34
|
+
"test:hw:timers": "npm exec -- cuttlefish-test tests/42-timers.test.ts",
|
|
35
|
+
"test:http": "tsx ../../tests/hardware/start-server.ts",
|
|
36
|
+
"test:hw:http": "cd ../../tests/hardware && npm exec -- cuttlefish-test http-client.test.ts",
|
|
37
|
+
"test:mqtt": "tsx ../../tests/hardware/start-server.ts",
|
|
38
|
+
"test:hw:mqtt": "cd ../../tests/hardware && npm exec -- cuttlefish-test mqtt-client.test.ts"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@typecad/cuttlefish": "1.0.0-alpha.8"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@typecad/expect": "1.0.0-alpha.8",
|
|
45
|
+
"@typecad/board-xiao-nrf52840": "1.0.0-alpha.8",
|
|
46
|
+
"typescript": "^5.7.3"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/justind000/typecode.git",
|
|
55
|
+
"directory": "packages/framework-zephyr"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/justind000/typecode/tree/main/packages/framework-zephyr",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/justind000/typecode/issues"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"ble",
|
|
63
|
+
"cpp",
|
|
64
|
+
"cuttlefish",
|
|
65
|
+
"embedded",
|
|
66
|
+
"firmware",
|
|
67
|
+
"framework",
|
|
68
|
+
"gpio",
|
|
69
|
+
"microcontroller",
|
|
70
|
+
"nordic",
|
|
71
|
+
"nrf52840",
|
|
72
|
+
"rtos",
|
|
73
|
+
"typecad",
|
|
74
|
+
"typescript",
|
|
75
|
+
"zephyr"
|
|
76
|
+
],
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=18"
|
|
79
|
+
},
|
|
80
|
+
"author": "typecad0",
|
|
81
|
+
"sideEffects": false
|
|
82
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Timer methods polyfill — Zephyr k_timer + k_work (heap-free)
|
|
3
|
+
//
|
|
4
|
+
// setInterval / setTimeout / clearInterval / clearTimeout backed by a fixed
|
|
5
|
+
// pool of k_timer + k_work pairs. The timer's expiry function (ISR context)
|
|
6
|
+
// submits a k_work item to the system workqueue; the work handler runs the
|
|
7
|
+
// user callback in thread context. This split keeps the ISR cheap and lets the
|
|
8
|
+
// callback run on a real thread (so it can do real work — but must not block,
|
|
9
|
+
// since the system workqueue is shared).
|
|
10
|
+
//
|
|
11
|
+
// EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
|
|
12
|
+
// Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
import type { RuntimePolyfillIR } from '@typecad/cuttlefish/api/shared';
|
|
16
|
+
|
|
17
|
+
const MIN_TIMERS = 1;
|
|
18
|
+
const MAX_TIMERS_CAP = 16;
|
|
19
|
+
|
|
20
|
+
export interface TimerPolyfillIR extends RuntimePolyfillIR {
|
|
21
|
+
/** The clamped pool size the C++ was generated with. */
|
|
22
|
+
maxTimers: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build the timer_methods polyfill IR. `requestedTimers` is sized from
|
|
27
|
+
* ctx.analysis.timerCallCount; it is clamped to [1, 16].
|
|
28
|
+
*/
|
|
29
|
+
export function buildTimerPolyfill(requestedTimers: number): TimerPolyfillIR {
|
|
30
|
+
const maxTimers = Math.min(MAX_TIMERS_CAP, Math.max(MIN_TIMERS, requestedTimers));
|
|
31
|
+
|
|
32
|
+
const helperStructs = [`
|
|
33
|
+
// cuttlefish timer runtime — k_timer + k_work pool (heap-free).
|
|
34
|
+
struct __tc_TimerSlot {
|
|
35
|
+
struct k_timer timer;
|
|
36
|
+
struct k_work work;
|
|
37
|
+
void (*callback)(void);
|
|
38
|
+
bool active;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
static struct __tc_TimerSlot __tc_timer_slots[${maxTimers}];
|
|
42
|
+
|
|
43
|
+
static void __tc_timer_work_handler(struct k_work* w) {
|
|
44
|
+
// Runs on the system workqueue thread (NOT ISR). Find the owning slot by
|
|
45
|
+
// address and invoke the callback. Must not block.
|
|
46
|
+
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
47
|
+
if (&__tc_timer_slots[i].work == w) {
|
|
48
|
+
if (__tc_timer_slots[i].callback != nullptr) { __tc_timer_slots[i].callback(); }
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static void __tc_timer_expiry_fn(struct k_timer* t) {
|
|
55
|
+
// ISR context: submit the work item, do NOT run the callback here.
|
|
56
|
+
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
57
|
+
if (&__tc_timer_slots[i].timer == t) {
|
|
58
|
+
(void)k_work_submit(&__tc_timer_slots[i].work);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static int32_t __tc_timer_add(void (*cb)(void), int32_t ms, bool repeat) {
|
|
65
|
+
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
66
|
+
if (!__tc_timer_slots[i].active) {
|
|
67
|
+
__tc_timer_slots[i].callback = cb;
|
|
68
|
+
__tc_timer_slots[i].active = true;
|
|
69
|
+
k_timer_init(&__tc_timer_slots[i].timer, __tc_timer_expiry_fn, nullptr);
|
|
70
|
+
k_work_init(&__tc_timer_slots[i].work, __tc_timer_work_handler);
|
|
71
|
+
k_timer_start(&__tc_timer_slots[i].timer, K_MSEC(ms), repeat ? K_MSEC(ms) : K_FOREVER);
|
|
72
|
+
return i + 1; // 1-based id (Arduino parity)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return 0; // pool full
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static void __tc_timer_clear(int32_t id) {
|
|
79
|
+
if (id > 0 && id <= ${maxTimers}) {
|
|
80
|
+
int32_t i = id - 1;
|
|
81
|
+
k_timer_stop(&__tc_timer_slots[i].timer);
|
|
82
|
+
__tc_timer_slots[i].active = false;
|
|
83
|
+
__tc_timer_slots[i].callback = nullptr;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`];
|
|
87
|
+
|
|
88
|
+
const helperFunctions = [`
|
|
89
|
+
int32_t __tc_setInterval(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, true); }
|
|
90
|
+
int32_t __tc_setTimeout(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, false); }
|
|
91
|
+
void __tc_clearInterval(int32_t id) { __tc_timer_clear(id); }
|
|
92
|
+
void __tc_clearTimeout(int32_t id) { __tc_timer_clear(id); }
|
|
93
|
+
`];
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
kind: 'polyfill',
|
|
97
|
+
id: 'timer_methods',
|
|
98
|
+
domain: 'embedded',
|
|
99
|
+
requiredIncludes: [],
|
|
100
|
+
forwardDeclarations: [],
|
|
101
|
+
helperStructs,
|
|
102
|
+
helperFunctions,
|
|
103
|
+
shimMacros: [],
|
|
104
|
+
dependencies: [],
|
|
105
|
+
maxTimers,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// GPIO controller resolution — pin → devicetree nodelabel
|
|
3
|
+
//
|
|
4
|
+
// Most SoCs expose a single GPIO controller, so a HAL pin maps to
|
|
5
|
+
// `chip.gpioController` unconditionally. SoCs that split GPIO across multiple
|
|
6
|
+
// devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48) list a range per
|
|
7
|
+
// controller in `chip.gpioControllers`. These helpers route a HAL pin to the
|
|
8
|
+
// owning controller.
|
|
9
|
+
//
|
|
10
|
+
// Two forms:
|
|
11
|
+
// - controllerNodelabelForPin(): used by the compile-time lowering paths
|
|
12
|
+
// (gpio/pulse/spi raw writes), where the pin number is a literal known at
|
|
13
|
+
// transpile time. Resolves to a single nodelabel so the emitted
|
|
14
|
+
// `DT_NODELABEL(<nodelabel>)` macro is statically valid.
|
|
15
|
+
// - emitGpioDevDispatcher(): used by paths that take a RUNTIME pin (the
|
|
16
|
+
// @typecad/safety __tc_gpio_read/__tc_gpio_write shims). Emits a tiny
|
|
17
|
+
// `__tc_gpio_dev(uint32_t pin)` that returns the owning `const struct
|
|
18
|
+
// device*`, so a single shim body handles any pin. Single-controller SoCs
|
|
19
|
+
// collapse to a one-liner.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
import type { ZephyrChipDescriptor } from './types.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
|
|
26
|
+
*
|
|
27
|
+
* For single-controller SoCs (no `gpioControllers`) this is always
|
|
28
|
+
* `chip.gpioController`. For multi-controller SoCs it finds the entry whose
|
|
29
|
+
* [minPin, maxPin] range contains `pin`, falling back to `chip.gpioController`
|
|
30
|
+
* for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
|
|
31
|
+
*/
|
|
32
|
+
export function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string {
|
|
33
|
+
const ranges = chip.gpioControllers;
|
|
34
|
+
if (ranges && ranges.length > 0) {
|
|
35
|
+
const hit = ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
|
|
36
|
+
if (hit) return hit.nodelabel;
|
|
37
|
+
}
|
|
38
|
+
return chip.gpioController;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Emit the C++ source for a runtime pin → GPIO-device dispatcher.
|
|
43
|
+
*
|
|
44
|
+
* Returns lines defining `static inline const struct device* __tc_gpio_dev(uint32_t pin)`.
|
|
45
|
+
* Each branch resolves its controller via `DEVICE_DT_GET(DT_NODELABEL(...))` at
|
|
46
|
+
* compile time (the macro is evaluated per branch, so it is always statically
|
|
47
|
+
* valid); only `pin` is runtime. For a single-controller SoC this collapses to
|
|
48
|
+
* a one-liner returning that controller, so the existing XIAO nRF52840 behavior
|
|
49
|
+
* is byte-for-byte unchanged.
|
|
50
|
+
*/
|
|
51
|
+
export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
|
|
52
|
+
const ranges = chip.gpioControllers;
|
|
53
|
+
if (!ranges || ranges.length === 0) {
|
|
54
|
+
return [
|
|
55
|
+
'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
|
|
56
|
+
` (void)pin;`,
|
|
57
|
+
` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
|
|
58
|
+
'}',
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
const lines: string[] = [
|
|
62
|
+
'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
|
|
63
|
+
];
|
|
64
|
+
for (const r of ranges) {
|
|
65
|
+
lines.push(
|
|
66
|
+
` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
lines.push(
|
|
70
|
+
` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
|
|
71
|
+
'}',
|
|
72
|
+
);
|
|
73
|
+
return lines;
|
|
74
|
+
}
|