@esp-vision/device-core 0.4.1 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2 (unreleased)
4
+
5
+ ### Fixed
6
+
7
+ - Route browser ROM reset calls through the active flash stub after upload, preserving its two-byte status decoder during P4 watchdog register writes; report reset failure diagnostics without invalidating successful firmware verification.
8
+
3
9
  ## 0.4.1 (unreleased)
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esp-vision/device-core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Shared EV-MUX, framed REPL, device session, and action contracts for ESP Vision clients.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "title": "ESP Vision Device Actions",
4
- "deviceCoreVersion": "0.4.1",
4
+ "deviceCoreVersion": "0.4.2",
5
5
  "schemaVersion": 1,
6
6
  "actions": [
7
7
  {
@@ -15,6 +15,13 @@ export function browserRomCompatibility(loader) {
15
15
  }
16
16
  }
17
17
  }
18
+ // runStub returns a separate loader. Its command replies use two status bytes;
19
+ // the ROM loader still expects four and cannot issue reset register writes.
20
+ let activeStub
21
+ const hardReset = loader.hardReset
22
+ loader.hardReset = function (...args) {
23
+ return activeStub ? activeStub.hardReset(...args) : hardReset.apply(this, args)
24
+ }
18
25
  const runStub = loader.runStub
19
26
  loader.runStub = async function (skipFlashDetection = false) {
20
27
  const memFinish = this.memFinish, packet = this.readPacket
@@ -34,6 +41,7 @@ export function browserRomCompatibility(loader) {
34
41
  const stub = await runStub.call(this, skipFlashDetection)
35
42
  if (!stub.IS_STUB) throw new Error('This device has no supported browser flash stub')
36
43
  browserStubCompatibility(stub)
44
+ activeStub = stub
37
45
  return stub
38
46
  } finally { this.memFinish = memFinish; this.readPacket = packet }
39
47
  }
@@ -112,7 +112,10 @@ export class FirmwareFlashSession {
112
112
  }
113
113
  await this.reset()
114
114
  }
115
- catch { onPhase('Firmware verified. Press RESET or reconnect power to start it.'); return { verified: true, needsReset: true } }
115
+ catch (error) {
116
+ onPhase('Firmware verified. Automatic reset did not complete; wait for runtime reconnection or press RESET.')
117
+ return { verified: true, needsReset: true, resetError: { code: error?.code ?? 'RESET_FAILED', message: error?.message ?? String(error) } }
118
+ }
116
119
  onPhase('Firmware verified and reset. Reconnect in the IDE to verify EV-MUX v4.')
117
120
  return { verified: true, needsReset: false }
118
121
  } catch (error) { this.state = 'error'; throw error }
@@ -23,7 +23,7 @@ try {
23
23
  assert.ok(Object.keys(result.metafile.inputs).some(path => path.includes('stubs/esp32p4.json')))
24
24
  assert.ok(Object.keys(result.metafile.inputs).some(path => path.includes('stubs/esp32s31.json')))
25
25
  assert.ok(!Object.keys(result.metafile.inputs).some(path => /src\/(node|cli)/.test(path)))
26
- const context = vm.createContext({ EventTarget, URL, TextDecoder, TextEncoder, Uint8Array, AbortController, setTimeout, clearTimeout, console, crypto: webcrypto, structuredClone })
26
+ const context = vm.createContext({ atob, btoa, EventTarget, URL, TextDecoder, TextEncoder, Uint8Array, AbortController, setTimeout, clearTimeout, console, crypto: webcrypto, structuredClone })
27
27
  vm.runInContext(code, context)
28
28
  let opens = 0, closes = 0
29
29
  const adapter = context.DeviceCore.openBrowserRom({ port: {
@@ -46,6 +46,29 @@ try {
46
46
  raw._inputBuffer.push(0xc0, 0xdb, 0x47, 0xc0)
47
47
  await assert.rejects(raw.readPacket(100), /Invalid SLIP escape/)
48
48
  }
49
+ // Exercise real upstream stub status decoding. A ROM reset consumes four
50
+ // status bytes while the running stub replies with two; route reset to it.
51
+ {
52
+ const raw = new context.DeviceCore.TestLoader({ getInfo: () => ({ usbVendorId: 0x303a, usbProductId: 0x1001 }) }, { log() {}, error() {}, debug() {} })
53
+ raw.chipFamily = 0x3280; raw.chipRevision = 302
54
+ raw.memBegin = raw.memBlock = raw.memFinish = async () => {}
55
+ raw.readPacket = async () => new Uint8Array([79, 72, 65, 73])
56
+ context.DeviceCore.testCompatibility(raw)
57
+ const stub = await raw.runStub(true)
58
+ let writes = 0
59
+ stub.detectUsbConnectionType = async () => true
60
+ stub.getUsbMode = async () => ({ mode: 'usb-jtag-serial' })
61
+ stub.sendCommand = async () => { writes++ }
62
+ stub.getResponse = async () => [0, new Uint8Array([0, 0])]
63
+ // Confirm the original ROM instance rejects the exact same stub response.
64
+ raw.detectUsbConnectionType = stub.detectUsbConnectionType
65
+ raw.getUsbMode = stub.getUsbMode
66
+ raw.sendCommand = async () => {}
67
+ raw.getResponse = stub.getResponse
68
+ await assert.rejects(context.DeviceCore.TestLoader.prototype.hardReset.call(raw), /status|response|enough/i)
69
+ await raw.hardReset()
70
+ assert.equal(writes, 4, 'P4 watchdog writes must use the running stub status decoder')
71
+ }
49
72
  const prototype = context.DeviceCore.TestLoader.prototype
50
73
  const originals = Object.fromEntries(['initialize', 'disconnect', 'getSecurityInfo', 'runStub', 'hardReset'].map(key => [key, prototype[key]]))
51
74
  let initializeCalls = 0, disconnectCalls = 0
@@ -115,6 +138,7 @@ try {
115
138
  async flashData() { writes++; writing.abort() },
116
139
  async readFlash(offset) { return offset === 32768 ? table : data },
117
140
  async readRegister() { return 0 },
141
+ async hardReset() { resets++ },
118
142
  }
119
143
  }
120
144
  prototype.hardReset = async () => { resets++ }
@@ -80,7 +80,10 @@ test('failed readback never reports verified or resets the device', async () =>
80
80
  })
81
81
  test('reset failure returns verified with explicit manual reset requirement', async () => {
82
82
  const f = await fixture(); f.loader.hardReset = async () => { throw new Error('reset unavailable') }
83
- assert.equal((await f.device.firmware.update(f.request)).needsReset, true)
83
+ const result = await f.device.firmware.update(f.request)
84
+ assert.equal(result.needsReset, true)
85
+ assert.equal(result.verified, true)
86
+ assert.deepEqual(result.resetError, { code: 'RESET_FAILED', message: 'reset unavailable' })
84
87
  })
85
88
 
86
89
  test('firmware check lists all board versions and selects an explicit older version', async () => {