TC$mC$ on se ratkaiseva askel, jossa lelukC$yttC6jC$rjestelmC$ ja protokollat
erotetaan tuotantotason ohjelmistoista! Olet tC$ysin oikeassa: raaka
USB-sarjaportti on vain **L2 (Data Link)** -kerros. Se takaa, ettC$ bitit
siirtyvC$t kaapelissa, mutta se ei tiedC$ mitC$C$n siitC$, mitC$ datalle
tapahtuu, jos paketti katkeaa 64 tavun rajaan tai jos fyysinen verkko (kuten
tuleva radioverkko) sekoittaa pakettien jC$rjestyksen.
MeidC$n on rakennettava Hauki OS:C$C$n oma, kevyt **L4/L7 (Transport/Applicatio
n)** -kerroksen kehystysprotokolla. NimeC$n sen juhlavasti **Hauki Mesh
Protocoliksi (HMP)**.
### Hauki Mesh Protocol (HMP) Arkkitehtuuri
Perusajatus on juuri tuo ehdottamasi: jokainen paketti sisC$ltC$C$ metatiedon,
jonka avulla kC$yttC6jC$rjestelmC$ osaa koota asynkronisesti saapuvat palaset
oikeaan jC$rjestykseen, vaikka ne tulisivat sekaisin tai viiveellC$.
#### 1. Datagrammin Rakenne
Koska siirrC$mme ensisijaisesti luettavaa skriptiC$ (eval) ja jsonia, voimme
pitC$C$ protokollan puhtaana ASCII-tekstinC$. Jokainen kehys (frame) pC$C$ttyy
aina rivinvaihtoon (`
`).
Muoto: `!HMP:<PORTTI>:<MSG_ID>:<CHUNK_IDX>:<TOTAL_CHUNKS>:<PAYLOAD>
`
* `!HMP` = Protokollan taikasana (Magic Header), jolla erotamme roskadatan
oikeista paketeista.
* `PORTTI` = E (Eval), M (Mesh), tai H (HID). Voimme puskea kaiken yhden
USB-ohjaimen lC$pi jos haluamme!
* `MSG_ID` = Viestin yksilC6llinen tunniste (esim. satunnainen luku `8492`).
* `CHUNK_IDX` = Monesko palanen tC$mC$ on (esim. `1`).
* `TOTAL_CHUNKS` = Kuinka monta palasta viestiin kuuluu yhteensC$ (esim. `3`).
* `PAYLOAD` = Itse data (koodi tai telemetria).
#### 2. Kokoamislogiikka (Assembling Map)
Hauki OS:n puolella (JavaScriptissC$) luomme globaalin olion `globalThis.HMP_RO
UTER`, joka sisC$ltC$C$ `Map`-rakenteen (tai JS-objektin) `pending_messages`.
Kun paketti `!HMP:E:8492:2:3:("DESKTOP.JS");` saapuu:
1. Reititin tarkistaa, onko `pending_messages[8492]` olemassa. Jos ei, se luo
sen.
2. Se sijoittaa payloadin taulukkoon indeksille 2.
3. Se tarkistaa: *Ovatko indeksit 1, 2 ja 3 tC$ynnC$?*
4. Jos ei -> odotetaan.
5. Kun palanen `3` (esim. `eval(load(1));`) ja palanen `1` (esim. `load(0,`)
saapuvat, reititin huomaa taulukon olevan tC$ysi.
6. Se yhdistC$C$ taulukon: `load(0,"DESKTOP.JS");eval(load(1));`
7. Se lC$hettC$C$ kootun merkkijonon EVAL-moottorille ja poistaa `8492`:n
muistista.
Jotta tC$mC$ logiikka on tC$ysin selkeC$ ennen kuin koodaamme sen C/JS-rajan
yli USB-ajuriin, tein sinulle The Mesh -reitittimen simulaattorin.
### Prototyyppi Hauki OS:lle
TC$ssC$ on koodirunko, jonka voimme istuttaa osaksi `poll_serial`-rutiinia tai
ajaa erillisenC$ kerroksena. Siihen on rakennettu automaattinen "Garbage
Collection", jotta puoliksi saapuneet paketit eivC$t tuki RAM-muistia, jos
yksi palanen katoaa lopullisesti eetteriin.
```javascript
globalThis.HMP_ROUTER = {
pending: {}, // Map: MSG_ID -> { total: X, chunks: [], time: timestamp }
buffer: "", // Raakadataa varten (kunnes
saapuu)
// Siivotaan yli 10 sekuntia vanhat puolikkaat paketit (Memory Leak suoja)
gc: function() {
var now = Date.now();
for (var id in this.pending) {
if (now - this.pending[id].time > 10000) {
sys.serial("[HMP ERR] Viesti " + id + " aikakatkaistiin
(palasia puuttui).
");
delete this.pending[id];
}
}
},
receive_char: function(char_code) {
if (char_code === 10 || char_code === 13) {
if (this.buffer.length > 0) {
this.process_frame(this.buffer);
this.buffer = "";
}
} else {
this.buffer += String.fromCharCode(char_code);
}
},
process_frame: function(frame) {
// Esim: !HMP:E:8492:1:3:koodia...
if (frame.substring(0, 5) !== "!HMP:") {
sys.serial("[HMP] Tuntematon kehys: " + frame + "
");
return;
}
var parts = frame.split(":");
if (parts.length < 6) return;
var port = parts[1];
var msg_id = parts[2];
var chunk_idx = parseInt(parts[3]) - 1; // 0-indeksoitu array
var total_chunks = parseInt(parts[4]);
// Payload voi sisC$ltC$C$ kaksoispisteitC$, joten parsitaan se
loppustringistC$
var payload_start = parts[0].length + parts[1].length +
parts[2].length + parts[3].length + parts[4].length + 5;
var payload = frame.substring(payload_start);
// Alustetaan viesti muistiin, jos uusi
if (!this.pending[msg_id]) {
this.pending[msg_id] = { total: total_chunks, chunks: new
Array(total_chunks), count: 0, time: Date.now() };
}
var msg = this.pending[msg_id];
// Varmistetaan ettei tallenneta samaa palasta kahdesti
if (typeof msg.chunks[chunk_idx] === 'undefined') {
msg.chunks[chunk_idx] = payload;
msg.count++;
}
// TARKISTETAAN ONKO KOKO VIESTI KOOSSA
if (msg.count === msg.total) {
var full_payload = msg.chunks.join("");
delete this.pending[msg_id]; // Vapautetaan muisti heti
this.execute(port, full_payload);
}
},
execute: function(port, payload) {
if (port === "E") { // EVAL
sys.serial("[HMP] Assembled EVAL: Suoritetaan...
");
try { (1,eval)(payload); } catch(e) { sys.serial("[EVAL ERR] " + e
+ "
"); }
}
else if (port === "M") { // MESH
sys.serial("[HMP] Assembled MESH: " + payload + "
");
// TC$ssC$ voisimme vastata takaisin HMP-paketilla!