fix: Different approach on the COM Port and MQTT Fix
This commit is contained in:
@@ -401,25 +401,37 @@ function StepFlash({ device, onFlashed }) {
|
|||||||
// Start reading raw UART output from the device after flash+reset
|
// Start reading raw UART output from the device after flash+reset
|
||||||
const startSerialMonitor = async (port) => {
|
const startSerialMonitor = async (port) => {
|
||||||
serialActiveRef.current = true;
|
serialActiveRef.current = true;
|
||||||
// Give the OS a moment to fully release the port from esptool before we re-open
|
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
// Wait for the OS/browser to fully release the port after esptool closed it
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await port.open({ baudRate: 115200 });
|
await port.open({ baudRate: 115200 });
|
||||||
} catch (openErr) {
|
} catch (openErr) {
|
||||||
appendSerial(`[Error opening port: ${openErr.message}]`);
|
appendSerial(`[Error opening port: ${openErr.message}]`);
|
||||||
|
scrollSerial();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use getReader() directly — avoids locking issues from pipeTo()
|
||||||
|
let reader;
|
||||||
|
try {
|
||||||
|
reader = port.readable.getReader();
|
||||||
|
} catch (readerErr) {
|
||||||
|
appendSerial(`[Error getting reader: ${readerErr.message}]`);
|
||||||
|
scrollSerial();
|
||||||
|
try { await port.close(); } catch (_) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const decoder = new TextDecoderStream();
|
|
||||||
const readableStreamClosed = port.readable.pipeTo(decoder.writable);
|
|
||||||
const reader = decoder.readable.getReader();
|
|
||||||
serialReaderRef.current = reader;
|
serialReaderRef.current = reader;
|
||||||
|
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
let lineBuffer = "";
|
let lineBuffer = "";
|
||||||
try {
|
try {
|
||||||
while (serialActiveRef.current) {
|
while (serialActiveRef.current) {
|
||||||
const { value, done: streamDone } = await reader.read();
|
const { value, done: streamDone } = await reader.read();
|
||||||
if (streamDone) break;
|
if (streamDone) break;
|
||||||
lineBuffer += value;
|
lineBuffer += textDecoder.decode(value, { stream: true });
|
||||||
const lines = lineBuffer.split(/\r?\n/);
|
const lines = lineBuffer.split(/\r?\n/);
|
||||||
lineBuffer = lines.pop(); // keep incomplete last fragment
|
lineBuffer = lines.pop(); // keep incomplete last fragment
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
@@ -431,13 +443,16 @@ function StepFlash({ device, onFlashed }) {
|
|||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reader cancelled on cleanup — expected
|
// Reader cancelled on cleanup — expected
|
||||||
|
} finally {
|
||||||
|
try { reader.releaseLock(); } catch (_) {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopSerialMonitor = async () => {
|
const stopSerialMonitor = async () => {
|
||||||
serialActiveRef.current = false;
|
serialActiveRef.current = false;
|
||||||
try { await serialReaderRef.current?.cancel(); } catch (_) {}
|
try { await serialReaderRef.current?.cancel(); } catch (_) {}
|
||||||
try { portRef.current?.close(); } catch (_) {}
|
try { serialReaderRef.current?.releaseLock(); } catch (_) {}
|
||||||
|
try { await portRef.current?.close(); } catch (_) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFlash = async () => {
|
const handleFlash = async () => {
|
||||||
@@ -724,31 +739,30 @@ function StepVerify({ device, onVerified }) {
|
|||||||
setTimedOut(false);
|
setTimedOut(false);
|
||||||
setError("");
|
setError("");
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = new Date().toISOString();
|
||||||
|
|
||||||
intervalRef.current = setInterval(async () => {
|
intervalRef.current = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.get(`/manufacturing/devices/${device.serial_number}`);
|
// Poll the heartbeat endpoint — device is verified when it sends a heartbeat
|
||||||
if (data.mfg_status === "provisioned") {
|
// after we started polling (i.e. after the flash completed)
|
||||||
|
const hbData = await api.get(
|
||||||
|
`/mqtt/heartbeats/${device.serial_number}?limit=1&offset=0`
|
||||||
|
);
|
||||||
|
if (hbData.heartbeats && hbData.heartbeats.length > 0) {
|
||||||
|
const latest = hbData.heartbeats[0];
|
||||||
|
const receivedAt = latest.received_at;
|
||||||
|
if (receivedAt && receivedAt > startTime) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(timeoutRef.current);
|
||||||
onVerified(data);
|
// Promote device status to provisioned
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Also accept any last_seen update (heartbeat) as evidence of life
|
|
||||||
if (data.last_seen) {
|
|
||||||
const ts = new Date(data.last_seen).getTime();
|
|
||||||
if (ts > startTime) {
|
|
||||||
clearInterval(intervalRef.current);
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
// Promote to provisioned
|
|
||||||
try {
|
try {
|
||||||
await api.request(`/manufacturing/devices/${device.serial_number}/status`, {
|
await api.request(`/manufacturing/devices/${device.serial_number}/status`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({ status: "provisioned", note: "Auto-verified via wizard" }),
|
body: JSON.stringify({ status: "provisioned", note: "Auto-verified via wizard" }),
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
onVerified({ ...data, mfg_status: "provisioned" });
|
const deviceData = await api.get(`/manufacturing/devices/${device.serial_number}`);
|
||||||
|
onVerified({ ...deviceData, mfg_status: "provisioned" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user