Spaces:
Running
Running
| /* | |
| * VALIS Node Firmware — ESP32-C3 | |
| * Variable Arrived Location In Superposition | |
| * | |
| * Proof of concept: Meilong scatter protocol over serial. | |
| * Phase 1: Pi sends message via serial, ESP32 scrambles into 3 XOR shares, | |
| * outputs each share on separate "channels" (simulated via tagged serial output). | |
| * Pi collects shares and reassembles. | |
| * | |
| * Phase 2 (with coupling hardware): inject shares onto power line via DAC/ADC. | |
| * | |
| * "It's a walkie-talkie with math." | |
| */ | |
| // === MEILONG PROTOCOL === | |
| void scramble(const uint8_t* data, size_t len, uint8_t* s1, uint8_t* s2, uint8_t* s3) { | |
| // Generate two random shares, XOR to create third | |
| for (size_t i = 0; i < len; i++) { | |
| s1[i] = (uint8_t)random(256); | |
| s2[i] = (uint8_t)random(256); | |
| s3[i] = data[i] ^ s1[i] ^ s2[i]; | |
| } | |
| } | |
| void reassemble(const uint8_t* s1, const uint8_t* s2, const uint8_t* s3, size_t len, uint8_t* out) { | |
| for (size_t i = 0; i < len; i++) { | |
| out[i] = s1[i] ^ s2[i] ^ s3[i]; | |
| } | |
| } | |
| // === BASE64 (minimal for share transport) === | |
| static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
| String toBase64(const uint8_t* data, size_t len) { | |
| String out = ""; | |
| for (size_t i = 0; i < len; i += 3) { | |
| uint32_t n = ((uint32_t)data[i]) << 16; | |
| if (i + 1 < len) n |= ((uint32_t)data[i + 1]) << 8; | |
| if (i + 2 < len) n |= data[i + 2]; | |
| out += b64chars[(n >> 18) & 0x3F]; | |
| out += b64chars[(n >> 12) & 0x3F]; | |
| out += (i + 1 < len) ? b64chars[(n >> 6) & 0x3F] : '='; | |
| out += (i + 2 < len) ? b64chars[n & 0x3F] : '='; | |
| } | |
| return out; | |
| } | |
| // === LED FEEDBACK === | |
| void blinkLED(int times, int delayMs) { | |
| for (int i = 0; i < times; i++) { | |
| digitalWrite(LED_PIN, HIGH); | |
| delay(delayMs); | |
| digitalWrite(LED_PIN, LOW); | |
| delay(delayMs); | |
| } | |
| } | |
| void setLED(bool on) { | |
| digitalWrite(LED_PIN, on ? HIGH : LOW); | |
| } | |
| // === NODE STATE === | |
| enum NodeMode { | |
| MODE_IDLE, | |
| MODE_SCATTER, | |
| MODE_COLLECT, | |
| MODE_RELAY | |
| }; | |
| NodeMode currentMode = MODE_IDLE; | |
| String nodeId = ""; | |
| unsigned long lastHeartbeat = 0; | |
| // === SERIAL PROTOCOL === | |
| // Commands from Pi: | |
| // {"cmd":"scatter","msg":"hello world"} — scramble and output 3 shares | |
| // {"cmd":"collect","s1":"...","s2":"...","s3":"..."} — reassemble from 3 shares | |
| // {"cmd":"ping"} — heartbeat check | |
| // {"cmd":"id"} — report node ID | |
| // | |
| // Responses to Pi: | |
| // {"type":"share","index":0,"data":"base64...","hop":"uuid"} | |
| // {"type":"share","index":1,"data":"base64...","hop":"uuid"} | |
| // {"type":"share","index":2,"data":"base64...","hop":"uuid"} | |
| // {"type":"assembled","msg":"hello world","hop":"uuid"} | |
| // {"type":"pong","id":"node-xxxx","uptime":12345} | |
| String serialBuffer = ""; | |
| void processCommand(const String& json) { | |
| JsonDocument doc; | |
| DeserializationError err = deserializeJson(doc, json); | |
| if (err) { | |
| Serial.println("{\"type\":\"error\",\"msg\":\"bad json\"}"); | |
| return; | |
| } | |
| const char* cmd = doc["cmd"]; | |
| if (!cmd) return; | |
| // === PING === | |
| if (strcmp(cmd, "ping") == 0) { | |
| JsonDocument resp; | |
| resp["type"] = "pong"; | |
| resp["id"] = nodeId; | |
| resp["uptime"] = millis() / 1000; | |
| resp["mode"] = currentMode; | |
| serializeJson(resp, Serial); | |
| Serial.println(); | |
| blinkLED(1, 50); | |
| return; | |
| } | |
| // === ID === | |
| if (strcmp(cmd, "id") == 0) { | |
| JsonDocument resp; | |
| resp["type"] = "identity"; | |
| resp["id"] = nodeId; | |
| resp["chip"] = "ESP32-C3"; | |
| resp["protocol"] = "VALIS/Meilong"; | |
| resp["version"] = "0.1"; | |
| serializeJson(resp, Serial); | |
| Serial.println(); | |
| return; | |
| } | |
| // === SCATTER (Meilong scramble) === | |
| if (strcmp(cmd, "scatter") == 0) { | |
| const char* msg = doc["msg"]; | |
| if (!msg) { | |
| Serial.println("{\"type\":\"error\",\"msg\":\"no message\"}"); | |
| return; | |
| } | |
| setLED(true); | |
| currentMode = MODE_SCATTER; | |
| size_t len = strlen(msg); | |
| if (len > MAX_MSG_LEN) len = MAX_MSG_LEN; | |
| uint8_t s1[MAX_MSG_LEN], s2[MAX_MSG_LEN], s3[MAX_MSG_LEN]; | |
| scramble((const uint8_t*)msg, len, s1, s2, s3); | |
| // Generate hop ID | |
| String hopId = String(random(100000, 999999)); | |
| // Output each share with a staggered delay (simulating different routes) | |
| uint8_t* shares[] = {s1, s2, s3}; | |
| const char* routes[] = {"alpha", "beta", "gamma"}; | |
| for (int i = 0; i < 3; i++) { | |
| delay(100 + random(400)); // staggered — different "routes" take different time | |
| JsonDocument shareDoc; | |
| shareDoc["type"] = "share"; | |
| shareDoc["index"] = i; | |
| shareDoc["route"] = routes[i]; | |
| shareDoc["data"] = toBase64(shares[i], len); | |
| shareDoc["hop"] = hopId; | |
| shareDoc["len"] = len; | |
| serializeJson(shareDoc, Serial); | |
| Serial.println(); | |
| blinkLED(1, 30); | |
| } | |
| // Confirm scatter complete | |
| JsonDocument done; | |
| done["type"] = "scattered"; | |
| done["hop"] = hopId; | |
| done["shares"] = 3; | |
| done["msg_len"] = len; | |
| done["note"] = "the dragon sleeps between the nodes"; | |
| serializeJson(done, Serial); | |
| Serial.println(); | |
| currentMode = MODE_IDLE; | |
| setLED(false); | |
| return; | |
| } | |
| // === HEARTBEAT === | |
| Serial.println("{\"type\":\"error\",\"msg\":\"unknown command\"}"); | |
| } | |
| // === SETUP === | |
| void setup() { | |
| Serial.begin(115200); | |
| pinMode(LED_PIN, OUTPUT); | |
| // Generate node ID from chip MAC | |
| uint64_t mac = ESP.getEfuseMac(); | |
| nodeId = "valis-" + String((uint32_t)(mac >> 24), HEX); | |
| // Seed random from analog noise | |
| randomSeed(analogRead(0) ^ micros()); | |
| delay(1000); | |
| // Boot announcement | |
| JsonDocument boot; | |
| boot["type"] = "boot"; | |
| boot["id"] = nodeId; | |
| boot["protocol"] = "VALIS/Meilong"; | |
| boot["version"] = "0.1"; | |
| boot["chip"] = "ESP32-C3"; | |
| boot["msg"] = "node online — the dragon stirs"; | |
| serializeJson(boot, Serial); | |
| Serial.println(); | |
| blinkLED(3, 100); | |
| } | |
| // === LOOP === | |
| void loop() { | |
| // Read serial input | |
| while (Serial.available()) { | |
| char c = Serial.read(); | |
| if (c == '\n' || c == '\r') { | |
| if (serialBuffer.length() > 0) { | |
| processCommand(serialBuffer); | |
| serialBuffer = ""; | |
| } | |
| } else { | |
| serialBuffer += c; | |
| if (serialBuffer.length() > 1024) { | |
| serialBuffer = ""; // overflow protection | |
| } | |
| } | |
| } | |
| // Periodic heartbeat (every 30s) | |
| if (millis() - lastHeartbeat > 30000) { | |
| lastHeartbeat = millis(); | |
| JsonDocument hb; | |
| hb["type"] = "heartbeat"; | |
| hb["id"] = nodeId; | |
| hb["uptime"] = millis() / 1000; | |
| hb["mode"] = currentMode; | |
| serializeJson(hb, Serial); | |
| Serial.println(); | |
| } | |
| } | |