Merge pull request #514 from pikasTech/fix/device-pod-vector-launch-20260527
fix: 支持 device-pod Flash 向量启动抓串口
This commit is contained in:
@@ -60,6 +60,8 @@ HWLAB coder / code agent runner 镜像必须通过正式 CI/CD 预装 device-pod
|
||||
- **device-pod-cli 入口**:runner 内必须预装 `device-pod-cli` skill,稳定入口为 `/app/skills/device-pod-cli/scripts/device-pod-cli.mjs`,其实现指向 `/app/tools/device-pod-cli.mjs`。code agent 应通过该 CLI 读取 workspace 下 `.device-pod/<devicePodId>.json`,再走 cloud-api/gateway/device-host-cli 调用设备能力。
|
||||
- **device-host-cli 资产**:runner 内必须随 `device-pod-cli` skill 打包 Windows 侧自包含 host CLI 资产,稳定路径为 `/app/skills/device-pod-cli/assets/device-host-cli.mjs`。新 Windows 硬件 PC 接入时,不运行独立安装器;code agent 使用预装 cmd 透传入口把该资产发送到目标 workspace 的 `tools\device-host-cli.mjs`,再通过同一 cmd 透传入口执行 `node tools\device-host-cli.mjs health` 验证。
|
||||
|
||||
`device-host-cli` 不是一次性预装脚本,而是 HWLAB 内部 code agent 可继续热开发的 host 侧工具。DeepSeek、Codex runner 或其他 HWLAB 内部 code agent 在真实硬件闭环中遇到 Keil、debug probe、串口、文件工作区或硬件启动路径不顺手时,优先在目标 Windows workspace 的 `tools\device-host-cli.mjs` 上新增或修复具名设备能力,并通过 `device-pod-cli -> cloud-api -> gateway -> device-host-cli` 的真实链路热验证;验证通过后再把同一实现回填到 `/app/skills/device-pod-cli/assets/device-host-cli.mjs` 对应的 HWLAB repo 资产,进入下一次 CI/CD 预装。不得因为 host CLI 暂时不顺手而把 `device-pod-cli` 退化为泛化 cmd/shell 入口。
|
||||
|
||||
目标 Windows workspace 的最小布局为:
|
||||
|
||||
```text
|
||||
|
||||
@@ -38,6 +38,12 @@ device pod, debug probe control, or I/O probe reads such as UART boot logs.
|
||||
- `device-host-cli` is expected to be self-contained on the user PC. Keil,
|
||||
serial-monitor, mklink, and other skills can be implementation references, but
|
||||
are not runtime dependencies of this HWLAB runner skill.
|
||||
- `device-host-cli` is not just a copied preinstall script. It is the hot
|
||||
development tool that HWLAB internal code agents, including DeepSeek-backed
|
||||
runners, may keep improving on the target Windows workspace when hardware,
|
||||
Keil, probe, UART, or filesystem operations are not smooth enough. Hot fixes
|
||||
must be validated through the real device-pod/gateway route first, then copied
|
||||
back into the HWLAB repo asset for the next CI/CD preinstall.
|
||||
|
||||
## Windows Host CLI Bootstrap
|
||||
|
||||
@@ -77,6 +83,26 @@ After that, `device-pod-cli` should use the profile's `hostCli`, normally
|
||||
`node tools\device-host-cli.mjs`, and should not depend on any Windows skill
|
||||
directory at runtime.
|
||||
|
||||
## Host CLI Hot Development
|
||||
|
||||
When a profiled operation is awkward or missing, prefer improving the Windows
|
||||
workspace copy of `tools\device-host-cli.mjs` instead of bypassing the device-pod
|
||||
model with arbitrary cmd snippets. The intended loop is:
|
||||
|
||||
```text
|
||||
edit tools\device-host-cli.mjs in the Windows device-pod workspace
|
||||
node tools\device-host-cli.mjs health
|
||||
node tools\device-host-cli.mjs <new named operation>
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs <devicePodId>:... <same operation>
|
||||
copy the validated change back to skills/device-pod-cli/assets/device-host-cli.mjs
|
||||
```
|
||||
|
||||
The host CLI should stay self-contained and named-operation oriented: add
|
||||
`debug-probe launch-flash` or `io-probe uart/1 read-after-launch-flash` style
|
||||
device semantics, not a generic shell escape. If gateway/cmd transfer itself is
|
||||
the friction, fix `tran.mjs` or the gateway transport separately instead of
|
||||
embedding transport workarounds into `device-host-cli`.
|
||||
|
||||
## Commands
|
||||
|
||||
All commands emit JSON and must remain short-running. Use `build start` /
|
||||
@@ -93,14 +119,17 @@ node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:wo
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe chip-id
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe download start --approved --reason "DEV smoke"
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe download status <jobId>
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe launch-flash --approved --reason "manual flash-vector launch"
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 ports
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 read
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 read --duration-ms 5000
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 read-after-launch-flash --approved --reason "boot log capture"
|
||||
```
|
||||
|
||||
## Safety
|
||||
|
||||
- Mutating operations such as workspace patching, download, reset, and I/O write
|
||||
require `--approved --reason <text>` before the CLI dispatches anything.
|
||||
- Mutating operations such as workspace patching, download, reset,
|
||||
`launch-flash`, `read-after-launch-flash`, and I/O write require
|
||||
`--approved --reason <text>` before the CLI dispatches anything.
|
||||
- `io-probe:/inner/...` is intentionally distinct from external physical I/O
|
||||
paths such as `io-probe:/uart/1`; do not use internal target state as evidence
|
||||
for external UART/GPIO observations.
|
||||
|
||||
@@ -555,6 +555,29 @@ async function pyocdArgs(profile, commandArgs) {
|
||||
return { pyocd: tools.pyocd.path, args };
|
||||
}
|
||||
|
||||
async function pyocdCommanderArgs(profile, commands, options = {}) {
|
||||
const tools = await toolInfo(profile);
|
||||
if (!tools.pyocd.found) throw new Error('pyOCD executable not found');
|
||||
const debug = profile.debugInterface || {};
|
||||
const args = ['commander'];
|
||||
if (debug.probeUid) args.push('-u', debug.probeUid);
|
||||
if (debug.pyocdTarget) args.push('-t', debug.pyocdTarget);
|
||||
const connectMode = options['connect-mode'] || options.connectMode || debug.launchConnectMode || 'attach';
|
||||
if (connectMode) args.push('-M', connectMode);
|
||||
if (debug.frequency) args.push('-f', debug.frequency);
|
||||
args.push('-W');
|
||||
for (const command of commands) args.push('-c', command);
|
||||
return { pyocd: tools.pyocd.path, args };
|
||||
}
|
||||
|
||||
function parseFlashVectors(text, flashBase) {
|
||||
const normalizedBase = String(flashBase || '').toLowerCase().replace(/^0x/u, '');
|
||||
const escapedBase = normalizedBase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const match = String(text || '').toLowerCase().match(new RegExp(`${escapedBase}\\s*:\\s*([0-9a-f]{8})\\s+([0-9a-f]{8})`, 'u'));
|
||||
if (!match) throw new Error(`unable to parse flash vectors at ${flashBase}`);
|
||||
return { initialSp: `0x${match[1]}`, resetHandler: `0x${match[2]}` };
|
||||
}
|
||||
|
||||
async function debugStatus() {
|
||||
const profile = loadProfile();
|
||||
const tools = await toolInfo(profile);
|
||||
@@ -584,6 +607,45 @@ async function resetRun() {
|
||||
ok('debug-probe.reset', { exitCode: result.exitCode, success: result.exitCode === 0, stdout: result.stdout, stderr: result.stderr });
|
||||
}
|
||||
|
||||
async function launchFlash(options = {}) {
|
||||
const profile = loadProfile();
|
||||
const flashBase = String(options['flash-base'] || options.flashBase || profile.debugInterface?.flashBase || profile.target?.flashBase || '0x08000000');
|
||||
const skipHardwareReset = Boolean(options['skip-hardware-reset'] || options.skipHardwareReset);
|
||||
const prepareCommand = skipHardwareReset ? 'halt' : 'reset halt HARDWARE';
|
||||
const timeoutMs = Number(options['timeout-ms'] || options.timeoutMs || 60000);
|
||||
const read = await pyocdCommanderArgs(profile, [prepareCommand, `read32 ${flashBase} 8`], options);
|
||||
const vectorResult = await runCapture(read.pyocd, read.args, { timeoutMs });
|
||||
if (vectorResult.exitCode !== 0) throw new Error(`pyOCD vector read failed: ${shortTail(vectorResult.stdout || vectorResult.stderr)}`);
|
||||
const vectorText = `${vectorResult.stdout}\n${vectorResult.stderr}`;
|
||||
const vectors = parseFlashVectors(vectorText, flashBase);
|
||||
const launchCommands = [
|
||||
prepareCommand,
|
||||
`write32 0xE000ED08 ${flashBase}`,
|
||||
`wreg sp ${vectors.initialSp}`,
|
||||
`wreg pc ${vectors.resetHandler}`,
|
||||
'wreg xpsr 0x01000000',
|
||||
'wreg primask 0',
|
||||
'wreg faultmask 0',
|
||||
'wreg basepri 0',
|
||||
'go',
|
||||
];
|
||||
const launch = await pyocdCommanderArgs(profile, launchCommands, options);
|
||||
const launchResult = await runCapture(launch.pyocd, launch.args, { timeoutMs });
|
||||
ok('debug-probe.launch-flash', {
|
||||
success: launchResult.exitCode === 0,
|
||||
exitCode: launchResult.exitCode,
|
||||
flashBase,
|
||||
vectorTableRegister: '0xE000ED08',
|
||||
prepareCommand,
|
||||
skipHardwareReset,
|
||||
initialSp: vectors.initialSp,
|
||||
resetHandler: vectors.resetHandler,
|
||||
stdout: launchResult.stdout,
|
||||
stderr: launchResult.stderr,
|
||||
vectorRead: { exitCode: vectorResult.exitCode, stdout: vectorResult.stdout, stderr: vectorResult.stderr },
|
||||
});
|
||||
}
|
||||
|
||||
async function serialPorts() {
|
||||
const script = "Get-CimInstance Win32_SerialPort | Select-Object DeviceID,Name,PNPDeviceID | ConvertTo-Json -Compress";
|
||||
const result = await runPowerShell(script, 15000);
|
||||
@@ -627,6 +689,73 @@ async function serialRead(uartId, options) {
|
||||
ok('io-probe.uart.read', { uart, durationMs, exitCode: result.exitCode, content, bytes: Buffer.byteLength(content), logFile, stderr: result.stderr });
|
||||
}
|
||||
|
||||
async function serialReadAfterLaunchFlash(uartId, options) {
|
||||
const profile = loadProfile();
|
||||
const uart = resolveUart(profile, uartId);
|
||||
const tools = await toolInfo(profile);
|
||||
if (!tools.pyocd.found) throw new Error('pyOCD executable not found');
|
||||
const debug = profile.debugInterface || {};
|
||||
const durationMs = Number(options['duration-ms'] || options.durationMs || 5000);
|
||||
const flashBase = String(options['flash-base'] || options.flashBase || debug.flashBase || profile.target?.flashBase || '0x08000000');
|
||||
const skipHardwareReset = Boolean(options['skip-hardware-reset'] || options.skipHardwareReset);
|
||||
const prepareCommand = skipHardwareReset ? 'halt' : 'reset halt HARDWARE';
|
||||
const connectMode = options['connect-mode'] || options.connectMode || debug.launchConnectMode || 'attach';
|
||||
const script = [
|
||||
'$ErrorActionPreference = \'Stop\';',
|
||||
`$portName = ${psQuote(uart.port)};`,
|
||||
`$baud = ${Number(uart.baudRate || 115200)};`,
|
||||
`$durationMs = ${durationMs};`,
|
||||
`$pyocd = ${psQuote(tools.pyocd.path)};`,
|
||||
`$uid = ${psQuote(debug.probeUid || '')};`,
|
||||
`$target = ${psQuote(debug.pyocdTarget || '')};`,
|
||||
`$frequency = ${psQuote(debug.frequency || '')};`,
|
||||
`$connectMode = ${psQuote(connectMode || '')};`,
|
||||
`$flashBase = ${psQuote(flashBase)};`,
|
||||
`$prepareCommand = ${psQuote(prepareCommand)};`,
|
||||
'$common = @(\'commander\');',
|
||||
'if ($uid) { $common += @(\'-u\', $uid) }',
|
||||
'if ($target) { $common += @(\'-t\', $target) }',
|
||||
'if ($connectMode) { $common += @(\'-M\', $connectMode) }',
|
||||
'if ($frequency) { $common += @(\'-f\', $frequency) }',
|
||||
'$common += @(\'-W\');',
|
||||
'$port = [System.IO.Ports.SerialPort]::new($portName, $baud, [System.IO.Ports.Parity]::None, 8, [System.IO.Ports.StopBits]::One);',
|
||||
'$port.Encoding = [System.Text.UTF8Encoding]::new($false);',
|
||||
'$port.ReadTimeout = 200;',
|
||||
'try {',
|
||||
' $port.Open();',
|
||||
' $port.DiscardInBuffer();',
|
||||
' $port.Close();',
|
||||
' $vectorOutput = & $pyocd @common -c $prepareCommand -c "read32 $flashBase 8" 2>&1;',
|
||||
' $vectorExitCode = $LASTEXITCODE;',
|
||||
' $vectorText = ($vectorOutput | Out-String);',
|
||||
' if ($vectorExitCode -ne 0) { throw "pyOCD vector read failed: $vectorText" }',
|
||||
' $escapedBase = [regex]::Escape($flashBase.ToLower().Replace("0x", ""));',
|
||||
' $match = [regex]::Match($vectorText.ToLower(), "$escapedBase\\s*:\\s*([0-9a-f]{8})\\s+([0-9a-f]{8})");',
|
||||
' if (-not $match.Success) { throw "Unable to parse flash vectors from pyOCD output: $vectorText" }',
|
||||
' $initialSp = "0x" + $match.Groups[1].Value;',
|
||||
' $resetHandler = "0x" + $match.Groups[2].Value;',
|
||||
' $launchOutput = & $pyocd @common -c $prepareCommand -c "write32 0xE000ED08 $flashBase" -c "wreg sp $initialSp" -c "wreg pc $resetHandler" -c "wreg xpsr 0x01000000" -c "wreg primask 0" -c "wreg faultmask 0" -c "wreg basepri 0" -c "go" 2>&1;',
|
||||
' $launchExitCode = $LASTEXITCODE;',
|
||||
' $launchText = ($launchOutput | Out-String);',
|
||||
' if ($launchExitCode -ne 0) { throw "pyOCD flash-vector launch failed: $launchText" }',
|
||||
' $port.Open();',
|
||||
' Start-Sleep -Milliseconds $durationMs;',
|
||||
' $text = $port.ReadExisting();',
|
||||
' $bytes = [System.Text.Encoding]::UTF8.GetBytes($text);',
|
||||
' [pscustomobject]@{ contentB64 = [Convert]::ToBase64String($bytes); flashBase = $flashBase; vectorTableRegister = "0xE000ED08"; prepareCommand = $prepareCommand; initialSp = $initialSp; resetHandler = $resetHandler; vectorExitCode = $vectorExitCode; launchExitCode = $launchExitCode; vectorStdout = $vectorText; launchStdout = $launchText } | ConvertTo-Json -Depth 5 -Compress | Write-Output;',
|
||||
'} finally { if ($port.IsOpen) { $port.Close() }; $port.Dispose() }',
|
||||
].join(' ');
|
||||
const result = await runPowerShell(script, Math.max(15000, durationMs + 20000));
|
||||
if (result.exitCode !== 0) throw new Error(`uart read-after-launch-flash failed: ${shortTail(result.stdout || result.stderr)}`);
|
||||
const payload = JSON.parse(result.stdout.trim());
|
||||
const content = payload.contentB64 ? Buffer.from(payload.contentB64, 'base64').toString('utf8') : '';
|
||||
const logDir = path.join(stateRoot(profile.__workspaceRoot), 'serial');
|
||||
ensureDir(logDir);
|
||||
const logFile = path.join(logDir, `${uart.id.replace(/\//g, '_')}.jsonl`);
|
||||
fs.appendFileSync(logFile, `${JSON.stringify({ ts: nowIso(), direction: 'read-after-launch-flash', uartId: uart.id, port: uart.port, baudRate: uart.baudRate, content })}\n`, 'utf8');
|
||||
ok('io-probe.uart.read-after-launch-flash', { uart, durationMs, exitCode: result.exitCode, content, bytes: Buffer.byteLength(content), logFile, stderr: result.stderr, launch: { flashBase: payload.flashBase, vectorTableRegister: payload.vectorTableRegister, prepareCommand: payload.prepareCommand, initialSp: payload.initialSp, resetHandler: payload.resetHandler, vectorExitCode: payload.vectorExitCode, launchExitCode: payload.launchExitCode, vectorStdout: payload.vectorStdout, launchStdout: payload.launchStdout } });
|
||||
}
|
||||
|
||||
async function serialWrite(uartId, options) {
|
||||
const profile = loadProfile();
|
||||
const uart = resolveUart(profile, uartId);
|
||||
@@ -654,8 +783,8 @@ function usage() {
|
||||
'node tools/device-host-cli.mjs workspace rg <pattern> [path]',
|
||||
'node tools/device-host-cli.mjs workspace apply-patch [base] --patch-b64 <base64>',
|
||||
'node tools/device-host-cli.mjs workspace build start|status [jobId]',
|
||||
'node tools/device-host-cli.mjs debug-probe status|chip-id|bind|download|reset',
|
||||
'node tools/device-host-cli.mjs io-probe uart/1 ports|read|write [args]'
|
||||
'node tools/device-host-cli.mjs debug-probe status|chip-id|bind|download|reset|launch-flash',
|
||||
'node tools/device-host-cli.mjs io-probe uart/1 ports|read|read-after-launch-flash|write [args]'
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -688,12 +817,14 @@ async function main() {
|
||||
if (sub === 'status') return readJobStatus('keil-download', rest[1]);
|
||||
}
|
||||
if (command === 'reset') return await resetRun();
|
||||
if (command === 'launch-flash') return await launchFlash(parseArgs(rest));
|
||||
}
|
||||
if (group === 'io-probe') {
|
||||
const uartId = command;
|
||||
const sub = rest[0] || 'read';
|
||||
if (sub === 'ports') return await serialPorts();
|
||||
if (sub === 'read') return await serialRead(uartId, parseArgs(rest.slice(1)));
|
||||
if (sub === 'read-after-launch-flash') return await serialReadAfterLaunchFlash(uartId, parseArgs(rest.slice(1)));
|
||||
if (sub === 'write') return await serialWrite(uartId, parseArgs(rest.slice(1)));
|
||||
}
|
||||
throw new Error(`unsupported command: ${process.argv.slice(2).join(' ')}`);
|
||||
|
||||
@@ -93,25 +93,53 @@ function cmdQuote(value) {
|
||||
return `"${text.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
const CONTROL_OPTION_KEYS = new Set([
|
||||
'approved',
|
||||
'reason',
|
||||
'dryRun',
|
||||
'full',
|
||||
'timeoutMs',
|
||||
'traceId',
|
||||
'podId',
|
||||
'patch',
|
||||
'patchB64',
|
||||
]);
|
||||
|
||||
function camelToKebab(value) {
|
||||
return String(value).replace(/[A-Z]/gu, (match) => `-${match.toLowerCase()}`);
|
||||
}
|
||||
|
||||
function hostOptionArgs(parsed) {
|
||||
const out = [];
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (key === '_' || CONTROL_OPTION_KEYS.has(key) || value === undefined || value === false) continue;
|
||||
const name = `--${camelToKebab(key)}`;
|
||||
if (value === true) out.push(name);
|
||||
else out.push(name, String(value));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function buildHostCommand(selector, parsed) {
|
||||
const args = [];
|
||||
const options = hostOptionArgs(parsed);
|
||||
const operation = parsed._[0] || (selector.surface === 'workspace' ? 'ls' : selector.surface === 'debug-probe' ? 'status' : 'read');
|
||||
const mutating = isMutating(selector.surface, operation);
|
||||
if (mutating) requireApproval(parsed, `${selector.surface}.${operation}`);
|
||||
|
||||
if (selector.surface === 'workspace') {
|
||||
const workspacePath = selector.path.replace(/^\/+/, '') || '.';
|
||||
if (operation === 'ls' || operation === 'cat') args.push('workspace', operation, parsed._[1] ? path.posix.join(workspacePath, parsed._[1]) : workspacePath, ...parsed._.slice(2));
|
||||
else if (operation === 'rg') args.push('workspace', 'rg', parsed._[1] || '', workspacePath, ...parsed._.slice(2));
|
||||
if (operation === 'ls' || operation === 'cat') args.push('workspace', operation, parsed._[1] ? path.posix.join(workspacePath, parsed._[1]) : workspacePath, ...parsed._.slice(2), ...options);
|
||||
else if (operation === 'rg') args.push('workspace', 'rg', parsed._[1] || '', workspacePath, ...parsed._.slice(2), ...options);
|
||||
else if (operation === 'apply-patch') args.push('workspace', 'apply-patch', workspacePath, '--patch-b64', Buffer.from(await resolvePatchText(parsed), 'utf8').toString('base64'));
|
||||
else if (operation === 'build') args.push('workspace', 'build', ...(parsed._.slice(1).length ? parsed._.slice(1) : ['start']));
|
||||
else if (operation === 'build') args.push('workspace', 'build', ...(parsed._.slice(1).length ? parsed._.slice(1) : ['start']), ...options);
|
||||
else throw new Error(`unsupported workspace command: ${operation}`);
|
||||
} else if (selector.surface === 'debug-probe') {
|
||||
args.push('debug-probe', operation, ...parsed._.slice(1));
|
||||
args.push('debug-probe', operation, ...parsed._.slice(1), ...options);
|
||||
} else if (selector.surface === 'io-probe') {
|
||||
const probePath = selector.path.replace(/^\/+/, '') || 'uart/1';
|
||||
if (probePath.startsWith('inner/')) throw new Error(`unsupported io-probe path in MVP: ${probePath}`);
|
||||
args.push('io-probe', probePath, operation, ...parsed._.slice(1));
|
||||
args.push('io-probe', probePath, operation, ...parsed._.slice(1), ...options);
|
||||
} else {
|
||||
throw new Error(`unsupported surface: ${selector.surface}`);
|
||||
}
|
||||
@@ -120,8 +148,8 @@ async function buildHostCommand(selector, parsed) {
|
||||
|
||||
function isMutating(surface, operation) {
|
||||
return (surface === 'workspace' && operation === 'apply-patch')
|
||||
|| (surface === 'debug-probe' && (operation === 'download' || operation === 'reset'))
|
||||
|| (surface === 'io-probe' && operation === 'write');
|
||||
|| (surface === 'debug-probe' && (operation === 'download' || operation === 'reset' || operation === 'launch-flash'))
|
||||
|| (surface === 'io-probe' && (operation === 'write' || operation === 'read-after-launch-flash'));
|
||||
}
|
||||
function requireApproval(parsed, operation) {
|
||||
if (parsed.approved === true && typeof parsed.reason === 'string' && parsed.reason.trim()) return;
|
||||
@@ -195,8 +223,8 @@ function help() {
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:workspace:/firmware cat main.c',
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:workspace:/ apply-patch --approved --reason "DEV edit" < patch.diff',
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:workspace:/ build start|status <jobId>',
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:debug-probe chip-id|status|download|reset --approved --reason "DEV smoke"',
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 ports|read|write --approved --reason "DEV io"'
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:debug-probe chip-id|status|download|reset|launch-flash --approved --reason "DEV smoke"',
|
||||
'node tools/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 ports|read|read-after-launch-flash|write --approved --reason "DEV io"'
|
||||
] });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user