Compare commits

2 Commits
main ... dev

Author SHA1 Message Date
8c40e111cd Test ANSI escape codes 2026-03-26 17:33:24 +00:00
092e635fd1 Test ANSI escape codes 2026-03-26 17:32:56 +00:00
2 changed files with 68 additions and 48 deletions

106
main.html
View File

@@ -72,7 +72,7 @@
// Commands (PBIO_PYBRICKS_COMMAND_*) // Commands (PBIO_PYBRICKS_COMMAND_*)
const CMD_STOP_USER_PROGRAM = 0; const CMD_STOP_USER_PROGRAM = 0;
const CMD_START_USER_PROGRAM = 1; 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; const CMD_WRITE_USER_RAM = 4;
// Events (PBIO_PYBRICKS_EVENT_*) // Events (PBIO_PYBRICKS_EVENT_*)
@@ -83,6 +83,44 @@
let maxCharSize = 20; let maxCharSize = 20;
let lastStatusFlags = 0; let lastStatusFlags = 0;
let stdoutBuffer = ''; let stdoutBuffer = '';
// ── Pynamics ──────────────────────────────────────────────────────────────
// Matches: ESC[?PYN;<EVENT_ID>;<arg1>;<arg2>;...~
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) { function log(msg) {
const el = document.getElementById('log'); const el = document.getElementById('log');
el.textContent += msg + '\n'; el.textContent += msg + '\n';
@@ -104,10 +142,7 @@
const total = arrays.reduce((s, a) => s + a.length, 0); const total = arrays.reduce((s, a) => s + a.length, 0);
const out = new Uint8Array(total); const out = new Uint8Array(total);
let off = 0; let off = 0;
for (const a of arrays) { for (const a of arrays) { out.set(a, off); off += a.length; }
out.set(a, off);
off += a.length;
}
return out; return out;
} }
function hexpreview(data, n = 12) { function hexpreview(data, n = 12) {
@@ -135,40 +170,36 @@
function decodeStatus(flags) { function decodeStatus(flags) {
lastStatusFlags = flags; lastStatusFlags = flags;
const bits = { const bits = {
battLow: !!(flags & (1 << 0)), battLow: !!(flags & (1 << 0)),
battCrit: !!(flags & (1 << 1)), battCrit: !!(flags & (1 << 1)),
running: !!(flags & (1 << 6)), running: !!(flags & (1 << 6)),
shutdown: !!(flags & (1 << 7)), shutdown: !!(flags & (1 << 7)),
btn: !!(flags & (1 << 5)), btn: !!(flags & (1 << 5)),
hostConn: !!(flags & (1 << 9)), 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}`; return `running=${bits.running} btn=${bits.btn} hostConn=${bits.hostConn} fileIO=${bits.fileIO} shutdown=${bits.shutdown}`;
} }
function isBusy() { function isBusy() {
const running = !!(lastStatusFlags & (1 << 6)); return !!(lastStatusFlags & (1 << 6)) || !!(lastStatusFlags & (1 << 13));
const fileIO = !!(lastStatusFlags & (1 << 13));
return running || fileIO;
} }
function onNotify(event) { function onNotify(event) {
const data = new Uint8Array(event.target.value.buffer); const data = new Uint8Array(event.target.value.buffer);
if (data.length === 0) { if (data.length === 0) { log('[event] empty notification'); return; }
log('[event] empty notification');
return;
}
const type = data[0]; const type = data[0];
if (type === EVT_WRITE_STDOUT) { if (type === EVT_WRITE_STDOUT) {
// Buffer and reassemble split packets
stdoutBuffer += new TextDecoder().decode(data.slice(1)); stdoutBuffer += new TextDecoder().decode(data.slice(1));
const lines = stdoutBuffer.split('\n'); const lines = stdoutBuffer.split('\n');
stdoutBuffer = lines.pop() || ''; // Keep incomplete line stdoutBuffer = lines.pop() || '';
for (const line of lines) { for (const line of lines) {
if (line.trim()) { // Strip Pynamics codes and fire console events before displaying
log('[stdout] ' + line); const clean = processPynamics(line);
if (clean.trim()) {
log('[stdout] ' + clean);
} }
} }
} else if (type === EVT_STATUS_REPORT) { } else if (type === EVT_STATUS_REPORT) {
@@ -198,7 +229,6 @@
setStatus('Connecting GATT...'); setStatus('Connecting GATT...');
server = await device.gatt.connect(); server = await device.gatt.connect();
// Device Information
try { try {
const di = await server.getPrimaryService(DI_SERVICE_UUID); const di = await server.getPrimaryService(DI_SERVICE_UUID);
const sw = new TextDecoder().decode( const sw = new TextDecoder().decode(
@@ -215,7 +245,6 @@
const svc = await server.getPrimaryService(PYBRICKS_SERVICE_UUID); const svc = await server.getPrimaryService(PYBRICKS_SERVICE_UUID);
// Capabilities
try { try {
const capChar = await svc.getCharacteristic(PYBRICKS_CAPABILITIES_UUID); const capChar = await svc.getCharacteristic(PYBRICKS_CAPABILITIES_UUID);
const raw = await capChar.readValue(); const raw = await capChar.readValue();
@@ -239,7 +268,7 @@
await cmdChar.startNotifications(); await cmdChar.startNotifications();
cmdChar.addEventListener('characteristicvaluechanged', onNotify); cmdChar.addEventListener('characteristicvaluechanged', onNotify);
await sleep(500); // wait for first status await sleep(500);
log(`Connected to ${device.name}`); log(`Connected to ${device.name}`);
setStatus(`Connected: ${device.name}`); setStatus(`Connected: ${device.name}`);
@@ -265,11 +294,8 @@
} }
async function upload() { async function upload() {
if (!cmdChar) { if (!cmdChar) { log('Not connected to hub.'); return; }
log('Not connected to hub.');
return;
}
// Always stop any running program first
log('Stopping any running program...'); log('Stopping any running program...');
try { try {
await writeCmd(new Uint8Array([CMD_STOP_USER_PROGRAM]), 'STOP'); await writeCmd(new Uint8Array([CMD_STOP_USER_PROGRAM]), 'STOP');
@@ -277,39 +303,27 @@
} catch (e) { } catch (e) {
log('Stop command ignored (no program running)'); 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; document.getElementById('uploadBtn').disabled = true;
try { try {
const resp = await fetch('./main.bin'); const resp = await fetch('./main.bin');
if (!resp.ok) { if (!resp.ok) throw new Error(`fetch main.bin: ${resp.status} ${resp.statusText}`);
throw new Error(`fetch main.bin: ${resp.status} ${resp.statusText}`);
}
const blob = new Uint8Array(await resp.arrayBuffer()); const blob = new Uint8Array(await resp.arrayBuffer());
log(`\nLoaded main.bin: ${blob.length} bytes`); log(`\nLoaded main.bin: ${blob.length} bytes`);
log(`Preview : ${hexpreview(blob, 20)}`); log(`Preview : ${hexpreview(blob, 20)}`);
const maxProgSize = 261512; const maxProgSize = 261512;
if (blob.length > maxProgSize) { if (blob.length > maxProgSize) throw new Error(`Program too large (max ${maxProgSize} bytes)`);
throw new Error(`Program too large for hub (max ${maxProgSize} bytes)`);
}
setStatus('Uploading...'); setStatus('Uploading...');
const PAYLOAD = maxCharSize - 5; // 1 cmd + 4 offset const PAYLOAD = maxCharSize - 5;
const chunks = Math.ceil(blob.length / PAYLOAD); const chunks = Math.ceil(blob.length / PAYLOAD);
log(`\n── Upload ${blob.length} bytes in ${chunks} chunks ──`); log(`\n── Upload ${blob.length} bytes in ${chunks} chunks ──`);
for (let offset = 0; offset < blob.length; offset += PAYLOAD) { for (let offset = 0; offset < blob.length; offset += PAYLOAD) {
const chunk = blob.slice(offset, Math.min(offset + PAYLOAD, blob.length)); const chunk = blob.slice(offset, Math.min(offset + PAYLOAD, blob.length));
const packet = concat( const packet = concat(new Uint8Array([CMD_WRITE_USER_RAM]), u32le(offset), chunk);
new Uint8Array([CMD_WRITE_USER_RAM]),
u32le(offset),
chunk
);
await writeCmd(packet, `RAM@${offset}`); await writeCmd(packet, `RAM@${offset}`);
} }
log('All chunks sent ✓'); log('All chunks sent ✓');
@@ -342,9 +356,7 @@
} }
async function disconnect() { async function disconnect() {
if (device && device.gatt.connected) { if (device && device.gatt.connected) device.gatt.disconnect();
device.gatt.disconnect();
}
} }
</script> </script>
</body> </body>

10
main.py
View File

@@ -2,9 +2,17 @@ from pybricks.hubs import PrimeHub
from pybricks.parameters import Color from pybricks.parameters import Color
from pybricks.tools import wait from pybricks.tools import wait
import micropython import micropython
hub = PrimeHub() hub = PrimeHub()
hub.light.on(Color.RED) hub.light.on(Color.RED)
wait(100) wait(100)
print("Hello, World!") print("Hello, World!")
print("This is the spike prime hub.") print("This is the spike prime hub.")
print(micropython.mem_info()) #print(micropython.mem_info())
# Pynamics escape code test — event 99 (custom), name="hubReady", payload="spike-prime"
print("\x1b[?PYN;99;hubReady;spike-prime~")
# Another test — event 6 (notify), level="info", message="Boot complete"
print("\x1b[?PYN;6;info;Boot complete~")