@geastack/cli 0.1.52 → 0.1.54
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 +14 -1
- package/docs/ESP32-WAVESHARE-AMOLED-QUICKSTART.md +2 -2
- package/docs/NPX-COMMANDS.md +3 -1
- package/docs/SETUP.md +81 -15
- package/docs/SPEC.md +8 -5
- package/package.json +2 -3
- package/src/apps/app-index-writer.mjs +15 -0
- package/src/apps/apple-icons.mjs +147 -0
- package/src/apps/bundle-writer.mjs +156 -0
- package/src/apps/launcher-catalog.mjs +114 -0
- package/src/apps/openai-icons.mjs +356 -0
- package/src/apps/zip-writer.mjs +104 -0
- package/src/ble/ble-ota.swift +291 -0
- package/src/boards/config.mjs +114 -0
- package/src/boards/custom-target.mjs +309 -0
- package/src/boards/resolve.mjs +145 -0
- package/src/boards/targets.mjs +45 -0
- package/src/boards/usb.mjs +252 -0
- package/src/chips.mjs +1 -1
- package/src/commands/apps.mjs +204 -0
- package/src/commands/board.mjs +402 -0
- package/src/commands/boards.mjs +334 -0
- package/src/commands/doctor.mjs +91 -0
- package/src/context.mjs +50 -54
- package/src/device/device.mjs +96 -0
- package/src/device/image.mjs +115 -0
- package/src/device/serial.mjs +430 -0
- package/src/device/wifi.mjs +190 -0
- package/src/esp32/build.mjs +321 -0
- package/src/esp32/capabilities.mjs +54 -0
- package/src/esp32/flash.mjs +181 -0
- package/src/esp32/idf-env.mjs +171 -0
- package/src/esp32/ota.mjs +80 -0
- package/src/esp32/partitions.mjs +59 -0
- package/src/esp32/sdkconfig.mjs +103 -0
- package/src/esp32/wifi-config.mjs +72 -0
- package/src/gea.mjs +89 -427
- package/src/geaos/adapter.mjs +119 -0
- package/src/heap-report.mjs +1276 -0
- package/src/manifest.mjs +135 -36
- package/src/rp2350/adapter.mjs +155 -0
- package/src/serial-devices.mjs +32 -20
- package/src/setup-wizard.mjs +11 -12
- package/src/taurus/adapter.mjs +52 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { flag, option, optionList } from '../args.mjs'
|
|
4
|
+
import { resolveBoardSelection } from '../boards/resolve.mjs'
|
|
5
|
+
import { createChildEnv } from '../context.mjs'
|
|
6
|
+
import { chooseTransport, openDevice, saveScreenshot } from '../device/device.mjs'
|
|
7
|
+
import { geadev } from '../device/serial.mjs'
|
|
8
|
+
import { ExitCode, fail } from '../errors.mjs'
|
|
9
|
+
import { buildEsp32Firmware, buildImages, esp32BuildDir, fullCleanEsp32, requireEspIdf } from '../esp32/build.mjs'
|
|
10
|
+
import { eraseSlot, flashFirmware, flashImageSet, flashOptions, postFlashRestartNote, restoreBootMetadata, stageImage } from '../esp32/flash.mjs'
|
|
11
|
+
import { bleOta, otaEraseSlot, otaFlash, otaStage, waitForReboot } from '../esp32/ota.mjs'
|
|
12
|
+
import { manifestRequestsBleOta } from '../esp32/capabilities.mjs'
|
|
13
|
+
import { runGeaos } from '../geaos/adapter.mjs'
|
|
14
|
+
import { assertTargetEnabled, assertValidApp, resolveRequestedApp } from '../manifest.mjs'
|
|
15
|
+
import { buildRp2350, flashRp2350, rp2350BuildDir } from '../rp2350/adapter.mjs'
|
|
16
|
+
import { runTargetHook } from '../taurus/adapter.mjs'
|
|
17
|
+
|
|
18
|
+
// Every board-facing command: resolve the alias, pick the adapter, run.
|
|
19
|
+
|
|
20
|
+
export function selectBoard(ctx, parsed, needs = {}) {
|
|
21
|
+
const boardName = option(parsed, 'board', '')
|
|
22
|
+
const targetName = option(parsed, 'target', '')
|
|
23
|
+
if (!boardName && !targetName) fail('--board <alias> is required (see gea boards list).', ExitCode.usage)
|
|
24
|
+
try {
|
|
25
|
+
return resolveBoardSelection({
|
|
26
|
+
ctx,
|
|
27
|
+
boardName,
|
|
28
|
+
targetName,
|
|
29
|
+
requestedPort: option(parsed, 'port', ''),
|
|
30
|
+
requestedHost: option(parsed, 'host', ''),
|
|
31
|
+
needs,
|
|
32
|
+
deferUsbPort: true
|
|
33
|
+
})
|
|
34
|
+
} catch (error) {
|
|
35
|
+
fail(error.message, ExitCode.usage)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function optionalApp(ctx, parsed, rest, selection, { required = false } = {}) {
|
|
40
|
+
const requested = option(parsed, 'app') || rest[0]
|
|
41
|
+
let app = null
|
|
42
|
+
if (requested || required) {
|
|
43
|
+
app = resolveRequestedApp(ctx, parsed, rest)
|
|
44
|
+
} else {
|
|
45
|
+
try {
|
|
46
|
+
app = resolveRequestedApp(ctx, parsed, [])
|
|
47
|
+
} catch {
|
|
48
|
+
app = null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!app) return null
|
|
52
|
+
assertValidApp(app)
|
|
53
|
+
assertTargetEnabled(ctx, app, selection.boardName || selection.target)
|
|
54
|
+
return app
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function bleOtaRequested(parsed, app, env) {
|
|
58
|
+
return option(parsed, 'transport') === 'ble' || env.GEA_EMBEDDED_BLE_OTA === '1' || manifestRequestsBleOta(app?.packageJson)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function io(parsed, options) {
|
|
62
|
+
return {
|
|
63
|
+
env: options.env,
|
|
64
|
+
dryRun: flag(parsed, 'dry-run'),
|
|
65
|
+
stdout: options.stdout,
|
|
66
|
+
stderr: options.stderr
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- build ------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export async function buildCommand(ctx, parsed, rest, options) {
|
|
73
|
+
const selection = selectBoard(ctx, parsed)
|
|
74
|
+
const app = optionalApp(ctx, parsed, rest, selection)
|
|
75
|
+
const base = io(parsed, options)
|
|
76
|
+
const env = createChildEnv(ctx, base.env)
|
|
77
|
+
switch (selection.adapter) {
|
|
78
|
+
case 'esp32-idf': {
|
|
79
|
+
buildEsp32Firmware({ ctx, selection, app, env, bleOta: bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr, configureOnly: flag(parsed, 'configure-only') })
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
case 'rp2350-pico':
|
|
83
|
+
buildRp2350({ ctx, selection, app, env, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr, configureOnly: flag(parsed, 'configure-only') })
|
|
84
|
+
return 0
|
|
85
|
+
case 'geaos-linux':
|
|
86
|
+
case 'geaos-arm64':
|
|
87
|
+
return runGeaos({ ctx, selection, action: 'build', app, positionals: rest, env, dryRun: base.dryRun, stdout: base.stdout })
|
|
88
|
+
case 'taurus-s3':
|
|
89
|
+
return runTargetHook({ ctx, selection, action: 'build', app, env, dryRun: base.dryRun, stdout: base.stdout })
|
|
90
|
+
default:
|
|
91
|
+
fail(`Unknown adapter '${selection.adapter}' for target '${selection.target}'.`, ExitCode.usage)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function cleanCommand(ctx, parsed, rest, options) {
|
|
96
|
+
const selection = selectBoard(ctx, parsed)
|
|
97
|
+
const app = optionalApp(ctx, parsed, rest, selection)
|
|
98
|
+
const base = io(parsed, options)
|
|
99
|
+
if (selection.adapter === 'esp32-idf') {
|
|
100
|
+
fullCleanEsp32({ ctx, selection, app, env: base.env, stdout: base.stdout })
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
if (selection.adapter === 'rp2350-pico') {
|
|
104
|
+
const dir = rp2350BuildDir(ctx, selection)
|
|
105
|
+
base.stdout(`Removing build artifacts in ${dir}...`)
|
|
106
|
+
const { rmSync } = await import('node:fs')
|
|
107
|
+
rmSync(dir, { recursive: true, force: true })
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
fail(`'clean' is not supported for ${selection.adapter} boards.`, ExitCode.usage)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- flash / run --------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
async function flashEsp32(ctx, parsed, rest, options, selection, { monitor }) {
|
|
116
|
+
const base = io(parsed, options)
|
|
117
|
+
const env = createChildEnv(ctx, base.env)
|
|
118
|
+
const idf = requireEspIdf(env, base.stdout)
|
|
119
|
+
const flashEnv = idf.env
|
|
120
|
+
const opts = flashOptions(flashEnv, { manualBoot: flag(parsed, 'manual-boot'), noReset: option(parsed, 'reset') === false, baud: option(parsed, 'flash-baud', '') })
|
|
121
|
+
const common = { idf, selection, options: opts, port: selection.port, env: flashEnv, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr }
|
|
122
|
+
const slotImages = optionList(parsed, 'slot-image')
|
|
123
|
+
const eraseSlotName = option(parsed, 'erase-slot', '')
|
|
124
|
+
const slot = option(parsed, 'slot', '')
|
|
125
|
+
const explicitImage = option(parsed, 'image', '')
|
|
126
|
+
const app = explicitImage && !option(parsed, 'app') && !rest[0] ? null : optionalApp(ctx, parsed, rest, selection, { required: !explicitImage && !slotImages.length && !eraseSlotName && !flag(parsed, 'restore-boot') })
|
|
127
|
+
|
|
128
|
+
const buildDir = esp32BuildDir(ctx, selection, app?.id, flashEnv)
|
|
129
|
+
const images = { ...buildImages(buildDir), buildDir }
|
|
130
|
+
|
|
131
|
+
if (eraseSlotName) {
|
|
132
|
+
await eraseSlot({ ...common, slot: eraseSlotName })
|
|
133
|
+
return 0
|
|
134
|
+
}
|
|
135
|
+
if (flag(parsed, 'restore-boot')) {
|
|
136
|
+
await restoreBootMetadata({ ...common, images })
|
|
137
|
+
return 0
|
|
138
|
+
}
|
|
139
|
+
if (slotImages.length) {
|
|
140
|
+
await flashImageSet({ ...common, images, slotImages })
|
|
141
|
+
postFlashRestartNote(selection, base.stderr)
|
|
142
|
+
return 0
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let image = explicitImage ? path.resolve(ctx.cwd, explicitImage) : images.app
|
|
146
|
+
if (!explicitImage && !flag(parsed, 'no-build')) {
|
|
147
|
+
buildEsp32Firmware({ ctx, selection, app, env, bleOta: bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
|
|
148
|
+
}
|
|
149
|
+
const appLabel = app?.id || 'prebuilt image'
|
|
150
|
+
if (slot) {
|
|
151
|
+
await stageImage({ ...common, image, slot, appLabel })
|
|
152
|
+
return 0
|
|
153
|
+
}
|
|
154
|
+
await flashFirmware({ ...common, images, appImage: image, appLabel })
|
|
155
|
+
if (!monitor) {
|
|
156
|
+
postFlashRestartNote(selection, base.stderr)
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
return monitorCommand(ctx, parsed, rest, options, selection)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function flashCommand(ctx, parsed, rest, options, { monitor = false } = {}) {
|
|
163
|
+
const selection = selectBoard(ctx, parsed, { usbPort: true })
|
|
164
|
+
const base = io(parsed, options)
|
|
165
|
+
const env = createChildEnv(ctx, base.env)
|
|
166
|
+
switch (selection.adapter) {
|
|
167
|
+
case 'esp32-idf':
|
|
168
|
+
return flashEsp32(ctx, parsed, rest, options, selection, { monitor })
|
|
169
|
+
case 'rp2350-pico': {
|
|
170
|
+
const app = optionalApp(ctx, parsed, rest, selection)
|
|
171
|
+
const { uf2 } = buildRp2350({ ctx, selection, app, env, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
|
|
172
|
+
flashRp2350({ selection, uf2, env, dryRun: base.dryRun, stdout: base.stdout })
|
|
173
|
+
return monitor ? monitorCommand(ctx, parsed, rest, options, selection) : 0
|
|
174
|
+
}
|
|
175
|
+
case 'geaos-linux':
|
|
176
|
+
case 'geaos-arm64': {
|
|
177
|
+
const app = optionalApp(ctx, parsed, rest, selection)
|
|
178
|
+
return runGeaos({ ctx, selection, action: monitor ? 'flash-monitor' : 'flash', app, positionals: rest, env, dryRun: base.dryRun, stdout: base.stdout })
|
|
179
|
+
}
|
|
180
|
+
case 'taurus-s3': {
|
|
181
|
+
const app = optionalApp(ctx, parsed, rest, selection)
|
|
182
|
+
return runTargetHook({ ctx, selection, action: 'flash', app, env, dryRun: base.dryRun, stdout: base.stdout })
|
|
183
|
+
}
|
|
184
|
+
default:
|
|
185
|
+
fail(`Unknown adapter '${selection.adapter}' for target '${selection.target}'.`, ExitCode.usage)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---- ota ------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
export async function otaCommand(ctx, parsed, rest, options) {
|
|
192
|
+
const transport = option(parsed, 'transport', 'wifi')
|
|
193
|
+
if (transport !== 'wifi' && transport !== 'ble') fail("--transport must be 'wifi' or 'ble'.", ExitCode.usage)
|
|
194
|
+
const selection = selectBoard(ctx, parsed, transport === 'wifi' ? { otaHost: true } : {})
|
|
195
|
+
if (selection.adapter !== 'esp32-idf') fail(`OTA is only available for ESP32 boards (board '${selection.boardName}' is ${selection.adapter}).`, ExitCode.usage)
|
|
196
|
+
const base = io(parsed, options)
|
|
197
|
+
const env = createChildEnv(ctx, base.env)
|
|
198
|
+
const slot = option(parsed, 'slot', '')
|
|
199
|
+
const eraseSlotName = option(parsed, 'erase-slot', '')
|
|
200
|
+
const explicitImage = option(parsed, 'image', '')
|
|
201
|
+
|
|
202
|
+
if (eraseSlotName) {
|
|
203
|
+
await otaEraseSlot({ selection, host: selection.host, slot: eraseSlotName, dryRun: base.dryRun, stdout: base.stdout })
|
|
204
|
+
return 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const app = explicitImage && !option(parsed, 'app') && !rest[0] ? null : optionalApp(ctx, parsed, rest, selection, { required: !explicitImage })
|
|
208
|
+
let image = explicitImage ? path.resolve(ctx.cwd, explicitImage) : ''
|
|
209
|
+
if (!explicitImage) {
|
|
210
|
+
const prepared = flag(parsed, 'no-build')
|
|
211
|
+
? { images: buildImages(esp32BuildDir(ctx, selection, app.id, env)) }
|
|
212
|
+
: buildEsp32Firmware({ ctx, selection, app, env, bleOta: transport === 'ble' || bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
|
|
213
|
+
image = prepared.images.app
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (transport === 'ble') {
|
|
217
|
+
bleOta({ cliPackageRoot: ctx.cliPackageRoot, image, deviceName: option(parsed, 'device', ''), env, dryRun: base.dryRun, stdout: base.stdout })
|
|
218
|
+
return 0
|
|
219
|
+
}
|
|
220
|
+
if (slot) {
|
|
221
|
+
await otaStage({ selection, host: selection.host, image, slot, boot: flag(parsed, 'boot'), reboot: flag(parsed, 'reboot'), appLabel: app?.id || 'prebuilt image', dryRun: base.dryRun, stdout: base.stdout })
|
|
222
|
+
return 0
|
|
223
|
+
}
|
|
224
|
+
await otaFlash({ host: selection.host, image, dryRun: base.dryRun, stdout: base.stdout })
|
|
225
|
+
if (flag(parsed, 'monitor') || flag(parsed, 'logs')) {
|
|
226
|
+
if (base.dryRun) return 0
|
|
227
|
+
await waitForReboot({ host: selection.host, stdout: base.stdout })
|
|
228
|
+
return logsCommand(ctx, { ...parsed, options: { ...parsed.options, transport: 'wifi', follow: true } }, rest, options, selection)
|
|
229
|
+
}
|
|
230
|
+
return 0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---- monitor / logs / screenshot ---------------------------------------------
|
|
234
|
+
|
|
235
|
+
function abortOnSigint() {
|
|
236
|
+
const controller = new AbortController()
|
|
237
|
+
const onSigint = () => controller.abort()
|
|
238
|
+
process.once('SIGINT', onSigint)
|
|
239
|
+
return { signal: controller.signal, release: () => process.removeListener('SIGINT', onSigint) }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function withDevice(ctx, parsed, options, selection, transport, fn) {
|
|
243
|
+
const base = io(parsed, options)
|
|
244
|
+
if (base.dryRun) {
|
|
245
|
+
base.stdout(`[dry-run] ${transport} device: ${transport === 'wifi' ? option(parsed, 'host', '') || selection.otaHost : selection.port || `usb serial ${selection.usbSerial}`}`)
|
|
246
|
+
return 0
|
|
247
|
+
}
|
|
248
|
+
const device = await openDevice({
|
|
249
|
+
selection,
|
|
250
|
+
transport,
|
|
251
|
+
host: option(parsed, 'host', ''),
|
|
252
|
+
port: selection.port,
|
|
253
|
+
env: base.env,
|
|
254
|
+
trace: flag(parsed, 'trace'),
|
|
255
|
+
stderr: base.stderr,
|
|
256
|
+
waitSeconds: Number(option(parsed, 'wait', base.env.GEA_ESP32_MONITOR_WAIT_SECONDS || 0))
|
|
257
|
+
})
|
|
258
|
+
try {
|
|
259
|
+
return (await fn(device, base)) ?? 0
|
|
260
|
+
} finally {
|
|
261
|
+
await device.close()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function monitorCommand(ctx, parsed, rest, options, preselected = null) {
|
|
266
|
+
const selection = preselected || selectBoard(ctx, parsed, { usbPort: true })
|
|
267
|
+
if (selection.adapter === 'geaos-linux' || selection.adapter === 'geaos-arm64') {
|
|
268
|
+
const base = io(parsed, options)
|
|
269
|
+
return runGeaos({ ctx, selection, action: 'monitor', positionals: rest, env: createChildEnv(ctx, base.env), dryRun: base.dryRun, stdout: base.stdout })
|
|
270
|
+
}
|
|
271
|
+
return withDevice(ctx, parsed, options, selection, 'usb', async (device, base) => {
|
|
272
|
+
base.stderr(`Opening serial monitor on ${device.description}... (Ctrl+C to exit)`)
|
|
273
|
+
const { signal, release } = abortOnSigint()
|
|
274
|
+
try {
|
|
275
|
+
await device.logs({ write: (line) => base.stdout(line), timestamps: flag(parsed, 'timestamps'), logFile: option(parsed, 'log-file', ''), signal })
|
|
276
|
+
} finally {
|
|
277
|
+
release()
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function logsCommand(ctx, parsed, rest, options, preselected = null) {
|
|
283
|
+
const selection = preselected || selectBoard(ctx, parsed, {})
|
|
284
|
+
const transport = chooseTransport(option(parsed, 'transport', 'auto'), selection, { host: option(parsed, 'host', '') })
|
|
285
|
+
if (transport === 'usb') return monitorCommand(ctx, parsed, rest, options, preselected || selectBoard(ctx, parsed, { usbPort: true }))
|
|
286
|
+
return withDevice(ctx, parsed, options, selection, 'wifi', async (device, base) => {
|
|
287
|
+
const follow = flag(parsed, 'follow')
|
|
288
|
+
base.stderr(`Connecting to diagnostics stream at ${device.host}:8081${follow ? ' (Ctrl+C to exit)' : ''}`)
|
|
289
|
+
const { signal, release } = abortOnSigint()
|
|
290
|
+
try {
|
|
291
|
+
await device.logs({ follow, write: (chunk) => process.stdout.write(chunk), timeoutMs: Number(option(parsed, 'timeout', 10)) * 1000, signal })
|
|
292
|
+
} finally {
|
|
293
|
+
release()
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function screenshotCommand(ctx, parsed, rest, options) {
|
|
299
|
+
const selection = selectBoard(ctx, parsed, {})
|
|
300
|
+
const transport = chooseTransport(option(parsed, 'transport', 'auto'), selection, { host: option(parsed, 'host', '') })
|
|
301
|
+
const file = path.resolve(ctx.cwd, rest[0] || option(parsed, 'out', '') || 'screenshot.png')
|
|
302
|
+
const usbSelection = transport === 'usb' ? selectBoard(ctx, parsed, { usbPort: true }) : selection
|
|
303
|
+
return withDevice(ctx, parsed, options, usbSelection, transport, async (device, base) => {
|
|
304
|
+
const timeoutMs = Number(option(parsed, 'timeout', transport === 'wifi' ? 30 : 12)) * 1000
|
|
305
|
+
const shot = await saveScreenshot(device, file, { timeoutMs, legacy: flag(parsed, 'legacy') })
|
|
306
|
+
base.stdout(`Saved ${shot.width}x${shot.height} screenshot${shot.app ? ` of ${shot.app}` : ''} from ${device.description} to ${file}`)
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---- devctl -------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
const devctlUsage = `gea devctl <verb> [args] --board <alias> [--transport auto|usb|wifi]
|
|
313
|
+
|
|
314
|
+
Verbs (USB, GEADEV protocol):
|
|
315
|
+
ping | app | state | mem | summary | i2cscan | reboot
|
|
316
|
+
node <class> hit <x> <y> tap <x> <y> [holdMs]
|
|
317
|
+
drag <x1> <y1> <x2> <y2> [steps] [delayMs] swipe <x> <y1> <y2>
|
|
318
|
+
back key <code> notify <text>
|
|
319
|
+
storage get <key> | storage set <key> <value>
|
|
320
|
+
set-default <app-id> set-time [epochSeconds]
|
|
321
|
+
brightness [0-100] ls [path] rm <path>
|
|
322
|
+
push <local> <remote> [--base64] pull <remote> <local>
|
|
323
|
+
playfile <path>
|
|
324
|
+
Verbs (WiFi):
|
|
325
|
+
hbm on|off -- high-brightness mode (POST /display/hbm)`
|
|
326
|
+
|
|
327
|
+
export async function devctlCommand(ctx, parsed, rest, options) {
|
|
328
|
+
const verb = rest[0]
|
|
329
|
+
const args = rest.slice(1)
|
|
330
|
+
if (!verb || verb === 'help') {
|
|
331
|
+
options.stdout(devctlUsage)
|
|
332
|
+
return verb ? 0 : ExitCode.usage
|
|
333
|
+
}
|
|
334
|
+
const selection = selectBoard(ctx, parsed, {})
|
|
335
|
+
const wifiVerbs = new Set(['hbm'])
|
|
336
|
+
const requested = option(parsed, 'transport', wifiVerbs.has(verb) ? 'wifi' : 'usb')
|
|
337
|
+
const transport = chooseTransport(requested, selection, { host: option(parsed, 'host', '') })
|
|
338
|
+
const usbSelection = transport === 'usb' ? selectBoard(ctx, parsed, { usbPort: true }) : selection
|
|
339
|
+
return withDevice(ctx, parsed, options, usbSelection, transport, async (device, base) => {
|
|
340
|
+
if (verb === 'hbm') {
|
|
341
|
+
const state = args[0]
|
|
342
|
+
if (!['on', 'off', '1', '0'].includes(state)) fail('devctl hbm expects on|off.', ExitCode.usage)
|
|
343
|
+
const reply = await device.hbm(state === 'on' || state === '1')
|
|
344
|
+
base.stdout(JSON.stringify(reply))
|
|
345
|
+
return 0
|
|
346
|
+
}
|
|
347
|
+
if (device.kind !== 'usb') fail(`devctl ${verb} needs the USB transport.`, ExitCode.usage)
|
|
348
|
+
const d = device.serial
|
|
349
|
+
const num = (value, name) => {
|
|
350
|
+
const n = Number(value)
|
|
351
|
+
if (!Number.isFinite(n)) fail(`devctl ${verb}: ${name} must be a number.`, ExitCode.usage)
|
|
352
|
+
return n
|
|
353
|
+
}
|
|
354
|
+
const need = (count) => {
|
|
355
|
+
if (args.length < count) fail(`devctl ${verb} needs ${count} argument(s).\n${devctlUsage}`, ExitCode.usage)
|
|
356
|
+
}
|
|
357
|
+
const print = (value) => base.stdout(String(value))
|
|
358
|
+
switch (verb) {
|
|
359
|
+
case 'ping': print(await geadev.ping(d)); break
|
|
360
|
+
case 'app': print(await geadev.app(d)); break
|
|
361
|
+
case 'state': print(await geadev.state(d)); break
|
|
362
|
+
case 'mem': print(await geadev.mem(d)); break
|
|
363
|
+
case 'summary': print(await geadev.summary(d)); break
|
|
364
|
+
case 'i2cscan': print(await geadev.i2cScan(d)); break
|
|
365
|
+
case 'reboot': print(await geadev.reboot(d)); break
|
|
366
|
+
case 'node': need(1); print(await geadev.node(d, args[0])); break
|
|
367
|
+
case 'hit': need(2); print(await geadev.hit(d, num(args[0], 'x'), num(args[1], 'y'))); break
|
|
368
|
+
case 'tap': need(2); print(await geadev.tap(d, num(args[0], 'x'), num(args[1], 'y'), args[2] ? num(args[2], 'holdMs') : 80)); break
|
|
369
|
+
case 'drag': need(4); print(await geadev.drag(d, num(args[0], 'x1'), num(args[1], 'y1'), num(args[2], 'x2'), num(args[3], 'y2'), args[4] ? num(args[4], 'steps') : 6, args[5] ? num(args[5], 'delayMs') : 24)); break
|
|
370
|
+
case 'swipe': need(3); print(await geadev.swipe(d, num(args[0], 'x'), num(args[1], 'y1'), num(args[2], 'y2'))); break
|
|
371
|
+
case 'back': print(await geadev.back(d)); break
|
|
372
|
+
case 'key': need(1); print(await geadev.key(d, args[0])); break
|
|
373
|
+
case 'notify': need(1); print(await geadev.notify(d, args.join(' '))); break
|
|
374
|
+
case 'storage':
|
|
375
|
+
need(2)
|
|
376
|
+
if (args[0] === 'get') print((await geadev.storageGet(d, args[1])).value)
|
|
377
|
+
else if (args[0] === 'set') { need(3); print(await geadev.storageSet(d, args[1], args.slice(2).join(' '))) }
|
|
378
|
+
else fail('devctl storage expects get <key> or set <key> <value>.', ExitCode.usage)
|
|
379
|
+
break
|
|
380
|
+
case 'set-default': need(1); print(await geadev.setDefault(d, args[0])); break
|
|
381
|
+
case 'set-time': print(await geadev.setTime(d, args[0] ? num(args[0], 'epoch') : Math.floor(Date.now() / 1000))); break
|
|
382
|
+
case 'brightness': print(await geadev.brightness(d, args[0] === undefined ? undefined : num(args[0], 'value'))); break
|
|
383
|
+
case 'ls': print(await geadev.ls(d, args[0] || '/sdcard')); break
|
|
384
|
+
case 'rm': need(1); print(await geadev.rm(d, args[0])); break
|
|
385
|
+
case 'push': need(2); print(await geadev.pushFile(d, path.resolve(ctx.cwd, args[0]), args[1], { base64: flag(parsed, 'base64'), stderr: base.stderr })); break
|
|
386
|
+
case 'pull': need(2); print(await geadev.pullFile(d, args[0], path.resolve(ctx.cwd, args[1]))); break
|
|
387
|
+
case 'playfile': need(1); print(await geadev.playFile(d, args[0])); break
|
|
388
|
+
default:
|
|
389
|
+
fail(`Unknown devctl verb '${verb}'.\n${devctlUsage}`, ExitCode.usage)
|
|
390
|
+
}
|
|
391
|
+
return 0
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ---- geaos device passthrough -------------------------------------------------
|
|
396
|
+
|
|
397
|
+
export async function geaosDeviceCommand(ctx, parsed, rest, options) {
|
|
398
|
+
const selection = selectBoard(ctx, parsed, { usbPort: true })
|
|
399
|
+
if (selection.adapter !== 'geaos-linux' && selection.adapter !== 'geaos-arm64') fail(`'${rest[0]}' is a geaos device action; board '${selection.boardName}' is ${selection.adapter}.`, ExitCode.usage)
|
|
400
|
+
const base = io(parsed, options)
|
|
401
|
+
return runGeaos({ ctx, selection, action: rest[0], positionals: rest.slice(1), env: createChildEnv(ctx, base.env), dryRun: base.dryRun, stdout: base.stdout })
|
|
402
|
+
}
|