Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c40e111cd | |||
| 092e635fd1 |
106
main.html
106
main.html
@@ -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
10
main.py
@@ -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~")
|
||||||
Reference in New Issue
Block a user