diff --git a/.gitignore b/.gitignore index 4ef2717..fc3163d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,16 @@ *.log +*.o +*.d +*.bin +*.elf +*.map +*.ld +*.cache +*.a +**/.vscode/ +**/output + + + + diff --git a/ColorWheel.png b/ColorWheel.png new file mode 100644 index 0000000..d142d68 Binary files /dev/null and b/ColorWheel.png differ diff --git a/Segment_data_diagram-pdn.png b/Segment_data_diagram-pdn.png new file mode 100644 index 0000000..016691d Binary files /dev/null and b/Segment_data_diagram-pdn.png differ diff --git a/chromance-firmware/chromance-firmware.ino b/chromance-firmware/chromance-firmware.ino index 91ff8a5..9d154a1 100644 --- a/chromance-firmware/chromance-firmware.ino +++ b/chromance-firmware/chromance-firmware.ino @@ -6,108 +6,91 @@ (C) Voidstar Lab 2021 */ -#include +//#include +#include #include -#include -#include -#include +#include +#include +#include #include #include #include "mapping.h" #include "ripple.h" +#include "index.h" +#include +#include "debugUdp.h" -const char* ssid = "YourMom"; -const char* password = "is a nice lady"; - +#define HOSTNAME "ESP8266-OTA-" ///< Hostname. The setup function adds the Chip ID at the end. +#define NUMBER_OF_RIPPLES 30 // WiFi stuff - CHANGE FOR YOUR OWN NETWORK! -const IPAddress ip(4, 20, 6, 9); // IP address that THIS DEVICE should request -const IPAddress gateway(192, 168, 1, 1); // Your router -const IPAddress subnet(255, 255, 254, 0); // Your subnet mask (find it from your router's admin panel) -const int recv_port = 42069; // Port that OSC data should be sent to (pick one, put same one in EmotiBit's OSC Config XML file) +const IPAddress ip(192, 168, 5, 50); // IP address that THIS DEVICE should request +const IPAddress gateway(192, 168, 5, 1); // Your router +const IPAddress subnet(255, 255, 255, 0); // Your subnet mask (find it from your router's admin panel) +const int port = 23; -int lengths[] = {154, 168, 84, 154}; // Strips are different lengths because I am a dumb +//how many clients should be able to telnet to this ESP8266 +#define MAX_SRV_CLIENTS 2 -Adafruit_DotStar strip0(lengths[0], 15, 2, DOTSTAR_BRG); -Adafruit_DotStar strip1(lengths[1], 0, 4, DOTSTAR_BRG); -Adafruit_DotStar strip2(lengths[2], 16, 17, DOTSTAR_BRG); -Adafruit_DotStar strip3(lengths[3], 5, 18, DOTSTAR_BRG); +unsigned int localPort = 8888; // local port to listen on +unsigned int debugPort = 8000; // local port to listen on -Adafruit_DotStar strips[4] = {strip0, strip1, strip2, strip3}; +WiFiUDP Udp; -byte ledColors[40][14][3]; // LED buffer - each ripple writes to this, then we write this to the strips -float decay = 0.97; // Multiply all LED's by this amount each tick to create fancy fading tails +Adafruit_NeoPixel strip(NUM_OF_PIXELS, D4, NEO_GRB + NEO_KHZ800); +uint8_t ledColors[NUMBER_OF_SEGMENTS][LEDS_PER_SEGMENTS][3]; // LED buffer - each ripple writes to this, then we write this to the strips +float decay = 0.97; // Multiply all LED's by this amount each tick to create fancy fading tails +uint16_t g_hue = 40000; +uint32_t g_color; // These ripples are endlessly reused so we don't need to do any memory management -#define numberOfRipples 30 -Ripple ripples[numberOfRipples] = { - Ripple(0), - Ripple(1), - Ripple(2), - Ripple(3), - Ripple(4), - Ripple(5), - Ripple(6), - Ripple(7), - Ripple(8), - Ripple(9), - Ripple(10), - Ripple(11), - Ripple(12), - Ripple(13), - Ripple(14), - Ripple(15), - Ripple(16), - Ripple(17), - Ripple(18), - Ripple(19), - Ripple(20), - Ripple(21), - Ripple(22), - Ripple(23), - Ripple(24), - Ripple(25), - Ripple(26), - Ripple(27), - Ripple(28), - Ripple(29), +Ripple ripples[NUMBER_OF_RIPPLES] = { + Ripple(0), + Ripple(1), + Ripple(2), + Ripple(3), + Ripple(4), + Ripple(5), + Ripple(6), + Ripple(7), + Ripple(8), + Ripple(9), + Ripple(10), + Ripple(11), + Ripple(12), + Ripple(13), + Ripple(14), + Ripple(15), + Ripple(16), + Ripple(17), + Ripple(18), + Ripple(19), + Ripple(20), + Ripple(21), + Ripple(22), + Ripple(23), + Ripple(24), + Ripple(25), + Ripple(26), + Ripple(27), + Ripple(28), + Ripple(29) }; -// Biometric detection and interpretation -// IR (heartbeat) is used to fire outward ripples -float lastIrReading; // When our heart pumps, reflected IR drops sharply -float highestIrReading; // These vars let us detect this drop -unsigned long lastHeartbeat; // Track last heartbeat so we can detect noise/disconnections -#define heartbeatLockout 500 // Heartbeats that happen within this many milliseconds are ignored -#define heartbeatDelta 300 // Drop in reflected IR that constitutes a heartbeat - -// Heartbeat color ripples are proportional to skin temperature -#define lowTemperature 33.0 // Resting temperature -#define highTemperature 37.0 // Really fired up -float lastKnownTemperature = (lowTemperature + highTemperature) / 2.0; // Carries skin temperature from temperature callback to IR callback - -// EDA code was too unreliable and was cut. -// TODO: Rebuild EDA code - -// Gyroscope is used to reject data if you're moving too much -#define gyroAlpha 0.9 // Exponential smoothing constant -#define gyroThreshold 300 // Minimum angular velocity total (X+Y+Z) that disqualifies readings -float gyroX, gyroY, gyroZ; // If you don't have an EmotiBit or don't feel like wearing it, that's OK // We'll fire automatic pulses -#define randomPulsesEnabled true // Fire random rainbow pulses from random nodes +#define randomPulsesEnabled false // Fire random rainbow pulses from random nodes #define cubePulsesEnabled true // Draw cubes at random nodes -#define starburstPulsesEnabled true // Draw starbursts +#define starburstPulsesEnabled false // Draw starbursts #define simulatedBiometricsEnabled false // Simulate heartbeat and EDA ripples #define autoPulseTimeout 5000 // If no heartbeat is received in this many ms, begin firing random/simulated pulses -#define randomPulseTime 2000 // Fire a random pulse every (this many) ms -unsigned long lastRandomPulse; +#define randomPulseTime 5000 // Fire a random pulse every (this many) ms + byte lastAutoPulseNode = 255; -byte numberOfAutoPulseTypes = randomPulsesEnabled + cubePulsesEnabled + starburstPulsesEnabled; -byte currentAutoPulseType = 255; +byte currentAutoPulseType = 0; #define autoPulseChangeTime 30000 unsigned long lastAutoPulseChange; @@ -117,363 +100,526 @@ unsigned long lastAutoPulseChange; #define simulatedEdaVariance 10000 unsigned long nextSimulatedHeartbeat; unsigned long nextSimulatedEda; +int currentMode = 0; -void setup() { - Serial.begin(115200); +void renderCube(int node); +void renderUnstoppableSnake(int startingNode); +void renderContour(int node); +void renderStarburst(void); - Serial.println("*** LET'S GOOOOO ***"); +// Process the original chromance animation +void chromanceProcess(void); +void processUDP(void); +void setFixedColor(uint32_t color); - for (int i = 0; i < 4; i++) { - strips[i].begin(); - // strips[i].setBrightness(125); // If your PSU sucks, use this to limit the current - strips[i].show(); - } +ESP8266WebServer server(80); +debugUdp dbg(8000); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - WiFi.config(ip, gateway, subnet); - while (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.println("Connection Failed! Rebooting..."); - delay(5000); - ESP.restart(); - } +String page = MAIN_page; - Serial.print("WiFi connected, IP = "); - Serial.println(WiFi.localIP()); - - // Subscribe to OSC transmissions for important data - OscWiFi.subscribe(recv_port, "/EmotiBit/0/EDA", [](const OscMessage & m) { // This weird syntax is a lambda expression (anonymous nameless function) - lastKnownTemperature = m.arg(0); - }); - - OscWiFi.subscribe(recv_port, "/EmotiBit/0/GYRO:X", [](const OscMessage & m) { - gyroX = m.arg(0) * gyroAlpha + gyroX * (1 - gyroAlpha); - }); - - OscWiFi.subscribe(recv_port, "/EmotiBit/0/GYRO:Y", [](const OscMessage & m) { - gyroY = m.arg(0) * gyroAlpha + gyroY * (1 - gyroAlpha); - }); - - OscWiFi.subscribe(recv_port, "/EmotiBit/0/GYRO:Z", [](const OscMessage & m) { - gyroZ = m.arg(0) * gyroAlpha + gyroZ * (1 - gyroAlpha); - }); - - // Heartbeat detection and visualization happens here - OscWiFi.subscribe(recv_port, "/EmotiBit/0/PPG:IR", [](const OscMessage & m) { - float reading = m.arg(0); - Serial.println(reading); - - int hue = 0; - - // Ignore heartbeat when finger is wiggling around - it's not accurate - float gyroTotal = abs(gyroX) + abs(gyroY) + abs(gyroZ); - - if (gyroTotal < gyroThreshold && lastIrReading >= reading) { - // Our hand is sitting still and the reading dropped - let's pulse! - Serial.print("> "); - Serial.println(highestIrReading - reading); - if (highestIrReading - reading >= heartbeatDelta) { - if (millis() - lastHeartbeat >= heartbeatLockout) { - hue = fmap(lastKnownTemperature, lowTemperature, highTemperature, 0xFFFF, 0); - for (int i = 0; i < 6; i++) { - if (nodeConnections[15][i] > 0) { - bool firedRipple = false; - // Find a dead ripple to reuse it - for (int j = 0; j < 30; j++) { - if (!firedRipple && ripples[j].state == dead) { - ripples[j].start( - 15, - i, - strip0.ColorHSV(hue, 255, 255), - float(random(100)) / 100.0 * .2 + .8, - 500, - 2); - - firedRipple = true; - } - } - } - } - } +// Handle Web Requests - lastHeartbeat = millis(); - } +void handleRoot() { + dbg.print("Page opened"); + server.send(200, "text/html", page); +} + +void setCurrentMode(int mode){ + currentMode = mode; +} + +void setUDPMode(){ + setCurrentMode(1); + dbg.println("Setting to UDP listener "); + clearLeds(); + server.send(200, "text/html", page); +} + +void setChromanceMode(){ + setCurrentMode(0); + dbg.println("Setting to chromance mode "); + clearLeds(); + server.send(200, "text/html", page); +} + + +void handleNotFound() { + // digitalWrite(led, 1); + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } - else { - highestIrReading = 0; + server.send(404, "text/plain", message); + // digitalWrite(led, 0); } - lastIrReading = reading; - if (reading > highestIrReading) - highestIrReading = reading; - }); - - // Wireless OTA updating? On an ARDUINO?! It's more likely than you think! - ArduinoOTA - .onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else // U_SPIFFS - type = "filesystem"; - - // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() - Serial.println("Start updating " + type); - }) - .onEnd([]() { - Serial.println("\nEnd"); - }) - .onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - }) - .onError([](ota_error_t error) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - - ArduinoOTA.begin(); - - Serial.println("Ready for WiFi OTA updates"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); +void setupOTA(){ + String hostname(HOSTNAME); + // Start OTA server. + ArduinoOTA.setHostname((const char *)hostname.c_str()); + // Wireless OTA updating? On an ARDUINO?! It's more likely than you think! + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + dbg.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + dbg.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + dbg.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + dbg.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) dbg.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) dbg.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) dbg.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) dbg.println("Receive Failed"); + else if (error == OTA_END_ERROR) dbg.println("End Failed"); + }); + + ArduinoOTA.begin(); } -void loop() { - unsigned long benchmark = millis(); +void setupHTTPServer(){ + server.on("/", handleRoot); - OscWiFi.parse(); + server.on("/mode/set_hue", set_hue); + server.on("/mode/chromance", setChromanceMode); + server.on("/mode/udp", setUDPMode); + server.on("/inline", []() { + server.send(200, "text/plain", "this works as well"); + }); - ArduinoOTA.handle(); + server.onNotFound(handleNotFound); + server.begin(); +} - // Fade all dots to create trails - for (int strip = 0; strip < 40; strip++) { - for (int led = 0; led < 14; led++) { - for (int i = 0; i < 3; i++) { - ledColors[strip][led][i] *= decay; - } +void clearLeds(){ + memset(ledColors,0, NUMBER_OF_SEGMENTS*LEDS_PER_SEGMENTS*3); + strip.clear(); +} + +// Main setup function +void setup() { + Serial.begin(115200); + Serial.println("*** LET'S GOOOOO ***"); + + strip.begin(); + strip.show(); + + // WiFi.mode(WIFI_STA); + WiFi.begin("Nismon-IOT", "Ni$monIOT!!!!!"); + WiFi.config(ip, gateway, subnet); + + // Set Hostname. + String hostname(HOSTNAME); + hostname += String(ESP.getChipId(), HEX); + WiFi.hostname(hostname); + + + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Connection Failed! Rebooting..."); + delay(5000); + ESP.restart(); } - } + Udp.begin(localPort); - for (int i = 0; i < numberOfRipples; i++) { - ripples[i].advance(ledColors); - } + dbg.write("Online"); + // Print hostname. + dbg.println("Hostname: " + hostname); + dbg.print("WiFi connected, IP = "); + dbg.println(WiFi.localIP()); - for (int segment = 0; segment < 40; segment++) { - for (int fromBottom = 0; fromBottom < 14; fromBottom++) { - int strip = ledAssignments[segment][0]; - int led = round(fmap( - fromBottom, - 0, 13, - ledAssignments[segment][2], ledAssignments[segment][1])); - strips[strip].setPixelColor( - led, - ledColors[segment][fromBottom][0], - ledColors[segment][fromBottom][1], - ledColors[segment][fromBottom][2]); - } - } + setupOTA(); + + dbg.println("Ready for WiFi OTA updates"); + dbg.print("IP address: "); + dbg.println(WiFi.localIP()); - for (int i = 0; i < 4; i++) - strips[i].show(); - if (millis() - lastHeartbeat >= autoPulseTimeout) { - // When biometric data is unavailable, visualize at random - if (numberOfAutoPulseTypes && millis() - lastRandomPulse >= randomPulseTime) { - unsigned int baseColor = random(0xFFFF); + setupHTTPServer(); + // Boot color + setFixedColor(14230237); - if (currentAutoPulseType == 255 || (numberOfAutoPulseTypes > 1 && millis() - lastAutoPulseChange >= autoPulseChangeTime)) { - byte possiblePulse = 255; - while (true) { - possiblePulse = random(3); +} - if (possiblePulse == currentAutoPulseType) - continue; +void loop() { + unsigned long benchmark = millis(); + static int currentLed = 0; + static unsigned long lastStep = 0; + - switch (possiblePulse) { - case 0: - if (!randomPulsesEnabled) - continue; - break; + ArduinoOTA.handle(); + server.handleClient(); - case 1: - if (!cubePulsesEnabled) - continue; - break; - case 2: - if (!starburstPulsesEnabled) - continue; - break; + if ( currentMode == 0 ){ + chromanceProcess(); + }else{ + processUDP(); + } - default: - continue; - } + // + for (uint8_t segment = 0; segment < NUMBER_OF_SEGMENTS; segment++) { + for (uint8_t fromBottom = 0; fromBottom < LEDS_PER_SEGMENTS; fromBottom++) { - currentAutoPulseType = possiblePulse; - lastAutoPulseChange = millis(); - break; + uint16_t led = round(fmap(fromBottom,0, (LEDS_PER_SEGMENTS-1),ledAssignments[segment][2], ledAssignments[segment][1])); + strip.setPixelColor(led, ledColors[segment][fromBottom][0], ledColors[segment][fromBottom][1],ledColors[segment][fromBottom][2]); } - } + } + // Update LEDS ! + strip.show(); +} - switch (currentAutoPulseType) { - case 0: { - int node = 0; - bool foundStartingNode = false; - while (!foundStartingNode) { - node = random(25); - foundStartingNode = true; - for (int i = 0; i < numberOfBorderNodes; i++) { - // Don't fire a pulse on one of the outer nodes - it looks boring - if (node == borderNodes[i]) - foundStartingNode = false; - } - - if (node == lastAutoPulseNode) - foundStartingNode = false; - } - lastAutoPulseNode = node; - - for (int i = 0; i < 6; i++) { - if (nodeConnections[node][i] >= 0) { - for (int j = 0; j < numberOfRipples; j++) { - if (ripples[j].state == dead) { - ripples[j].start( - node, - i, -// strip0.ColorHSV(baseColor + (0xFFFF / 6) * i, 255, 255), - strip0.ColorHSV(baseColor, 255, 255), - float(random(100)) / 100.0 * .2 + .5, - 3000, - 1); +void setFixedColor(uint32_t color){ + g_color = color; + uint8_t r = (g_color & 0xff0000 ) >> 16; + uint8_t g = (g_color & 0x00ff00 ) >> 8; + uint8_t b = (g_color & 0x0000ff ) >> 0; + + for (int i = 0; i < NUM_OF_PIXELS; i++) { + strip.setPixelColor(i, r,g,b); + } + // Update LEDS ! + strip.show(); +} + +void processUDP(){ + // buffers for receiving and sending data + static char ReplyBuffer[] = "acknowledged\r\n"; // a string to send back + static char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; //buffer to hold incoming packet, + + // if there's data available, read a packet + int packetSize = Udp.parsePacket(); + if (packetSize) { + dbg.printf("Received packet of size %d from %s:%d\n (to %s:%d, free heap = %d B)\n", + packetSize, + Udp.remoteIP().toString().c_str(), Udp.remotePort(), + Udp.destinationIP().toString().c_str(), Udp.localPort(), + ESP.getFreeHeap()); + + // read the packet into packetBufffer + int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); + + + if (n >= LEDS_PER_SEGMENTS*NUMBER_OF_SEGMENTS*3){ + memcpy(ledColors, packetBuffer, LEDS_PER_SEGMENTS*NUMBER_OF_SEGMENTS*3); + } + else{ + packetBuffer[n] = 0; + switch (packetBuffer[0]){ + + case 's': + renderStarburst(); + break; + case 'c': + renderCube(random(numberOfCubeNodes)); + break; + case 'o': + renderContour(1); + break; + case 't': + test(); break; - } - } - } } - break; - } + } + // dbg.println("Contents:"); + // dbg.println(packetBuffer); - case 1: { - int node = cubeNodes[random(numberOfCubeNodes)]; + // send a reply, to the IP address and port that sent us the packet we received + // Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + // Udp.write(ReplyBuffer); + // Udp.endPacket(); + } +} - while (node == lastAutoPulseNode) - node = cubeNodes[random(numberOfCubeNodes)]; +void chromanceProcess(){ + static unsigned long lastRandomPulse; + + // Fade all dots to create trails + // Doing so by fading all the framebuffer manually + for (int seg = 0; seg < NUMBER_OF_SEGMENTS; seg++) { + for (int led = 0; led < LEDS_PER_SEGMENTS; led++) { + for (int i = 0; i < 3; i++) { + ledColors[seg][led][i] *= decay; + } + } + } - lastAutoPulseNode = node; + // Advance each started ripple + for (int i = 0; i < NUMBER_OF_RIPPLES; i++) { + ripples[i].advance(ledColors); + } - byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + // Fire up a new ripple every 2-6 secondes + if (millis() - lastRandomPulse >= random(2000,6000)) { + // renderTriburst(); + currentAutoPulseType = random(5); + dbg.write("Alive"); + switch (currentAutoPulseType) { + case 0: { + renderContour(1); + break; + } + case 1: { + renderUnstoppableSnake(random(0,NUMBER_OF_NODES)); + break; + } + case 2: { + renderCube(random(numberOfCubeNodes)); + break; + } + case 3: { + renderStarburst(); + break; + } + case 4: { + renderTriburst(); + break; + } - for (int i = 0; i < 6; i++) { - if (nodeConnections[node][i] >= 0) { - for (int j = 0; j < numberOfRipples; j++) { - if (ripples[j].state == dead) { - ripples[j].start( - node, - i, -// strip0.ColorHSV(baseColor + (0xFFFF / 6) * i, 255, 255), - strip0.ColorHSV(baseColor, 255, 255), - .5, - 2000, - behavior); + default: + break; + } + lastRandomPulse = millis(); + } +} - break; - } - } - } - } - break; - } +void renderCube(int node){ - case 2: { - byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + while (node == lastAutoPulseNode) + node = cubeNodes[random(numberOfCubeNodes)]; - lastAutoPulseNode = starburstNode; + lastAutoPulseNode = node; - for (int i = 0; i < 6; i++) { - for (int j = 0; j < numberOfRipples; j++) { - if (ripples[j].state == dead) { - ripples[j].start( - starburstNode, - i, - strip0.ColorHSV(baseColor + (0xFFFF / 6) * i, 255, 255), - .65, - 1500, - behavior); + rippleBehavior behavior = random(2) ? BEHAVIOR_ALWAYS_LEFT : BEHAVIOR_ALWAYS_RIGHT; - break; - } - } - } + for (int i = 0; i < SIDES_PER_NODES; i++) { + if (nodeConnections[node][i] >= 0) { + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + node, + i, + getRandomColor(), + getSpeed(), + 8000, + behavior); break; - } - - default: - break; - } - lastRandomPulse = millis(); + } + } + } } +} + +void renderContour(int node){ - if (simulatedBiometricsEnabled) { - // Simulated heartbeat - if (millis() >= nextSimulatedHeartbeat) { - for (int i = 0; i < 6; i++) { - for (int j = 0; j < numberOfRipples; j++) { - if (ripples[j].state == dead) { - ripples[j].start( - 15, + rippleBehavior behavior; + + for (int i = 0; i < SIDES_PER_NODES; i++) { + if (nodeConnections[node][i] >= 0) { + if ( i < 3){ + behavior = BEHAVIOR_ALWAYS_RIGHT; + } + else{ + behavior = BEHAVIOR_ALWAYS_LEFT; + } + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + node, i, - 0xEE1111, - float(random(100)) / 100.0 * .1 + .4, - 1000, - 0); + getRandomColor(), + getSpeed(), + 8000, + behavior); + break; + } + } + } + } +} - break; +void renderUnstoppableSnake(int startingNode){ + // int node = cubeNodes[random(numberOfCubeNodes)]; + unsigned int randStartingNode = random(5); + + rippleBehavior behavior = BEHAVIOR_ANGRY; + + for (int i = 0; i < SIDES_PER_NODES; i++) { + if (nodeConnections[startingNode][(randStartingNode+i)%5] >= 0) { + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + startingNode, + (randStartingNode+i)%5, + getRandomColor(), + getSpeed(), + 8000, + behavior); + return; } - } } + } + } +} - nextSimulatedHeartbeat = millis() + simulatedHeartbeatBaseTime + random(simulatedHeartbeatVariance); - } +void renderStarburst(){ + // int node = cubeNodes[random(numberOfCubeNodes)]; + unsigned int startingNode = starburstNode; + + rippleBehavior behavior = BEHAVIOR_FEISTY; + uint32_t color = getRandomColor(); + for (int i = 0; i < SIDES_PER_NODES; i++) { + if (nodeConnections[startingNode][i] >= 0) { + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + startingNode, + i, + getRandomColor(), + getSpeed(), + 5000, + behavior); + break; + } + } + } + } +} - // Simulated EDA ripples - if (millis() >= nextSimulatedEda) { - for (int i = 0; i < 10; i++) { - for (int j = 0; j < numberOfRipples; j++) { - if (ripples[j].state == dead) { - byte targetNode = borderNodes[random(numberOfBorderNodes)]; - byte direction = 255; - - while (direction == 255) { - direction = random(6); - if (nodeConnections[targetNode][direction] < 0) - direction = 255; - } - - ripples[j].start( - targetNode, - direction, - 0x1111EE, - float(random(100)) / 100.0 * .5 + 2, - 300, - 2 - ); - - break; +void renderTriburst(){ + // int node = cubeNodes[random(numberOfCubeNodes)]; + unsigned int startingNode = triNodes[random(numberOfTriNodes)];; + + rippleBehavior behavior = BEHAVIOR_ANGRY; + uint8_t rippleCnt = 0; + int i=0; + + while (rippleCnt < 3) { + if (nodeConnections[startingNode][i] >= 0) { + i+=2; // Skip 1 side + i%=6; + rippleCnt ++; + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + startingNode, + i, + getRandomColor(), + getSpeed(), + random(500,2500), + behavior); + break; } - } } + }else{ + i++; + } + } +} + - nextSimulatedEda = millis() + simulatedEdaBaseTime + random(simulatedEdaVariance); +void test(){ +// int node = cubeNodes[random(numberOfCubeNodes)]; + unsigned int startingNode = starburstNode; + static int node = 0; + + rippleBehavior behavior = BEHAVIOR_LAZY; + if (nodeConnections[startingNode][node] >= 0) { + for (int j = 0; j < NUMBER_OF_RIPPLES; j++) { + if (ripples[j].state == STATE_DEAD) { + ripples[j].start( + startingNode, + node, + getRandomColor(), + getSpeed(), + 5000, + behavior); + break; } } } + node++; + node %= 6; +} - // Serial.print("Benchmark: "); - // Serial.println(millis() - benchmark); +uint32_t getRandomColor() +{ +// uint16_t color = secureRandom(765,1530); + return strip.ColorHSV(g_hue, random(200,255), random(100,255)); } + +float getSpeed(){ + return random(500,800)/1000.0; + // return 0.5; +} + +void set_hue(){ + + if (server.args() > 0){ + for (uint8_t i = 0; i < server.args(); i++) { + if (server.argName(i) == "hue"){ + String hue_arg = server.arg(i); + + if (hue_arg.charAt(0) == '#'){ + hue_arg.setCharAt(0,'0'); + } + setFixedColor(strtol(hue_arg.c_str(), 0, 16)); + Serial.println("Setting hue to : "); + Serial.print(strtol(hue_arg.c_str(), 0, 16)); + } + Serial.println("Arg : "); + Serial.print(server.arg(i)); + server.send(200, "text/plain", "Setting hue to " + server.arg(i)); + } + } +} + +void testStrip(){ + // // strip.setPixelColor(currentLed, 0,0,0); + // for (int i = 0; i < NUMBER_OF_SEGMENTS; i++) { + + // strip.setPixelColor(LEDS_PER_SEGMENTS*(i-1), 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+1, 0,0,0); + // stfgnvb nrip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+2, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+3, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+4, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+5, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+6, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+7, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+8, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+9, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+10, 0,0,0); + // strip.setPixelColor((LEDS_PER_SEGMENTS*(i-1))+11, 0,0,0); + + // strip.setPixelColor(LEDS_PER_SEGMENTS*i, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+1, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+2, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+3, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+4, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+5, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+6, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+7, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+8, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+9, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+10, 20,50,50); + // strip.setPixelColor((LEDS_PER_SEGMENTS*i)+11, 20,50,50); + // // currentLed %= (NUMBER_OF_SEGMENTS*LEDS_PER_SEGMENTS); +} \ No newline at end of file diff --git a/chromance-firmware/debugUdp.cpp b/chromance-firmware/debugUdp.cpp new file mode 100644 index 0000000..7bf8812 --- /dev/null +++ b/chromance-firmware/debugUdp.cpp @@ -0,0 +1,73 @@ +#include "debugUdp.h" + +size_t debugUdp::readBytes(uint8_t *buffer, size_t length) { + return read((char *) buffer, length); +} + +size_t debugUdp::write(const char *buffer, size_t size) +{ + size_t ret = 0; + if ( conn_.remotePort() != 0 ){ + conn_.beginPacket(conn_.remoteIP(), conn_.remotePort()); + ret = conn_.write(buffer, size); + conn_.write('\n'); + conn_.endPacket(); + } + return ret; +} +size_t debugUdp::write(const char *str) { + size_t ret = 0; + if ( conn_.remotePort() != 0 ){ + conn_.beginPacket(conn_.remoteIP(), conn_.remotePort()); + ret = conn_.write(str, strlen(str)); + conn_.write('\n'); + conn_.endPacket(); + } + return ret; +} +size_t debugUdp::write(uint8_t c) +{ + size_t ret = 0; + if ( conn_.remotePort() != 0 ){ + conn_.beginPacket(conn_.remoteIP(), conn_.remotePort()); + ret = conn_.write(&c, 1); + conn_.write('\n'); + conn_.endPacket(); + } + return ret; +} +size_t debugUdp::write(const uint8_t *buffer, size_t size) +{ + size_t ret = 0; + if ( conn_.remotePort() != 0 ){ + conn_.beginPacket(conn_.remoteIP(), conn_.remotePort()); + ret = conn_.write(buffer, size); + conn_.write('\n'); + conn_.endPacket(); + } + return ret; +} + +int debugUdp::read(void) +{ + // return -1 when data is unavailable (arduino api) + return conn_.read(); +} +// ::read(buffer, size): same as readBytes without timeout +int debugUdp::read(char* buffer, size_t size) +{ + return conn_.read(buffer, size); +} + +int debugUdp::read(uint8_t* buffer, size_t size) +{ + return conn_.read((unsigned char*)buffer, size); +} + +int debugUdp::available(void){ + return 0; +}; + +int debugUdp::peek(void) { + return 0; +}; \ No newline at end of file diff --git a/chromance-firmware/debugUdp.h b/chromance-firmware/debugUdp.h new file mode 100644 index 0000000..872393b --- /dev/null +++ b/chromance-firmware/debugUdp.h @@ -0,0 +1,36 @@ +#ifndef DEBUG_UDP_H_ +#define DEBUG_UDP_H_ + +#include +#include +#include + +class debugUdp: public Stream { +public: + using Stream::Stream; + using Print::write; // Import other write() methods to support things like write(0) properly + + debugUdp(int port){ + conn_.begin(port); + }; + + size_t write(uint8_t c) override; + size_t write(const uint8_t *buffer, size_t size) override; + size_t write(const char *buffer, size_t size); + size_t write(const char *str); + int read(void) override; + int read(char* buffer, size_t size); + int read(uint8_t* buffer, size_t size) override; + size_t readBytes(uint8_t *buffer, size_t length) override; + int available(void) override; + int peek(void) override; + size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character + String readStringUntil(char terminator); + virtual void flush() { conn_.flush(); } + virtual bool outputCanTimeout () { return true; } + +private: + WiFiUDP conn_; + // int available(void) override; +}; +#endif \ No newline at end of file diff --git a/chromance-firmware/index.h b/chromance-firmware/index.h new file mode 100644 index 0000000..98d7703 --- /dev/null +++ b/chromance-firmware/index.h @@ -0,0 +1,41 @@ +const char MAIN_page[] PROGMEM = R"=====( + + + + + This is a Bootstrap CDN example + + + + + +
+

Leds Controller

+ Click to set Chromance mode Chromance
+ Click to turn UDP packets
+
+
+ + +
+
+ + + + + +)====="; \ No newline at end of file diff --git a/chromance-firmware/mapping.h b/chromance-firmware/mapping.h index c518149..cbc5268 100644 --- a/chromance-firmware/mapping.h +++ b/chromance-firmware/mapping.h @@ -7,152 +7,231 @@ #define MAPPING_H_ // I accidentally noted these down 1-indexed and I'm too tired to adjust them -#define headof(S) ((S - 1) * 14) -#define tailof(S) (headof(S) + 13) +#define tailof(S) ((S * LEDS_PER_SEGMENTS)) +#define headof(S) (tailof(S) + (LEDS_PER_SEGMENTS -1)) + +#define NUMBER_OF_NODES 24 +#define SIDES_PER_NODES 6 + +#define NUMBER_OF_SEGMENTS 38 +#define SIDES_PER_SEGMENT 2 +#define NUM_OF_PIXELS (NUMBER_OF_SEGMENTS*LEDS_PER_SEGMENTS) + +#define LEDS_PER_SEGMENTS 12 + // Beam 0 is at 12:00 and advance clockwise // -1 means nothing connected on that side -int nodeConnections[25][6] = { - {-1, -1, 1, -1, 0, -1}, - {-1, -1, 3, -1, 2, -1}, - {-1, -1, 5, -1, 4, -1}, - {-1, 0, 6, 12, -1, -1}, - {-1, 2, 8, 14, 7, 1}, +// First node is the upper left one, they are numbered from left to right 1 row at the time +// The value is the connected segment number +const int nodeConnections[NUMBER_OF_NODES][SIDES_PER_NODES] = { + {-1, -1, 2, -1, 3, -1}, + {-1, -1, 20, -1, 19, -1}, + {-1, -1, 22, -1, 21, -1}, + {-1, 3, 7, 4, -1, -1}, + {-1, 19, 18, 1, 8, 2}, + {-1, 21, 24, 26, 25, 20}, + {-1, -1, -1, 29, 23, 22}, + {-1, 8, -1, 6, -1, 7}, + {-1, 25, -1, 17, -1, 18}, + {-1, 23, -1, 30, -1, 24}, + {4, -1, 5, -1, -1, -1}, + {1, -1, 0, -1, 9, -1}, + {26, -1, 27, -1, 36, -1}, + {29, -1, -1, -1, 28, -1}, + {6, 9, 15, 10, -1, 5}, + {17, 36, 35, 13, 14, 0}, + {30, 28, -1, 31, 37, 27}, + {-1, 14, -1, 16, -1, 15}, + {-1, 37, -1, 34, -1, 35}, + {10, -1, 11, -1, -1, -1}, + {13, -1, 33, -1, 12, -1}, + {31, -1, -1, -1, 32, -1}, + {16, 12, -1, -1, -1, 11}, + {34, 32, -1, -1, -1, 33} +}; +// int nodeConnections[NUMBER_OF_NODES][SIDES_PER_NODES] = { +// {-1, -1, 3, -1, 4, -1}, +// {-1, -1, 21, -1, 22, -1}, +// {-1, -1, 23, -1, 22, -1}, - {-1, 4, 10, 16, 9, 3}, - {-1, -1, -1, 18, 11, 5}, - {-1, 7, -1, 13, -1, 6}, - {-1, 9, -1, 15, -1, 8}, - {-1, 11, -1, 17, -1, 10}, +// {-1, 4, 8, 5, -1, -1}, +// {-1, 20, 19, 2, 9, 3}, +// {-1, 22, 25, 27, 26, 21}, +// {-1, -1, -1, 30, 24, 23}, + +// {-1, 9, -1, 7, -1, 8}, +// {-1, 26, -1, 18, -1, 19}, +// {-1, 24, -1, 31, -1, 25}, + +// {5, -1, 6, -1, -1, -1}, +// {2, -1, 1, -1, 10, -1}, +// {27, -1, 28, -1, 37, -1}, +// {30, -1, -1, -1, 29, -1}, - {12, -1, 19, -1, -1, -1}, - {14, -1, 21, -1, 20, -1}, - {16, -1, 23, -1, 22, -1}, - {18, -1, -1, -1, 24, -1}, - {13, 20, 25, 29, -1, -1}, +// {7, 10, 16, 11, -1, 6}, +// {18, 37, 36, 14, 15, 1}, +// {31, 29, -1, 32, 38, 28}, + +// {-1, 15, -1, 17, -1, 16}, +// {-1, 38, -1, 35, -1, 36}, - {15, 22, 27, 31, 26, 21}, - {17, 24, -1, 33, 28, 23}, - {-1, 26, -1, 30, -1, 25}, - {-1, 28, -1, 32, -1, 27}, - {29, -1, 34, -1, -1, -1}, +// {11, -1, 12, -1, -1, -1}, +// {14, -1, 34, -1, 13, -1}, +// {32, -1, -1, -1, 33, -1}, - {31, -1, 36, -1, 35, -1}, - {33, -1, -1, -1, 37, -1}, - {30, 35, 38, -1, -1, 34}, - {32, 37, -1, -1, 39, 36}, - {-1, 39, -1, -1, -1, 38} -}; +// {17, 13, -1, -1, -1, 12}, +// {35, 33, -1, -1, -1, 34} +// }; + +// int nodeConnections[NUMBER_OF_NODES][SIDES_PER_NODES] = { +// {-1, -1, 4, -1, 3, -1}, +// {-1, -1, 5, -1, 4, -1}, +// {-1, -1, 6, -1, 5, -1}, + +// {-1, 0, 7, 10, -1, -1}, +// {-1, 1, 8, 11, 7, 0}, +// {-1, 2, 9, 12, 8, 1}, +// {-1, -1, -1, 13, 9, 2}, +// {-1, 4, -1, 14, -1, 3}, +// {-1, 5, -1, 15, -1, 4}, +// {-1, 6, -1, 16, -1, 5}, + +// {3, -1, 14, -1, -1, -1}, +// {4, -1, 15, -1, 14, -1}, +// {5, -1, 16, -1, 15, -1}, +// {6, -1, -1, -1, 16, -1}, + +// {7, 11, 17, 19, -1, -1}, +// {8, 12, 18, 20, 17, 11}, +// {9, 13, -1, 21, 18, 12}, + +// {-1, 15, -1, 22, -1, 14}, +// {-1, 16, -1, 23, -1, 15}, + +// {14, -1, 22, -1, -1, -1}, +// {15, -1, 23, -1, 22, -1}, +// {16, -1, -1, -1, 23, -1}, + +// {17, 20, -1, -1, -1, 19}, +// {18, 21, -1, -1, -1, 20} +// }; +/* + x x x + ╱ ╲ ╱ ╲ ╱ ╲ + x x x x + │╲ ╱ ╲ ╱ ╲ ╱│ + │ x x x │ + x │ x │ x │ x + ╲│╱ ╲│╱ ╲│╱ + x x x + │╲ ╱│╲ ╱│ + │ x │ x │ + x │ x │ x + ╲x╱ ╲x╱ +*/ // First member: Node closer to ceiling // Second: Node closer to floor -int segmentConnections[40][2] = { - {0, 3}, +const int segmentConnections[NUMBER_OF_SEGMENTS][SIDES_PER_SEGMENT] = { + {11, 15}, + {4, 11}, {0, 4}, + {0, 3}, + {3, 10}, + {10, 14}, + {7, 14}, + {3, 7}, + {4, 7}, + {11, 14}, + {14, 19}, + {19, 22}, + {20, 22}, + {15, 20}, + {15, 17}, + {14, 17}, + {17, 22}, + {8, 15}, + {4, 8}, {1, 4}, {1, 5}, {2, 5}, {2, 6}, - {3, 7}, - {4, 7}, - {4, 8}, - {5, 8}, - {5, 9}, {6, 9}, // ayy - {3, 10}, - {7, 14}, - {4, 11}, - {8, 15}, + {5, 9}, + {5, 8}, {5, 12}, - {9, 16}, - {6, 13}, - {10, 14}, - {11, 14}, - {11, 15}, - {12, 15}, {12, 16}, {13, 16}, - {14, 17}, - {15, 17}, - {15, 18}, - {16, 18}, - {14, 19}, - {17, 22}, - {15, 20}, - {18, 23}, + {6, 13}, + {9, 16}, {16, 21}, - {19, 22}, - {20, 22}, - {20, 23}, {21, 23}, - {22, 24}, - {23, 24} + {20, 23}, + {18, 23}, + {15, 18}, + {12, 15}, + {16, 18} }; // First member: Strip number // Second: LED index closer to ceiling // Third: LED index closer to floor -int ledAssignments[40][3] = { - {2, headof(3), tailof(3)}, - {2, tailof(2), headof(2)}, - {1, headof(10), tailof(10)}, - {1, tailof(9), headof(9)}, - {1, headof(4), tailof(4)}, - {1, tailof(3), headof(3)}, - - {2, tailof(6), headof(6)}, - {3, tailof(11), headof(11)}, - {1, headof(11), tailof(11)}, - {1, tailof(8), headof(8)}, - {1, headof(12), tailof(12)}, - {0, tailof(11), headof(11)}, - - {2, headof(4), tailof(4)}, - {3, tailof(10), headof(10)}, - {2, tailof(1), headof(1)}, - {1, tailof(7), headof(7)}, - {1, headof(5), tailof(5)}, - {0, tailof(10), headof(10)}, - {1, tailof(2), headof(2)}, - - {2, headof(5), tailof(5)}, - {3, tailof(4), headof(4)}, - {3, headof(5), tailof(5)}, - {0, headof(5), tailof(5)}, +const int ledAssignments[NUMBER_OF_SEGMENTS][3] = { + {0, headof(0), tailof(0)}, + {0, headof(1), tailof(1)}, + {0, headof(2), tailof(2)}, + {0, tailof(3), headof(3)}, {0, tailof(4), headof(4)}, - {1, tailof(1), headof(1)}, - - {3, tailof(9), headof(9)}, + {0, tailof(5), headof(5)}, {0, headof(6), tailof(6)}, - {1, tailof(6), headof(6)}, - {0, tailof(9), headof(9)}, - - {3, tailof(3), headof(3)}, - {3, tailof(8), headof(8)}, - {3, headof(6), tailof(6)}, - {0, tailof(8), headof(8)}, - {0, tailof(3), headof(3)}, - - {3, tailof(2), headof(2)}, - {3, headof(7), tailof(7)}, {0, headof(7), tailof(7)}, - {0, tailof(2), headof(2)}, - - {3, tailof(1), headof(1)}, - {0, tailof(1), headof(1)} + {0, headof(8), tailof(8)}, + {0, tailof(9), headof(9)}, + {0, tailof(10), headof(10)}, + {0, tailof(11), headof(11)}, + {0, headof(12), tailof(12)}, + {0, headof(13), tailof(13)}, + {0, tailof(14), headof(14)}, + {0, headof(15), tailof(15)}, + {0, tailof(16), headof(16)}, + {0, headof(17), tailof(17)}, + {0, headof(18), tailof(18)}, + {0, headof(19), tailof(19)}, + {0, tailof(20), headof(20)}, + {0, headof(21), tailof(21)}, + {0, tailof(22), headof(22)}, + {0, tailof(23), headof(23)}, + {0, headof(24), tailof(24)}, + {0, tailof(25), headof(25)}, + {0, tailof(26), headof(26)}, + {0, tailof(27), headof(27)}, + {0, headof(28), tailof(28)}, + {0, headof(29), tailof(29)}, + {0, tailof(30), headof(30)}, + {0, tailof(31), headof(31)}, + {0, tailof(32), headof(32)}, + {0, headof(33), tailof(33)}, + {0, headof(34), tailof(34)}, + {0, headof(35), tailof(35)}, + {0, headof(36), tailof(36)}, + {0, tailof(37), headof(37)} }; // Border nodes are on the very edge of the network. // Ripples fired here don't look very impressive. -int numberOfBorderNodes = 10; -int borderNodes[] = {0, 1, 2, 3, 6, 10, 13, 19, 21, 24}; +const int numberOfBorderNodes = 13; +const int borderNodes[] = {0, 1, 2, 3, 6, 10, 13, 14, 16, 19, 21 , 22 , 23}; // Cube nodes link three equiangular segments // Firing ripples that always turn in one direction will draw a cube -int numberOfCubeNodes = 8; -int cubeNodes[] = {7, 8, 9, 11, 12, 17, 18}; +const int numberOfCubeNodes = 7; +const int cubeNodes[] = {7, 8, 9, 11, 12, 17, 18}; + +const int numberOfTriNodes = 13; +const int triNodes[] = {4,5, 7, 8, 9, 11, 12, 14,15,16, 17, 18, 20}; // Firing ripples that always turn in one direction will draw a starburst -int starburstNode = 15; +const int starburstNode = 15; #endif diff --git a/chromance-firmware/ripple.cpp b/chromance-firmware/ripple.cpp new file mode 100644 index 0000000..e795635 --- /dev/null +++ b/chromance-firmware/ripple.cpp @@ -0,0 +1,425 @@ +#include "ripple.h" + + +float fmap(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +Ripple::Ripple(int id) : rippleId(id) { + Serial.print("Instanced ripple #"); + Serial.println(rippleId); +} + +void Ripple::start(byte n, byte d, unsigned long c, float s, unsigned long l, rippleBehavior b) { + color = c; + speed = s; + lifespan = l; + behavior = b; + + birthday = millis(); + pressure = 0; + state = STATE_WITHIN_NODE; + + position[0] = n; + position[1] = d; + + justStarted = true; + + Serial.print("Ripple "); + Serial.print(rippleId); + Serial.print(" starting at node "); + Serial.print(position[0]); + Serial.print(" direction "); + Serial.println(position[1]); +} + +void Ripple::renderLed(byte ledColors[NUMBER_OF_SEGMENTS][LEDS_PER_SEGMENTS][3], unsigned long age) { + int strip = ledAssignments[position[0]][0]; + int led = ledAssignments[position[0]][2] + position[1]; + int red = ledColors[position[0]][position[1]][0]; + int green = ledColors[position[0]][position[1]][1]; + int blue = ledColors[position[0]][position[1]][2]; + + ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red))); + ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green))); + ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue))); +} + +void Ripple::advance(byte ledColors[NUMBER_OF_SEGMENTS][LEDS_PER_SEGMENTS][3]) { + + unsigned long age = millis() - birthday; + + if (state == STATE_DEAD) { + return; + } + + pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages + // TODO: Motion of ripple is severely affected by loop speed. Make it time invariant + + if (pressure < 1 && (state == STATE_TRAVEL_UP || state == STATE_TRAVEL_DOWN)) { + // Ripple is visible but hasn't moved - render it to avoid flickering + renderLed(ledColors, age); + } + + while (pressure >= 1) { +#ifdef DEBUG_ADVANCEMENT + Serial.print("---- Ripple "); + Serial.print(rippleId); + Serial.println(" advancing:"); +#endif + + switch (state) { + case STATE_WITHIN_NODE: { + if (justStarted) { + justStarted = false; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Picking direction out of node "); + Serial.print(position[0]); + Serial.print(" with agr. "); + Serial.println(behavior); +#endif + + int newDirection = -1; + + int sharpLeft = (position[1] + 1) % SIDES_PER_NODES; + int wideLeft = (position[1] + 2) % SIDES_PER_NODES; + int forward = (position[1] + 3) % SIDES_PER_NODES; + int wideRight = (position[1] + 4) % SIDES_PER_NODES; + int sharpRight = (position[1] + 5) % SIDES_PER_NODES; + + if (behavior <= BEHAVIOR_ANGRY) { // Semi-random aggressive turn mode + // The more aggressive a ripple, the tighter turns it wants to make. + // If there aren't any segments it can turn to, we need to adjust its behavior. + byte anger = behavior; + int forwardConnection = nodeConnections[position[0]][forward]; + + while (newDirection < 0) { + if (anger == BEHAVIOR_COUCH_POTATO) { + + // We can't go straight ahead - we need to take a rest +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Never continue ... too lazy - stopping"); +#endif + // Die now + age = lifespan; + break; + } + + if (anger == BEHAVIOR_LAZY) { + + if (forwardConnection < 0) { + // We can't go straight ahead - we need to take a rest + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't go straight - stopping"); + #endif + // Die now + age = lifespan; + break; + //break; + } + else { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Going forward"); + #endif + newDirection = forward; + } + } + + if (anger == BEHAVIOR_WEAK) { + + if (forwardConnection < 0) { + // We can't go straight ahead - we need to take a more aggressive angle + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't go straight - picking more agr. path"); + #endif + anger += 1; + } + else { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Going forward"); + #endif + newDirection = forward; + } + } + + if (anger == BEHAVIOR_FEISTY) { + int leftConnection = nodeConnections[position[0]][wideLeft]; + int rightConnection = nodeConnections[position[0]][wideRight]; + + if (leftConnection >= 0 && rightConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning left or right at random"); + #endif + newDirection = random(2) ? wideLeft : wideRight; + } + else if (leftConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn left"); + #endif + newDirection = wideLeft; + } + else if (rightConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn right"); + #endif + newDirection = wideRight; + } + else { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't make wide turn - picking more agr. path"); + #endif + anger+=1; // Can't take shallow turn - must become more aggressive + } + } + + if (anger == BEHAVIOR_ANGRY) { + int leftConnection = nodeConnections[position[0]][sharpLeft]; + int rightConnection = nodeConnections[position[0]][sharpRight]; + + if (leftConnection >= 0 && rightConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning left or right at random"); + #endif + newDirection = random(2) ? sharpLeft : sharpRight; + } + else if (leftConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn left"); + #endif + newDirection = sharpLeft; + } + else if (rightConnection >= 0) { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn right"); + #endif + newDirection = sharpRight; + } + else { + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't make tight turn - picking less agr. path"); + #endif + anger--; // Can't take tight turn - must become less aggressive + } + } + + // Note that this can't handle some circumstances, + // like a node with segments in nothing but the 0 and 3 positions. + // Good thing we don't have any of those! + + } // End while loop + } + else if (behavior == BEHAVIOR_ALWAYS_RIGHT) { + for (int i = 1; i < SIDES_PER_NODES; i++) { + int possibleDirection = (position[1] + i) % SIDES_PER_NODES; + + if (nodeConnections[position[0]][possibleDirection] >= 0) { + newDirection = possibleDirection; + break; + } + } + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning as rightward as possible"); + #endif + } + else if (behavior == BEHAVIOR_ALWAYS_LEFT) { + for (int i = 5; i >= 1; i--) { + int possibleDirection = (position[1] + i) % SIDES_PER_NODES; + + if (nodeConnections[position[0]][possibleDirection] >= 0) { + newDirection = possibleDirection; + break; + } + } + #ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning as leftward as possible"); + #endif + } + else if (behavior == BEHAVIOR_EXPLODING) { + for (int i = 5; i >= 1; i--) { + int possibleDirection = (position[1] + i) % SIDES_PER_NODES; + + if (nodeConnections[position[0]][possibleDirection] >= 0 && (possibleDirection != position[0])) { + newDirection = possibleDirection; + // start + } + } + +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Exploding !"); +#endif + } + +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Leaving node "); + Serial.print(position[0]); + Serial.print(" in direction "); + Serial.println(newDirection); +#endif + if (newDirection >= 0){ + position[1] = newDirection; + } + }// End else from 'if (justStarted) {' + + position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on + if ( position[0] > 37 ) + { + Serial.print("Uhoh, position[0] out of bound at line 296 :"); + Serial.println(position[0]); + } +#ifdef DEBUG_ADVANCEMENT + Serial.print(" and entering segment "); + Serial.println(position[0]); +#endif + + if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node +#ifdef DEBUG_ADVANCEMENT + Serial.println(" (starting at bottom)"); +#endif + state = STATE_TRAVEL_UP; + position[1] = 0; // Starting at bottom of segment + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" (starting at top)"); +#endif + state = STATE_TRAVEL_DOWN; + position[1] = LEDS_PER_SEGMENTS - 1; // Starting at top of LED-strip + } + break; + } + + case STATE_TRAVEL_UP: { + position[1]++; + + if (position[1] >= LEDS_PER_SEGMENTS) { + // We've reached the top! +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Reached top of seg. "); + Serial.println(position[0]); +#endif + // Enter the new node. + int segment = position[0]; + position[0] = segmentConnections[position[0]][0]; + if ( position[0] > NUMBER_OF_SEGMENTS ) + { + Serial.print("Segment out of bound :"); + Serial.print(position[0]); + Serial.println(""); + } + for (int i = 0; i < SIDES_PER_NODES; i++) { + // Figure out from which direction the ripple is entering the node. + // Allows us to exit in an appropriately aggressive direction. + int incomingConnection = nodeConnections[position[0]][i]; + if (incomingConnection == segment) + position[1] = i; + } +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Entering node "); + Serial.print(position[0]); + Serial.print(" from direction "); + Serial.println(position[1]); +#endif + state = STATE_WITHIN_NODE; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Moved up to seg. "); + Serial.print(position[0]); + Serial.print(" LED "); + Serial.println(position[1]); +#endif + } + break; + } + + case STATE_TRAVEL_DOWN: { + position[1]--; + if (position[1] < 0) { + // We've reached the bottom! +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Reached bottom of seg. "); + Serial.println(position[0]); +#endif + // Enter the new node. + int segment = position[0]; + position[0] = segmentConnections[position[0]][1]; + { + Serial.print("Segment out of bound :"); + Serial.print(position[0]); + Serial.println(""); + } + for (int i = 0; i < 6; i++) { + // Figure out from which direction the ripple is entering the node. + // Allows us to exit in an appropriately aggressive direction. + int incomingConnection = nodeConnections[position[0]][i]; + if (incomingConnection == segment) + position[1] = i; + } +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Entering node "); + Serial.print(position[0]); + Serial.print(" from direction "); + Serial.println(position[1]); +#endif + state = STATE_WITHIN_NODE; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Moved down to seg. "); + Serial.print(position[0]); + Serial.print(" LED "); + Serial.println(position[1]); +#endif + } + break; + } + + default: + break; + } + + pressure -= 1; + + if (state == STATE_TRAVEL_UP || state == STATE_TRAVEL_DOWN) { + // Ripple is visible - render it + renderLed(ledColors, age); + } + } + +#ifdef DEBUG_AGE + Serial.print(" Age is now "); + Serial.print(age); + Serial.print('/'); + Serial.println(lifespan); +#endif + + if (lifespan && age >= lifespan) { + // We STATE_DEAD +#ifdef DEBUG_AGE + Serial.println(" Lifespan is up! Ripple is STATE_DEAD."); +#endif + state = STATE_DEAD; + position[0] = position[1] = pressure = age = 0; + } + +#ifdef DEBUG_RENDERING + Serial.print("Rendering ripple position ("); + Serial.print(position[0]); + Serial.print(','); + Serial.print(position[1]); + Serial.print(") at Strip "); + Serial.print(strip); + Serial.print(", LED "); + Serial.print(led); + Serial.print(", color 0x"); + for (int i = 0; i < 3; i++) { + if (ledColors[position[0]][position[1]][i] <= 0x0F){ + Serial.print('0'); + } + Serial.print(ledColors[position[0]][position[1]][i], HEX); + } + Serial.println(); +#endif +} \ No newline at end of file diff --git a/chromance-firmware/ripple.h b/chromance-firmware/ripple.h index fd76184..3e3c845 100644 --- a/chromance-firmware/ripple.h +++ b/chromance-firmware/ripple.h @@ -7,418 +7,74 @@ #define RIPPLE_H_ // WARNING: These slow things down enough to affect performance. Don't turn on unless you need them! -//#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement -//#define DEBUG_RENDERING // Print debug messages about translating logical to actual position - -#include +#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement +// #define DEBUG_RENDERING // Print debug messages about translating logical to actual position + +// #include +#include +#include +#include +#include +#include +#include +#include #include "mapping.h" enum rippleState { - dead, - withinNode, // Ripple isn't drawn as it passes through a node to keep the speed consistent - travelingUpwards, - travelingDownwards + STATE_DEAD, // Ripple is to be deleted and should not lit up + STATE_WITHIN_NODE, // Ripple isn't drawn as it passes through a node to keep the speed consistent + STATE_TRAVEL_UP, // Ripple is moving upward + STATE_TRAVEL_DOWN // Ripple is moving downward }; enum rippleBehavior { - weaksauce = 0, - feisty = 1, - angry = 2, - alwaysTurnsRight = 3, - alwaysTurnsLeft = 4 + BEHAVIOR_COUCH_POTATO, // Stop at next node + BEHAVIOR_LAZY, // Only go straight + BEHAVIOR_WEAK, // Go straight if possible + BEHAVIOR_FEISTY, + BEHAVIOR_ANGRY, + BEHAVIOR_ALWAYS_RIGHT, + BEHAVIOR_ALWAYS_LEFT, + BEHAVIOR_EXPLODING // Coming soon }; -float fmap(float x, float in_min, float in_max, float out_min, float out_max) { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} +float fmap(float x, float in_min, float in_max, float out_min, float out_max); class Ripple { - public: - Ripple(int id) : rippleId(id) { - Serial.print("Instanced ripple #"); - Serial.println(rippleId); - } - rippleState state = dead; - unsigned long color; +public: + Ripple(int id); + void start(byte n, byte d, unsigned long c, float s, unsigned long l, rippleBehavior b); + void advance(byte ledColors[NUMBER_OF_SEGMENTS][LEDS_PER_SEGMENTS][3]); + + rippleState state = STATE_DEAD; + unsigned long color; /* - If within a node: 0 is node, 1 is direction - If traveling, 0 is segment, 1 is LED position from bottom + If within a node: 0 is node, 1 is direction + If traveling, 0 is segment, 1 is LED position from bottom */ int position[2]; - // Place the Ripple in a node - void start(byte n, byte d, unsigned long c, float s, unsigned long l, byte b) { - color = c; - speed = s; - lifespan = l; - behavior = b; - - birthday = millis(); - pressure = 0; - state = withinNode; - - position[0] = n; - position[1] = d; +private: - justStarted = true; + void renderLed(byte ledColors[NUMBER_OF_SEGMENTS][LEDS_PER_SEGMENTS][3], unsigned long age); - Serial.print("Ripple "); - Serial.print(rippleId); - Serial.print(" starting at node "); - Serial.print(position[0]); - Serial.print(" direction "); - Serial.println(position[1]); - } - void advance(byte ledColors[40][14][3]) { - unsigned long age = millis() - birthday; - - if (state == dead) - return; - - pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages - // TODO: Motion of ripple is severely affected by loop speed. Make it time invariant - - if (pressure < 1 && (state == travelingUpwards || state == travelingDownwards)) { - // Ripple is visible but hasn't moved - render it to avoid flickering - renderLed(ledColors, age); - } - - while (pressure >= 1) { -#ifdef DEBUG_ADVANCEMENT - Serial.print("Ripple "); - Serial.print(rippleId); - Serial.println(" advancing:"); -#endif - - switch (state) { - case withinNode: { - if (justStarted) { - justStarted = false; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Picking direction out of node "); - Serial.print(position[0]); - Serial.print(" with agr. "); - Serial.println(behavior); -#endif - - int newDirection = -1; - - int sharpLeft = (position[1] + 1) % 6; - int wideLeft = (position[1] + 2) % 6; - int forward = (position[1] + 3) % 6; - int wideRight = (position[1] + 4) % 6; - int sharpRight = (position[1] + 5) % 6; - - if (behavior <= 2) { // Semi-random aggressive turn mode - // The more aggressive a ripple, the tighter turns it wants to make. - // If there aren't any segments it can turn to, we need to adjust its behavior. - byte anger = behavior; - - while (newDirection < 0) { - if (anger == 0) { - int forwardConnection = nodeConnections[position[0]][forward]; - - if (forwardConnection < 0) { - // We can't go straight ahead - we need to take a more aggressive angle -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can't go straight - picking more agr. path"); -#endif - anger++; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Going forward"); -#endif - newDirection = forward; - } - } - if (anger == 1) { - int leftConnection = nodeConnections[position[0]][wideLeft]; - int rightConnection = nodeConnections[position[0]][wideRight]; - - if (leftConnection >= 0 && rightConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Turning left or right at random"); -#endif - newDirection = random(2) ? wideLeft : wideRight; - } - else if (leftConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can only turn left"); -#endif - newDirection = wideLeft; - } - else if (rightConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can only turn right"); -#endif - newDirection = wideRight; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can't make wide turn - picking more agr. path"); -#endif - anger++; // Can't take shallow turn - must become more aggressive - } - } - - if (anger == 2) { - int leftConnection = nodeConnections[position[0]][sharpLeft]; - int rightConnection = nodeConnections[position[0]][sharpRight]; - - if (leftConnection >= 0 && rightConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Turning left or right at random"); -#endif - newDirection = random(2) ? sharpLeft : sharpRight; - } - else if (leftConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can only turn left"); -#endif - newDirection = sharpLeft; - } - else if (rightConnection >= 0) { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can only turn right"); -#endif - newDirection = sharpRight; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Can't make tight turn - picking less agr. path"); -#endif - anger--; // Can't take tight turn - must become less aggressive - } - } - - // Note that this can't handle some circumstances, - // like a node with segments in nothing but the 0 and 3 positions. - // Good thing we don't have any of those! - } - } - else if (behavior == alwaysTurnsRight) { - for (int i = 1; i < 6; i++) { - int possibleDirection = (position[1] + i) % 6; - - if (nodeConnections[position[0]][possibleDirection] >= 0) { - newDirection = possibleDirection; - break; - } - } - -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Turning as rightward as possible"); -#endif - } - else if (behavior == alwaysTurnsLeft) { - for (int i = 5; i >= 1; i--) { - int possibleDirection = (position[1] + i) % 6; - - if (nodeConnections[position[0]][possibleDirection] >= 0) { - newDirection = possibleDirection; - break; - } - } - -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Turning as leftward as possible"); -#endif - } - -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Leaving node "); - Serial.print(position[0]); - Serial.print(" in direction "); - Serial.println(newDirection); -#endif - - position[1] = newDirection; - } - - position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on - -#ifdef DEBUG_ADVANCEMENT - Serial.print(" and entering segment "); - Serial.println(position[0]); -#endif - - if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node -#ifdef DEBUG_ADVANCEMENT - Serial.println(" (starting at bottom)"); -#endif - state = travelingUpwards; - position[1] = 0; // Starting at bottom of segment - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.println(" (starting at top)"); -#endif - state = travelingDownwards; - position[1] = 13; // Starting at top of 14-LED-long strip - } - break; - } - - case travelingUpwards: { - position[1]++; - - if (position[1] >= 14) { - // We've reached the top! -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Reached top of seg. "); - Serial.println(position[0]); -#endif - // Enter the new node. - int segment = position[0]; - position[0] = segmentConnections[position[0]][0]; - for (int i = 0; i < 6; i++) { - // Figure out from which direction the ripple is entering the node. - // Allows us to exit in an appropriately aggressive direction. - int incomingConnection = nodeConnections[position[0]][i]; - if (incomingConnection == segment) - position[1] = i; - } -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Entering node "); - Serial.print(position[0]); - Serial.print(" from direction "); - Serial.println(position[1]); -#endif - state = withinNode; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Moved up to seg. "); - Serial.print(position[0]); - Serial.print(" LED "); - Serial.println(position[1]); -#endif - } - break; - } - - case travelingDownwards: { - position[1]--; - if (position[1] < 0) { - // We've reached the bottom! -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Reached bottom of seg. "); - Serial.println(position[0]); -#endif - // Enter the new node. - int segment = position[0]; - position[0] = segmentConnections[position[0]][1]; - for (int i = 0; i < 6; i++) { - // Figure out from which direction the ripple is entering the node. - // Allows us to exit in an appropriately aggressive direction. - int incomingConnection = nodeConnections[position[0]][i]; - if (incomingConnection == segment) - position[1] = i; - } -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Entering node "); - Serial.print(position[0]); - Serial.print(" from direction "); - Serial.println(position[1]); -#endif - state = withinNode; - } - else { -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Moved down to seg. "); - Serial.print(position[0]); - Serial.print(" LED "); - Serial.println(position[1]); -#endif - } - break; - } - - default: - break; - } - - pressure -= 1; - - if (state == travelingUpwards || state == travelingDownwards) { - // Ripple is visible - render it - renderLed(ledColors, age); - } - } - -#ifdef DEBUG_ADVANCEMENT - Serial.print(" Age is now "); - Serial.print(age); - Serial.print('/'); - Serial.println(lifespan); -#endif - - if (lifespan && age >= lifespan) { - // We dead -#ifdef DEBUG_ADVANCEMENT - Serial.println(" Lifespan is up! Ripple is dead."); -#endif - state = dead; - position[0] = position[1] = pressure = age = 0; - } - } - - private: float speed; // Each loop, ripples move this many LED's. unsigned long lifespan; // The ripple stops after this many milliseconds - /* - 0: Always goes straight ahead if possible - 1: Can take 60-degree turns - 2: Can take 120-degree turns - */ - byte behavior; - + 0: Always goes straight ahead if possible + 1: Can take 60-degree turns + 2: Can take 120-degree turns + */ + rippleBehavior behavior; bool justStarted = false; - float pressure; // When Pressure reaches 1, ripple will move unsigned long birthday; // Used to track age of ripple - static byte rippleCount; // Used to give them unique ID's byte rippleId; // Used to identify this ripple in debug output - - void renderLed(byte ledColors[40][14][3], unsigned long age) { - int strip = ledAssignments[position[0]][0]; - int led = ledAssignments[position[0]][2] + position[1]; - - int red = ledColors[position[0]][position[1]][0]; - int green = ledColors[position[0]][position[1]][1]; - int blue = ledColors[position[0]][position[1]][2]; - - ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red))); - ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green))); - ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue))); - -#ifdef DEBUG_RENDERING - Serial.print("Rendering ripple position ("); - Serial.print(position[0]); - Serial.print(','); - Serial.print(position[1]); - Serial.print(") at Strip "); - Serial.print(strip); - Serial.print(", LED "); - Serial.print(led); - Serial.print(", color 0x"); - for (int i = 0; i < 3; i++) { - if (ledColors[position[0]][position[1]][i] <= 0x0F) - Serial.print('0'); - Serial.print(ledColors[position[0]][position[1]][i], HEX); - } - Serial.println(); -#endif - } }; #endif diff --git a/testScripts/requirements.txt b/testScripts/requirements.txt new file mode 100644 index 0000000..9fbcd6d --- /dev/null +++ b/testScripts/requirements.txt @@ -0,0 +1,4 @@ +matplotlib==3.3.4 +numpy==1.19.5 +pyaudio==0.2.11 +scipy==1.6.0 diff --git a/testScripts/sendUdpTest.py b/testScripts/sendUdpTest.py new file mode 100644 index 0000000..631b3d4 --- /dev/null +++ b/testScripts/sendUdpTest.py @@ -0,0 +1,48 @@ +import socket +import time +import random + +UDP_IP = "192.168.5.50" +UDP_PORT = 8888 + +fd = open("out.bin", "wb") + +b = bytearray() + +sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP + +# for j in range(0, 457): +buff = bytearray(457*3) + +def clear(): + buff.clear() + for i in range(0, 457): + buff.append(0) # R + buff.append(0) # G + buff.append(0) # B + +def setSegment(idx, r , g , b): + global buff + segmentIdx = idx*36 + for i in range(0, random.randint(0,12)): + buff[segmentIdx + (i*3)] = r # R + buff[segmentIdx + (i*3) + 1] = g # G + buff[segmentIdx + (i*3) + 2] = b # B + +for i in range(0, 2000): + clear() + setSegment(4, 10, 40, 0) + setSegment(6, 10, 40, 0) + setSegment(1, 10, 40, 0) + setSegment(17, 10, 40, 0) + setSegment(26, 10, 40, 0) + setSegment(30, 10, 40, 0) + setSegment(29, 10, 40, 0) + sock.sendto(buff, (UDP_IP, UDP_PORT)) + time.sleep(0.02) + print(i) + # print(buff.__len__()) +# fd.write(b) +# fd.flush() +# fd.close() \ No newline at end of file diff --git a/testScripts/spectrumTest.py b/testScripts/spectrumTest.py new file mode 100644 index 0000000..d87113e --- /dev/null +++ b/testScripts/spectrumTest.py @@ -0,0 +1,107 @@ + +""" +Notebook for streaming data from a microphone in realtime + +audio is captured using pyaudio +then converted from binary data to ints using struct +then displayed using matplotlib + +if you don't have pyaudio, then run + +>>> pip install pyaudio + +note: with 2048 samples per chunk, I'm getting 20FPS +""" + +import pyaudio +import os +import struct +import numpy as np +import matplotlib.pyplot as plt +import time +from tkinter import TclError +from scipy.fftpack import fft +# use this backend to display in separate Tk window +# matplotlib tk + +# constants +CHUNK = 8196 # samples per frame +FORMAT = pyaudio.paInt16 # audio format (bytes per sample?) +CHANNELS = 1 # single channel for microphone +RATE = 44100 # samples per second + +# create matplotlib figure and axes +fig, (ax1, ax2) = plt.subplots(2, figsize=(15, 7)) + +# pyaudio class instance +p = pyaudio.PyAudio() + +SPEAKERS = p.get_default_output_device_info() + +stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK + ) #The part I have modified + +# variable for plotting +x = np.arange(0, 2 * CHUNK, 2) # samples (waveform) +xf = np.linspace(0, RATE, CHUNK) # frequencies (spectrum) + + +# create semilogx line for spectrum +line_fft, = ax2.semilogx(xf, np.random.rand(CHUNK), '-', lw=2) + +# format spectrum axespi +ax2.set_xlim(20, RATE / 2) + +print('stream started') + +# for measuring frame rate +frame_count = 0 +start_time = time.time() +plt.show() +while True: + + # binary data + data = stream.read(CHUNK) + + # convert data to integers, make np array, then offset it by 127 + data_int = struct.unpack(str(2 * CHUNK) + 'B', data) + + # create np array and offset by 128 + data_np = np.array(data_int, dtype='b')[::2] + 128 + + + # line.set_ydata(data_np) + + # compute FFT and update line + # data = np.fft.fft(data_int) + # data_fft = np.fft.fftfreq(8, 128/CHUNK) + yf = fft(data_int) + line_fft.set_ydata(np.abs(yf[0:CHUNK]) / (128 * CHUNK)) + + + # max = 0; + # maxIdx = 0; + # tmpArr = np.abs(yf[0:CHUNK]) / (128 * CHUNK) + # for i in range(0,tmpArr.size): + # tmp = tmpArr[i] + # if ( tmp > max): + # max = tmp; + # maxIdx = i; + # print(data[30]) + # update figure canvas + try: + fig.canvas.draw() + fig.canvas.flush_events() + frame_count += 1 + + except TclError: + # calculate average frame rate + frame_rate = frame_count / (time.time() - start_time) + + print('stream stopped') + print('average frame rate = {:.0f} FPS'.format(frame_rate)) + break \ No newline at end of file diff --git a/testScripts/spectrumTestUDP.py b/testScripts/spectrumTestUDP.py new file mode 100644 index 0000000..3cbe77d --- /dev/null +++ b/testScripts/spectrumTestUDP.py @@ -0,0 +1,157 @@ + +""" +Notebook for streaming data from a microphone in realtime + +audio is captured using pyaudio +then converted from binary data to ints using struct +then displayed using matplotlib + +if you don't have pyaudio, then run + +>>> pip install pyaudio + +note: with 2048 samples per chunk, I'm getting 20FPS +""" + +import pyaudio +import os +import struct +import numpy as np +import matplotlib.pyplot as plt +import time +from tkinter import TclError +from scipy.fftpack import fft +import socket +import time +import random + +UDP_IP = "192.168.5.50" +UDP_PORT = 8888 + +fd = open("out.bin", "wb") + +b = bytearray() + +sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP + +# for j in range(0, 457): +buff = bytearray(457*3) + +# use this backend to display in separate Tk window +# matplotlib tk + +def clear(): + buff.clear() + for i in range(0, 457): + buff.append(0) # R + buff.append(0) # G + buff.append(0) # B + +def setSegment(idx, r , g , b): + global buff + segmentIdx = idx*36 + for i in range(0, 12): + buff[segmentIdx + (i*3)] = r # R + buff[segmentIdx + (i*3) + 1] = g # G + buff[segmentIdx + (i*3) + 2] = b # B + +# constants +CHUNK = 4096 *2 # samples per frame +FORMAT = pyaudio.paInt16 # audio format (bytes per sample?) +CHANNELS = 1 # single channel for microphone +RATE = 48000 # samples per second + +# create matplotlib figure and axes +fig, (ax1, ax2) = plt.subplots(2, figsize=(15, 7)) + +# pyaudio class instance +p = pyaudio.PyAudio() + +# stream object to get data from microphone +stream = p.open( + format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK +) + +# variable for plotting +x = np.arange(0, 2 * CHUNK, 2) # samples (waveform) +xf = np.linspace(0, RATE, CHUNK) # frequencies (spectrum) + +# create a line object with random data +line, = ax1.plot(x, np.random.rand(CHUNK), '-', lw=2) + +# create semilogx line for spectrum +line_fft, = ax2.semilogx(xf, np.random.rand(CHUNK), '-', lw=2) + +# format waveform axes +ax1.set_title('AUDIO WAVEFORM') +ax1.set_xlabel('samples') +ax1.set_ylabel('volume') +ax1.set_ylim(0, 255) +ax1.set_xlim(0, 2 * CHUNK) +plt.setp(ax1, xticks=[0, CHUNK, 2 * CHUNK], yticks=[0, 128, 255]) + +# format spectrum axes +ax2.set_xlim(20, RATE / 2) + +print('stream started') + +# for measuring frame rate +frame_count = 0 +start_time = time.time() +# plt.show() +while True: + + # binary data + data = stream.read(CHUNK) + + # convert data to integers, make np array, then offset it by 127 + data_int = struct.unpack(str(2 * CHUNK) + 'B', data) + + # create np array and offset by 128 + data_np = np.array(data_int, dtype='b')[::2] + 128 + + line.set_ydata(data_np) + + # compute FFT and update line + yf = fft(data_int) + line_fft.set_ydata(np.abs(yf[0:CHUNK]) / (128 * CHUNK)) + outData = np.abs(yf[0:CHUNK]) / (128 * CHUNK) + clear() + setSegment(4, int(outData[80]*255%255), 0, 0) + setSegment(6, int(outData[150]*255%255), 0, 0) + setSegment(1, int(outData[400]*255%255), 0, 0) + setSegment(17, int(outData[800]*255%255), 0, 0) + setSegment(26, int(outData[1000]*255%255), 0, 0) + setSegment(30, int(outData[2000]*255%255), 0, 0) + sock.sendto(buff, (UDP_IP, UDP_PORT)) + # update figure canvas + try: + fig.canvas.draw() + fig.canvas.flush_events() + frame_count += 1 + + except TclError: + + # calculate average frame rate + frame_rate = frame_count / (time.time() - start_time) + + print('stream stopped') + print('average frame rate = {:.0f} FPS'.format(frame_rate)) + break + + + # max = 0; + # maxIdx = 0; + # tmpArr = np.abs(yf[0:CHUNK]) / (128 * CHUNK) + # for i in range(0,tmpArr.size): + # tmp = tmpArr[i] + # if ( tmp > max): + # max = tmp; + # maxIdx = i; + # print(data[30]) + # update figure canvas diff --git a/testScripts/tets.py b/testScripts/tets.py new file mode 100644 index 0000000..1e99d35 --- /dev/null +++ b/testScripts/tets.py @@ -0,0 +1,25 @@ +import pyaudio +import struct +import matplotlib.pyplot as plt +import numpy as np +from scipy import signal + +p = pyaudio.PyAudio() +FORMAT = pyaudio.paInt16 +CHANNELS = 1 +RATE = 20000 +CHUNK = int(RATE/20) +stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) + + + +plt.ylabel('Frequency [Hz]') +plt.xlabel('Time [sec]') + +while True: + data = stream.read(CHUNK) + data = np.array(struct.unpack(str(2 * CHUNK) + 'B', data), dtype='b') + f, t, Sxx = signal.spectrogram(data, fs=CHUNK) + dBS = 10 * np.log10(Sxx) + plt.pcolormesh(t, f, dBS) + plt.pause(0.005) \ No newline at end of file