Report the upload address and required network, honor AP switches on an existing uploader, and keep delayed WebConfig cleanup from disabling OTA WiFi. Preserve actionable startup errors and account for OTA in wireless state controls. Exercise the production OTA lifecycle and stock website console, including power-saving idle sessions, in host regression tests.
76 lines
3.7 KiB
JavaScript
76 lines
3.7 KiB
JavaScript
// Only the SerialPort device boundary is simulated. The console, CRLF parser,
|
|
// encoder, streams, open/close and command writes come from the pinned website.
|
|
import assert from 'node:assert/strict';
|
|
import {spawn} from 'node:child_process';
|
|
import {readFileSync} from 'node:fs';
|
|
import {createInterface} from 'node:readline';
|
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
|
|
const {SerialConsole} = await import('data:text/javascript;base64,' +
|
|
readFileSync(process.argv[2]).toString('base64'));
|
|
const firmware = spawn(process.argv[3], [], {stdio:['pipe','pipe','inherit']});
|
|
let stream, pending, state;
|
|
let writes = 0;
|
|
createInterface({input:firmware.stdout}).on('line', line => {
|
|
if (line.startsWith('DATA ')) {
|
|
const bytes = Buffer.from(line.slice(5), 'hex');
|
|
// Delimiters must work when CR and LF arrive in separate USB transfers.
|
|
for (let n=0; n<bytes.length; n++) stream.enqueue(new Uint8Array(bytes.subarray(n,n+1)));
|
|
} else if (line.startsWith('STATE ')) state=line.slice(6).split(' ').map(Number);
|
|
else if (line==='DONE') {const done=pending;pending=null;done.resolve();}
|
|
});
|
|
firmware.on('exit', code => {if(pending) pending.reject(Error('Firmware exited: '+code));});
|
|
function command(text) {
|
|
assert(!pending, 'Peripheral calls must be serialized');
|
|
return new Promise((resolve,reject)=>{pending={resolve,reject};firmware.stdin.write(text+'\n');});
|
|
}
|
|
class Port {
|
|
async open(options) {
|
|
assert.deepEqual(options,{baudRate:115200});
|
|
this.readable=new ReadableStream({start(controller){stream=controller;},cancel(){stream=null;}});
|
|
this.writable=new WritableStream({write(bytes){++writes;return command('WRITE '+Buffer.from(bytes).toString('hex'));}});
|
|
}
|
|
async close(){await command('CLOSE');}
|
|
async setSignals(){throw Error('The stock console must work without a reset or explicit DTR');}
|
|
}
|
|
let client, lines;
|
|
async function connect() {
|
|
lines=[];client=new SerialConsole(new Port());client.onOutput=line=>lines.push(line);
|
|
client.connect(); await delay(0); assert(client.connected);
|
|
}
|
|
async function query(text, expected, label) {
|
|
const start=lines.length;
|
|
await client.sendCommand(text);
|
|
// All device work is synchronous at the pipe boundary, but Web Streams
|
|
// delivery is asynchronous. Bound it rather than making a wall-time sleep.
|
|
for(let n=0;n<50;n++) {
|
|
if(lines.slice(start).join('').includes(expected)) return;
|
|
await delay(2);
|
|
}
|
|
assert.fail(label+': stock console did not display the firmware reply');
|
|
}
|
|
try {
|
|
await connect();
|
|
await query('set powersaving on',' -> OK - powersaving on\n','power-saving setter');
|
|
await query('get powersaving',' -> > on\n','power-saving getter');
|
|
for(const elapsed of [121000,180000,360000,1800000,10800000,0xfffffffe]) {
|
|
const before=writes;
|
|
await command('TIME '+elapsed);
|
|
assert.equal(writes,before,'An idle web console gained an artificial keepalive');
|
|
await query('get powersaving',' -> > on\n','idle query');
|
|
assert.equal(state[1],0,'An open web console entered light sleep');
|
|
}
|
|
await client.disconnect();await connect();
|
|
await query('get powersaving',' -> > on\n','reopen query');
|
|
await query('set powersaving off',' -> OK - powersaving off\n','disable power saving');
|
|
await command('HOST_OFF');await command('TIME 121000');
|
|
assert.equal(state[1],0,'Disabled power saving slept');
|
|
await query('set powersaving on',' -> OK - powersaving on\n','enable battery power saving');
|
|
await command('TIME 242000');
|
|
assert.equal(state[1],1,'USB protection permanently disabled battery power saving');
|
|
console.log('Official console: CRLF, no-DTR idle, power saving, reopen and battery sleep passed');
|
|
} finally {
|
|
if(client?.connected) await client.disconnect();
|
|
firmware.stdin.end();
|
|
}
|