diff --git a/main.html b/main.html
index af120f4..7c8f1b2 100644
--- a/main.html
+++ b/main.html
@@ -72,7 +72,7 @@
// Commands (PBIO_PYBRICKS_COMMAND_*)
const CMD_STOP_USER_PROGRAM = 0;
const CMD_START_USER_PROGRAM = 1;
- const CMD_WRITE_USER_PROGRAM_META = 3; // not used here
+ const CMD_WRITE_USER_PROGRAM_META = 3;
const CMD_WRITE_USER_RAM = 4;
// Events (PBIO_PYBRICKS_EVENT_*)
@@ -83,6 +83,44 @@
let maxCharSize = 20;
let lastStatusFlags = 0;
let stdoutBuffer = '';
+
+ // ── Pynamics ──────────────────────────────────────────────────────────────
+ // Matches: ESC[?PYN;;;;...~
+ const PYNAMICS_REGEX = /\x1b\[\?PYN;([^;~]+);([^~]*?)~/g;
+
+ const PYNAMICS_EVENT_MAP = {
+ '1': 'click',
+ '2': 'focus',
+ '3': 'blur',
+ '4': 'navigate',
+ '5': 'state_change',
+ '6': 'notify',
+ '7': 'render',
+ '99': 'custom',
+ };
+
+ /**
+ * Strips all Pynamics escape codes from a string, fires console.log for
+ * each one, and returns the cleaned string (without the escape sequences).
+ */
+ function processPynamics(text) {
+ let match;
+ // Reset lastIndex so repeated calls work correctly
+ PYNAMICS_REGEX.lastIndex = 0;
+
+ while ((match = PYNAMICS_REGEX.exec(text)) !== null) {
+ const eventId = match[1];
+ const args = match[2].split(';').filter(Boolean);
+ const name = PYNAMICS_EVENT_MAP[eventId] ?? `unknown(${eventId})`;
+
+ console.log('[pynamics]', { event: name, eventId, args });
+ }
+
+ // Remove all escape sequences from the visible text
+ return text.replace(PYNAMICS_REGEX, '');
+ }
+ // ─────────────────────────────────────────────────────────────────────────
+
function log(msg) {
const el = document.getElementById('log');
el.textContent += msg + '\n';
@@ -104,10 +142,7 @@
const total = arrays.reduce((s, a) => s + a.length, 0);
const out = new Uint8Array(total);
let off = 0;
- for (const a of arrays) {
- out.set(a, off);
- off += a.length;
- }
+ for (const a of arrays) { out.set(a, off); off += a.length; }
return out;
}
function hexpreview(data, n = 12) {
@@ -135,40 +170,36 @@
function decodeStatus(flags) {
lastStatusFlags = flags;
const bits = {
- battLow: !!(flags & (1 << 0)),
+ battLow: !!(flags & (1 << 0)),
battCrit: !!(flags & (1 << 1)),
- running: !!(flags & (1 << 6)),
+ running: !!(flags & (1 << 6)),
shutdown: !!(flags & (1 << 7)),
- btn: !!(flags & (1 << 5)),
+ btn: !!(flags & (1 << 5)),
hostConn: !!(flags & (1 << 9)),
- fileIO: !!(flags & (1 << 13)),
+ fileIO: !!(flags & (1 << 13)),
};
return `running=${bits.running} btn=${bits.btn} hostConn=${bits.hostConn} fileIO=${bits.fileIO} shutdown=${bits.shutdown}`;
}
function isBusy() {
- const running = !!(lastStatusFlags & (1 << 6));
- const fileIO = !!(lastStatusFlags & (1 << 13));
- return running || fileIO;
+ return !!(lastStatusFlags & (1 << 6)) || !!(lastStatusFlags & (1 << 13));
}
function onNotify(event) {
const data = new Uint8Array(event.target.value.buffer);
- if (data.length === 0) {
- log('[event] empty notification');
- return;
- }
+ if (data.length === 0) { log('[event] empty notification'); return; }
const type = data[0];
if (type === EVT_WRITE_STDOUT) {
- // Buffer and reassemble split packets
stdoutBuffer += new TextDecoder().decode(data.slice(1));
const lines = stdoutBuffer.split('\n');
- stdoutBuffer = lines.pop() || ''; // Keep incomplete line
+ stdoutBuffer = lines.pop() || '';
for (const line of lines) {
- if (line.trim()) {
- log('[stdout] ' + line);
+ // Strip Pynamics codes and fire console events before displaying
+ const clean = processPynamics(line);
+ if (clean.trim()) {
+ log('[stdout] ' + clean);
}
}
} else if (type === EVT_STATUS_REPORT) {
@@ -198,7 +229,6 @@
setStatus('Connecting GATT...');
server = await device.gatt.connect();
- // Device Information
try {
const di = await server.getPrimaryService(DI_SERVICE_UUID);
const sw = new TextDecoder().decode(
@@ -215,7 +245,6 @@
const svc = await server.getPrimaryService(PYBRICKS_SERVICE_UUID);
- // Capabilities
try {
const capChar = await svc.getCharacteristic(PYBRICKS_CAPABILITIES_UUID);
const raw = await capChar.readValue();
@@ -239,7 +268,7 @@
await cmdChar.startNotifications();
cmdChar.addEventListener('characteristicvaluechanged', onNotify);
- await sleep(500); // wait for first status
+ await sleep(500);
log(`Connected to ${device.name}`);
setStatus(`Connected: ${device.name}`);
@@ -265,11 +294,8 @@
}
async function upload() {
- if (!cmdChar) {
- log('Not connected to hub.');
- return;
- }
- // Always stop any running program first
+ if (!cmdChar) { log('Not connected to hub.'); return; }
+
log('Stopping any running program...');
try {
await writeCmd(new Uint8Array([CMD_STOP_USER_PROGRAM]), 'STOP');
@@ -277,39 +303,27 @@
} catch (e) {
log('Stop command ignored (no program running)');
}
- /*if (isBusy()) {
- log('Hub is busy (program running or file IO), not uploading.');
- return;
- }*/
document.getElementById('uploadBtn').disabled = true;
try {
const resp = await fetch('./main.bin');
- if (!resp.ok) {
- throw new Error(`fetch main.bin: ${resp.status} ${resp.statusText}`);
- }
+ if (!resp.ok) throw new Error(`fetch main.bin: ${resp.status} ${resp.statusText}`);
const blob = new Uint8Array(await resp.arrayBuffer());
log(`\nLoaded main.bin: ${blob.length} bytes`);
log(`Preview : ${hexpreview(blob, 20)}`);
const maxProgSize = 261512;
- if (blob.length > maxProgSize) {
- throw new Error(`Program too large for hub (max ${maxProgSize} bytes)`);
- }
+ if (blob.length > maxProgSize) throw new Error(`Program too large (max ${maxProgSize} bytes)`);
setStatus('Uploading...');
- const PAYLOAD = maxCharSize - 5; // 1 cmd + 4 offset
+ const PAYLOAD = maxCharSize - 5;
const chunks = Math.ceil(blob.length / PAYLOAD);
log(`\n── Upload ${blob.length} bytes in ${chunks} chunks ──`);
for (let offset = 0; offset < blob.length; offset += PAYLOAD) {
const chunk = blob.slice(offset, Math.min(offset + PAYLOAD, blob.length));
- const packet = concat(
- new Uint8Array([CMD_WRITE_USER_RAM]),
- u32le(offset),
- chunk
- );
+ const packet = concat(new Uint8Array([CMD_WRITE_USER_RAM]), u32le(offset), chunk);
await writeCmd(packet, `RAM@${offset}`);
}
log('All chunks sent ✓');
@@ -342,9 +356,7 @@
}
async function disconnect() {
- if (device && device.gatt.connected) {
- device.gatt.disconnect();
- }
+ if (device && device.gatt.connected) device.gatt.disconnect();
}