#include <Adafruit_NeoPixel.h>
#define Strip_DATA 2
#define NUM_LEDS 10
#define STEP_DELAY 1000
#define FADE_INTERVAL 100
#define FADE_STEP 3
#define TRAIL_OFFSET 2
#define NUM_ROADS 3
#define ROAD_DURATION 1000
int roadPins[NUM_ROADS] = {3, 4, 5};
String roadNames[NUM_ROADS] = {"water", "straat", "druk straat"};
enum StepType { BUILDING, ROAD };
struct Step {
StepType type;
int index;
};
struct RoadState {
bool active;
unsigned long offTime;
};
Step routeSequence[NUM_LEDS + NUM_ROADS];
int sequenceLength = 0;
int currentStep = 0;
int currentBuildingStep = 0;
int totalBuildingSteps = 0;
bool sequenceActive = false;
uint8_t ledBrightness[NUM_LEDS];
RoadState roadStates[NUM_ROADS];
unsigned long lastStepUpdate = 0;
unsigned long lastFadeUpdate = 0;
Adafruit_NeoPixel strip(NUM_LEDS, Strip_DATA, NEO_GRB + NEO_KHZ800);
String locationNames[NUM_LEDS] = {
"park", "leieboorden", "broeltorens1", "broeltorens2", "school",
"kot", "konvent", "kruispunt", "boeren", "kerk"
};
void setup() {
Serial.begin(115200);
strip.begin();
strip.show();
Serial.println("Enter location names separated by commas:");
for (int i = 0; i < NUM_ROADS; i++) {
pinMode(roadPins[i], OUTPUT);
digitalWrite(roadPins[i], LOW);
roadStates[i] = {false, 0};
}
}
void loop() {
unsigned long now = millis();
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
prepareSequence(input);
}
if (sequenceActive && now - lastStepUpdate >= STEP_DELAY) {
lastStepUpdate = now;
handleStep(currentStep);
currentStep++;
if (currentStep >= sequenceLength) {
currentBuildingStep = totalBuildingSteps; // ensure we finish trail
sequenceActive = false;
Serial.println("Sequence complete. Fading out...");
}
}
if (now - lastFadeUpdate >= FADE_INTERVAL) {
lastFadeUpdate = now;
updateLEDs();
}
updateRoads(now);
if (!sequenceActive && areAllLEDsOff()) {
Serial.println("Fade-out complete.");
}
}
void handleStep(int stepIndex) {
if (stepIndex >= sequenceLength) return;
Step step = routeSequence[stepIndex];
if (step.type == BUILDING) {
int ledIndex = step.index;
ledBrightness[ledIndex] = 255;
currentBuildingStep++;
} else if (step.type == ROAD) {
int roadIndex = step.index;
digitalWrite(roadPins[roadIndex], HIGH);
roadStates[roadIndex].active = true;
roadStates[roadIndex].offTime = millis() + ROAD_DURATION;
}
}
void updateRoads(unsigned long now) {
for (int i = 0; i < NUM_ROADS; i++) {
if (roadStates[i].active && now >= roadStates[i].offTime) {
digitalWrite(roadPins[i], LOW);
roadStates[i].active = false;
}
}
}
bool isBroel(int index) {
return locationNames[index] == "broeltorens1" || locationNames[index] == "broeltorens2";
}
bool isLastBuilding(int index) {
for (int i = sequenceLength - 1; i >= 0; i--) {
if (routeSequence[i].type == BUILDING) {
return routeSequence[i].index == index;
}
}
return false;
}
void updateLEDs() {
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = ledBrightness[i];
if (brightness > 0) {
int trailOffset = currentBuildingStep - 1 - getBuildingTrailIndex(i);
if (trailOffset == 0) {
// Active LED
if (isBroel(i)) {
strip.setPixelColor(i, strip.Color(255, 0, 0)); // red
} else {
strip.setPixelColor(i, strip.Color(255, 100, 0)); // orange
}
} else if (trailOffset == 1) {
if (isBroel(i)) {
strip.setPixelColor(i, strip.Color(255, 0, 255)); // magenta
} else if (isLastBuilding(i)) {
strip.setPixelColor(i, strip.Color(0, 0, 255)); // blue
} else {
strip.setPixelColor(i, strip.Color(255, 0, 255)); // magenta
}
} else {
ledBrightness[i] = max(brightness - FADE_STEP, 0);
uint8_t val = map(ledBrightness[i], 0, 255, 0, 255);
if (isBroel(i)) {
strip.setPixelColor(i, strip.Color(val, 0, 0)); // fade red
} else if (isLastBuilding(i)) {
strip.setPixelColor(i, strip.Color(0, 0, val)); // fade blue
} else {
strip.setPixelColor(i, strip.Color(val, 0, val)); // fade magenta
}
}
} else {
strip.setPixelColor(i, 0);
}
}
strip.show();
}
int getBuildingTrailIndex(int ledIndex) {
int buildingStep = 0;
for (int i = 0; i < currentStep; i++) {
if (routeSequence[i].type == BUILDING) {
if (routeSequence[i].index == ledIndex) {
return buildingStep;
}
buildingStep++;
}
}
return -999;
}
bool areAllLEDsOff() {
for (int i = 0; i < NUM_LEDS; i++) {
if (ledBrightness[i] > 0) return false;
}
return true;
}
void prepareSequence(String input) {
strip.clear();
strip.show();
sequenceLength = 0;
currentStep = 0;
currentBuildingStep = 0;
totalBuildingSteps = 0;
sequenceActive = false;
for (int i = 0; i < NUM_LEDS; i++) {
ledBrightness[i] = 0;
}
for (int i = 0; i < NUM_ROADS; i++) {
digitalWrite(roadPins[i], LOW);
roadStates[i] = {false, 0};
}
int start = 0;
int commaIndex = input.indexOf(',');
while (start < input.length()) {
String loc;
if (commaIndex == -1) {
loc = input.substring(start);
start = input.length();
} else {
loc = input.substring(start, commaIndex);
start = commaIndex + 1;
commaIndex = input.indexOf(',', start);
}
loc.trim();
int bIndex = getLocationIndex(loc);
int rIndex = getRoadIndex(loc);
if (bIndex != -1) {
routeSequence[sequenceLength++] = {BUILDING, bIndex};
totalBuildingSteps++;
} else if (rIndex != -1) {
routeSequence[sequenceLength++] = {ROAD, rIndex};
} else {
Serial.print("Unknown location: ");
Serial.println(loc);
}
}
if (sequenceLength > 0) {
sequenceActive = true;
lastStepUpdate = millis();
lastFadeUpdate = millis();
Serial.println("Starting sequence...");
}
}
int getLocationIndex(String name) {
name.toLowerCase();
for (int i = 0; i < NUM_LEDS; i++) {
if (name == locationNames[i]) return i;
}
return -1;
}
int getRoadIndex(String name) {
name.toLowerCase();
for (int i = 0; i < NUM_ROADS; i++) {
if (name == roadNames[i]) return i;
}
return -1;
}