/* * ============================================================================= * Fab Academy 2026 — Week 15 — Yaroslav Artsishevskiy * Bullet shooter: web buttons fire colored "bullets" along the LED strip * ----------------------------------------------------------------------------- * How it works: * - The web page has six colored buttons (red, orange, yellow, green, * blue, purple). Click a button to fire a bullet of that color. * - Each bullet is born at LED 0 and travels down the strip, one LED * per frame. When it reaches the end, it disappears. * - Multiple bullets can travel at once. Click fast to fire a stream. * * Hardware: * 30 WS2812B LEDs on D0 (the A0/D0 Grove connector). * 5V to the 5V pad, GND to a GND pad. * * Libraries: * FastLED, WiFi, WebServer * ============================================================================= */ #include #include #include "Network.h" #include "WiFi.h" #include // ---- Strip ---- #define LED_PIN D0 #define NUM_LEDS 30 #define BRIGHTNESS 60 CRGB leds[NUM_LEDS]; // ---- Wi-Fi ---- NetworkUDP udp; WebServer server(80); const char* ssid = "YaroUDP"; const char* password = "12345678"; IPAddress local_IP(192, 168, 4, 1); IPAddress gateway(192, 168, 4, 1); IPAddress subnet(255, 255, 255, 0); const uint16_t udpPort = 1234; // ---- Bullets -------------------------------------------------------------- // We can have up to MAX_BULLETS travelling along the strip at the same time. // Each one remembers its position (which LED it's currently on) and its color. // position = -1 means "this slot is empty" — no bullet here. #define MAX_BULLETS 10 struct Bullet { int position; // current LED index, or -1 if unused CRGB color; // bullet color }; Bullet bullets[MAX_BULLETS]; // How fast bullets move: one step every BULLET_STEP_MS milliseconds. // Smaller = faster. 50 ms is a nice readable speed. const unsigned long BULLET_STEP_MS = 50; unsigned long lastBulletStep = 0; // ---- Helper: fire a new bullet ------------------------------------------- // Find the first empty slot in the bullets array and put a new bullet there. // If all slots are full, the new shot is silently dropped (rare edge case). void fireBullet(CRGB color) { for (int i = 0; i < MAX_BULLETS; i++) { if (bullets[i].position < 0) { bullets[i].position = 0; bullets[i].color = color; return; } } } // ---- The webpage ---------------------------------------------------------- // Six big colored buttons. Each one calls /shoot?color=NAME with fetch(). const char HTML_PAGE[] PROGMEM = R"HTML( YaroShoot

Shoot

)HTML"; // ---- HTTP handlers -------------------------------------------------------- void handleRoot() { server.send_P(200, "text/html", HTML_PAGE); } // Read the color name from the URL and fire a bullet of that color. void handleShoot() { String c = server.arg("color"); CRGB color = CRGB::White; // default fallback if (c == "red") color = CRGB::Red; else if (c == "orange") color = CRGB(255, 100, 0); else if (c == "yellow") color = CRGB::Yellow; else if (c == "green") color = CRGB::Green; else if (c == "blue") color = CRGB::Blue; else if (c == "purple") color = CRGB(150, 0, 200); fireBullet(color); Serial.print("Shoot "); Serial.println(c); server.send(200, "text/plain", "OK"); } // ---- Setup ---------------------------------------------------------------- void setup() { Serial.begin(115200); delay(500); // Strip FastLED.addLeds(leds, NUM_LEDS); FastLED.setBrightness(BRIGHTNESS); FastLED.clear(); FastLED.show(); // Mark all bullet slots as empty for (int i = 0; i < MAX_BULLETS; i++) bullets[i].position = -1; // Wi-Fi Network.begin(); WiFi.mode(WIFI_AP); WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(ssid, password); udp.begin(udpPort); // Web routes server.on("/", handleRoot); server.on("/shoot", handleShoot); server.begin(); Serial.println("Ready"); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); } // ---- Loop ----------------------------------------------------------------- void loop() { // 1. Handle any new HTTP requests (button clicks) server.handleClient(); // 2. Move and draw bullets, but only every BULLET_STEP_MS milliseconds // so the animation has a steady, readable speed. if (millis() - lastBulletStep >= BULLET_STEP_MS) { lastBulletStep = millis(); // Clear the strip for this frame FastLED.clear(); // For each active bullet: draw it at its current position, then move // it one step forward. If it has gone past the end, deactivate it. for (int i = 0; i < MAX_BULLETS; i++) { if (bullets[i].position >= 0) { // Draw at current position (only if still on the strip) if (bullets[i].position < NUM_LEDS) { leds[bullets[i].position] = bullets[i].color; } // Advance one step bullets[i].position++; // If gone past the end, free the slot if (bullets[i].position >= NUM_LEDS) { bullets[i].position = -1; } } } FastLED.show(); } }