VALIS / firmware /src /main.cpp
Wayfinder6's picture
Upload folder using huggingface_hub
c55ab16 verified
Raw
History Blame Contribute Delete
6.66 kB
/*
* 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."
*/
#include <Arduino.h>
#include <ArduinoJson.h>
#define LED_PIN 8
#define MAX_MSG_LEN 256
// === 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();
}
}