> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/benz206/SpotifyMacroBoard/llms.txt
> Use this file to discover all available pages before exploring further.

# Code overview

> Understanding the code structure, main functions, and program flow of the Spotify MacroBoard

This page provides a comprehensive overview of the Spotify MacroBoard source code, explaining the main components, program flow, and key functions.

## Code structure

The Spotify MacroBoard code is organized into several functional sections:

* **Setup and initialization**: Hardware configuration and WiFi connection
* **Display management**: OLED screen updates and graphics
* **LED control**: RGB LED strip animations and color transitions
* **Button handling**: Input detection and action triggering
* **API communication**: HTTP requests to Spotify control server
* **State management**: Tracking playback state and metadata

## Libraries and dependencies

The code uses several libraries for hardware control and networking:

<CodeGroup>
  ```cpp Library includes theme={null}
  #include <Adafruit_GFX.h>
  #include <Adafruit_SSD1306.h>
  #include <FastLED.h>
  #include <SMBCredentials.h>
  #include <WiFi.h>
  #include <WiFiClientSecure.h>
  #include <Wire.h>
  ```
</CodeGroup>

| Library | Purpose |
| - | - |
| `Adafruit_GFX` | Graphics primitives for drawing |
| `Adafruit_SSD1306` | OLED display driver (128x64, I2C) |
| `FastLED` | WS2812B LED strip control |
| `SMBCredentials.h` | User credentials and pin definitions |
| `WiFi` | ESP32 WiFi connectivity |
| `WiFiClientSecure` | HTTPS/TLS client for secure API calls |
| `Wire` | I2C communication protocol |

## Global objects and variables

### Hardware objects

<CodeGroup>
  ```cpp Hardware initialization theme={null}
  WiFiClientSecure wifiClient;
  TwoWire I2C = TwoWire(0);
  Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2C, OLED_RESET);
  CRGB LEDs[RGB_LED_NUM];
  ```
</CodeGroup>

* `wifiClient`: Secure HTTPS client for API communication
* `I2C`: I2C bus interface for OLED display
* `display`: SSD1306 OLED display object (128x64 pixels)
* `LEDs`: Array of RGB LED objects (20 LEDs)

### Button state tracking

<CodeGroup>
  ```cpp Button configuration theme={null}
  const int keys[7] = {SHUFFLE, VOLUME_DEC, VOLUME_INC, LOOP,
                       BACK,    PAUSE_PLAY, SKIP};
  bool keyPrevState[7] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
  bool keyState[7] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
  ```
</CodeGroup>

Buttons are configured with internal pull-up resistors, so they read `HIGH` when not pressed and `LOW` when pressed.

### Timing control

<CodeGroup>
  ```cpp Timing variables theme={null}
  const unsigned long RSSIDelay = 10000;    // WiFi signal check: 10 seconds
  const unsigned long currentDelay = 5000;   // Track info update: 5 seconds
  const unsigned long timeDelay = 1000;      // Progress bar update: 1 second
  const unsigned long fadeDelay = 4;         // LED fade step: 4 milliseconds

  unsigned long nextRSSICheck, nextCurrentCheck, nextTimeCheck, nextFade;
  ```
</CodeGroup>

The code uses non-blocking delays to handle multiple tasks concurrently.

### Playback state

<CodeGroup>
  ```cpp State variables theme={null}
  String title, artist, album, color, durationRaw, progressRaw, pausedRaw, volumeRaw;
  int progress, duration, volume;
  bool paused;
  int rssi;  // WiFi signal strength
  ```
</CodeGroup>

## Setup function

The `setup()` function runs once when the ESP32 boots and initializes all hardware components.

<Steps>
  <Step title="Configure GPIO pins">
    ```cpp theme={null}
    for (int i = 0; i < 7; i++) {
        pinMode(keys[i], INPUT_PULLUP);
    }
    pinMode(LED, OUTPUT);
    pinMode(SCL, INPUT_PULLUP);
    pinMode(SDA, INPUT_PULLUP);
    ```

    Buttons use internal pull-up resistors to eliminate the need for external resistors.
  </Step>

  <Step title="Initialize LED strip">
    ```cpp theme={null}
    FastLED.addLeds<CHIP_SET, LED, COLOR_CODE>(LEDs, RGB_LED_NUM);
    FastLED.setBrightness(BRIGHTNESS);
    FastLED.setMaxPowerInVoltsAndMilliamps(5, 500);

    for (int i = 0; i < RGB_LED_NUM; i++) LEDs[i] = CRGB::White;
    FastLED.show();
    ```

    LEDs start white to indicate power-on. Power is limited to 500mA for safety.
  </Step>

  <Step title="Initialize OLED display">
    ```cpp theme={null}
    I2C.begin(SDA, SCL, 400000);
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        for (int i = 0; i < RGB_LED_NUM; i++) {
            LEDs[i] = CRGB::OrangeRed;
        }
        FastLED.show();
        for (;;) {;}
    }
    ```

    I2C runs at 400kHz for faster display updates. If the display fails to initialize (wrong address, disconnected, etc.), LEDs turn orange-red and the system halts.
  </Step>

  <Step title="Connect to WiFi">
    ```cpp theme={null}
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, SSID_PASS);

    while (WiFi.status() != WL_CONNECTED) {
        for (int times = 0; times < 20; times++) {
            for (int i = 0; i < RGB_LED_NUM; i++) {
                byte brightness = 140 + 110 * sin(millis() / 250.0);
                LEDs[i] = CRGB::White;
                LEDs[i].fadeToBlackBy(255 - brightness);
            }
            delay(25);
            FastLED.show();
        }
    }
    ```

    LEDs pulse white while connecting. The pulsing animation provides visual feedback during connection.
  </Step>

  <Step title="Indicate successful connection">
    ```cpp theme={null}
    for (int step = 0; step < 256; step++) {
        for (int i = 0; i < RGB_LED_NUM; i++) {
            LEDs[i] = blend(CRGB::White, CRGB::Green, step);
        }
        delay(3);
        FastLED.show();
    }
    ```

    LEDs smoothly transition from white to green over 768ms to indicate successful WiFi connection.
  </Step>

  <Step title="Configure SSL certificate">
    ```cpp theme={null}
    wifiClient.setCACert(benzServerCert);
    ```

    Sets the server certificate for HTTPS verification.
  </Step>
</Steps>

## Main loop

The `loop()` function continuously monitors buttons, updates the display, and synchronizes playback state.

<CodeGroup>
  ```cpp Main loop structure theme={null}
  void loop() {
      // 1. Check button states
      for (int i = 0; i < 7; i++) {
          keyState[i] = digitalRead(keys[i]);
          if (keyState[i] == LOW && keyPrevState[i] == HIGH) {
              funcs[i]();
          }
          keyPrevState[i] = keyState[i];
      }

      // 2. Update WiFi signal strength (every 10 seconds)
      if (millis() > nextRSSICheck) {
          nextRSSICheck = millis() + RSSIDelay;
          rssi = WiFi.RSSI();
          updateScreen();
      }

      // 3. Fetch current track info (every 5 seconds)
      if (millis() > nextCurrentCheck) {
          nextCurrentCheck = millis() + currentDelay;
          updateCurrent();
      }

      // 4. Update progress bar (every 1 second)
      if (millis() > nextTimeCheck) {
          nextTimeCheck = millis() + timeDelay;
          updateTime(duration, progress);
          if (!paused) {
              progress++;
          }
      }

      // 5. Animate LED color transition (every 4 milliseconds)
      if (shouldFade && millis() > nextFade) {
          nextFade = millis() + fadeDelay;
          shouldFade = fadeLED();
      }
  }
  ```
</CodeGroup>

### Loop execution flow

1. **Button detection**: Scans all 7 buttons for state changes (edge detection)
2. **WiFi monitoring**: Updates signal strength indicator every 10 seconds
3. **Track sync**: Fetches current track metadata every 5 seconds
4. **Progress tracking**: Updates progress bar every second (increments locally if playing)
5. **LED animation**: Smoothly fades LEDs when track changes (4ms per step)

<Note>
  The loop uses non-blocking delays with `millis()` timing. This allows multiple tasks to run concurrently without freezing the system.
</Note>

## Key functions

### Button handling

Each button triggers a specific function through a function pointer array:

<CodeGroup>
  ```cpp Button function mapping theme={null}
  void (*funcs[7])() = {
      shuffle, volumeDecrease, volumeIncrease, repeat, back, pausePlay, skip
  };
  ```
</CodeGroup>

Button functions call `updateState()` with an action character:

<CodeGroup>
  ```cpp Example: Skip function theme={null}
  void skip() {
      updateState('s');
      nextCurrentCheck = millis() + 100;
      updateCurrent();
  }
  ```
</CodeGroup>

The skip function sends a skip command, then immediately requests updated track info.

### API communication

The `updateState()` function sends control commands to the server:

<CodeGroup>
  ```cpp Update state function theme={null}
  void updateState(char action, int subAction = 0) {
      String actionString = "";
      if (action == 'p') {
          actionString = "playPause";
      } else if (action == 'b') {
          actionString = "back";
      } else if (action == 's') {
          actionString = "skip";
      } else if (action == 'v') {
          if (subAction == 0) {
              actionString = "vdec";
          } else {
              actionString = "vinc";
          }
      } else if (action == 'l') {
          actionString = "loop";
      } else if (action == 'f') {
          actionString = "shuffle";
      }
      
      if (!wifiClient.connected()) {
          if (!wifiClient.connect("benzhou.tech", 443)) {
              for (int i = 0; i < RGB_LED_NUM; i++) LEDs[i] = CRGB::Red;
              FastLED.show();
              return;
          }
      }
      
      wifiClient.print("GET /api/manageState/" + PASSWORD + "/" + actionString +
                       " HTTP/1.1\r\n" + "Host: benzhou.tech\r\n" +
                       "Connection: Keep-Alive\r\n\r\n");
      wifiClient.flush();
  }
  ```
</CodeGroup>

<Info>
  API endpoint format: `GET /api/manageState/{PASSWORD}/{ACTION}`

  Actions: `playPause`, `back`, `skip`, `vdec`, `vinc`, `loop`, `shuffle`
</Info>

### Fetching current track

The `updateCurrent()` function retrieves track metadata:

<CodeGroup>
  ```cpp Update current track theme={null}
  void updateCurrent() {
      if (!wifiClient.connected()) {
          if (!wifiClient.connect("benzhou.tech", 443)) {
              for (int i = 0; i < RGB_LED_NUM; i++) LEDs[i] = CRGB::Red;
              FastLED.show();
              return;
          }
      }

      wifiClient.print("GET /api/getCurrent/" + PASSWORD + " HTTP/1.1\r\n" +
                       "Host: benzhou.tech\r\n" +
                       "Connection: Keep-Alive\r\n\r\n");

      // Wait for response (5 second timeout)
      unsigned long timeout = millis();
      while (!wifiClient.available()) {
          if (millis() - timeout > 5000) {
              for (int i = 0; i < RGB_LED_NUM; i++) LEDs[i] = CRGB::Yellow;
              FastLED.show();
              return;
          }
      }

      // Skip HTTP headers
      char endOfHeaders[] = "\r\n\r\n";
      if (!wifiClient.find(endOfHeaders)) {
          for (int i = 0; i < RGB_LED_NUM; i++) LEDs[i] = CRGB::Orange;
          FastLED.show();
          return;
      }
      
      // Read JSON response
      wifiClient.find("{\"ti");
      String response = "{\"ti";
      while (wifiClient.available()) {
          char c = wifiClient.read();
          response += c;
      }
      wifiClient.flush();

      // Parse response
      extractValue("title", response, title);
      extractValue("artist", response, artist);
      extractValue("album", response, album);
      extractValue("duration", response, durationRaw);
      extractValue("progress", response, progressRaw);
      extractValue("paused", response, pausedRaw);
      extractValue("volume", response, volumeRaw);
  }
  ```
</CodeGroup>

<Accordion title="Expected JSON response format">
  ```json theme={null}
  {
    "title": "Song Title",
    "artist": "Artist Name",
    "album": "Album Name",
    "duration": "240",
    "progress": "120",
    "paused": "false",
    "volume": "75",
    "color": [255, 128, 64]
  }
  ```
</Accordion>

### JSON parsing

The code uses a simple string-based JSON parser:

<CodeGroup>
  ```cpp Extract value from JSON theme={null}
  void extractValue(const String& key, const String& json, String& result) {
      String quoteKey = "\"" + key + "\":";
      int start = json.indexOf(quoteKey);
      if (start != -1) {
          int end = json.indexOf(",", start + quoteKey.length());
          if (end == -1) {
              end = json.indexOf("}", start + quoteKey.length());
          }
          result = json.substring(start + quoteKey.length(), end);
          result.trim();
          result.remove(0, 1);  // Remove opening quote
          result.remove(result.length() - 1);  // Remove closing quote
      }
  }
  ```
</CodeGroup>

This lightweight parser extracts values by finding key patterns and extracting substrings, avoiding the overhead of a full JSON library.

### LED color transitions

When a new track starts, LEDs smoothly transition to the album's dominant color:

<CodeGroup>
  ```cpp LED fade animation theme={null}
  bool fadeLED() {
      if (step > 255) {
          step = 0;
          return false;  // Fade complete
      }
      
      for (int i = 0; i < RGB_LED_NUM; i++) {
          LEDs[i] = blend(CRGB(prevRed, prevGreen, prevBlue),
                          CRGB(red, green, blue), step);
      }
      FastLED.show();
      step += 1;
      return true;  // Continue fading
  }
  ```
</CodeGroup>

The fade takes 256 steps × 4ms = 1,024ms (about 1 second) to complete.

### Display updates

The `updateScreen()` function draws the top status bar with WiFi signal and volume:

<CodeGroup>
  ```cpp Update screen function (excerpt) theme={null}
  void updateScreen() {
      display.setCursor(0, 0);
      display.setTextColor(WHITE);
      display.setTextSize(2);

      // Draw WiFi icon
      if (rssi == 0) {
          display.drawBitmap(2, 0, wifi_0, 16, 16, WHITE);
      } else if (rssi < -70) {
          display.drawBitmap(2, 0, wifi_1, 16, 16, WHITE);
      } else {
          display.drawBitmap(2, 0, wifi_2, 16, 16, WHITE);
      }

      // Draw volume icon
      if (volume > 66) {
          display.drawBitmap(108, 0, volume_3, 16, 16, WHITE);
      } else if (volume > 33) {
          display.drawBitmap(108, 0, volume_2, 16, 16, WHITE);
      } else if (volume > 0) {
          display.drawBitmap(108, 0, volume_1, 16, 16, WHITE);
      } else {
          display.drawBitmap(108, 0, volume_0, 16, 16, WHITE);
      }

      display.display();
  }
  ```
</CodeGroup>

The status bar uses 16×16 pixel bitmap icons for WiFi signal strength and volume level.

### Progress bar rendering

The `updateTime()` function draws the playback progress bar:

<CodeGroup>
  ```cpp Progress bar rendering theme={null}
  void updateTime(int total, int current) {
      display.fillRect(0, 48, 128, 16, BLACK);  // Clear area

      if (current > total) current = total;

      int barWidth = 100;
      int barHeight = 6;
      int barX = 14;
      int barY = 56;

      // Draw progress bar outline
      display.drawRect(barX, barY, barWidth, barHeight, WHITE);
      
      // Calculate and draw progress
      if (total <= 0) total = 1;
      int percent = (100 * current) / total;
      int progressWidth = (barWidth * percent) / 100;
      display.fillRect(barX, barY, progressWidth, barHeight, WHITE);

      // Draw time stamps
      int currentMinutes = current / 60;
      int currentSeconds = current % 60;
      int totalMinutes = total / 60;
      int totalSeconds = total % 60;

      display.setTextSize(1);
      display.setCursor(32, 48);
      display.print(currentMinutes);
      display.print(":");
      if (currentSeconds < 10) display.print("0");
      display.print(currentSeconds);
      display.print("/");
      display.print(totalMinutes);
      display.print(":");
      if (totalSeconds < 10) display.print("0");
      display.println(totalSeconds);

      display.display();
  }
  ```
</CodeGroup>

## Error indication

The code uses LED colors to indicate different error states:

| LED Color | Meaning | Cause |
| - | - | - |
| Orange-red | Display initialization failed | OLED not detected at I2C address 0x3C |
| Red | Connection failed | Cannot connect to server |
| Yellow | Request timeout | Server not responding within 5 seconds |
| Orange | Invalid response | Malformed HTTP headers |
| Green | Normal operation | Successfully connected and syncing |

## Memory usage

The compiled sketch has the following resource utilization on ESP32:

<CodeGroup>
  ```text Memory usage theme={null}
  Program storage: 886,189 bytes (67% of 1,310,720 bytes)
  Dynamic memory: 45,808 bytes (13% of 327,680 bytes)
  Free memory: 281,872 bytes for local variables
  ```
</CodeGroup>

<Tip>
  The code leaves 33% of flash storage free, allowing for future feature additions or larger assets.
</Tip>

## Performance considerations

### Non-blocking architecture

All timing uses `millis()` instead of `delay()`, allowing:

* Responsive button input
* Smooth LED animations
* Concurrent display updates
* Uninterrupted network communication

### Update frequencies

| Task | Frequency | Justification |
| - | - | - |
| Button scan | Every loop (\~1000 Hz) | Ensures no button presses are missed |
| Track metadata | 5 seconds | Balances freshness with API load |
| Progress bar | 1 second | Sufficient for visual feedback |
| WiFi signal | 10 seconds | Rarely changes quickly |
| LED fade | 4 milliseconds | Smooth visual transition |

### Connection management

HTTP connections use `Keep-Alive` to reduce overhead:

<CodeGroup>
  ```cpp Connection reuse theme={null}
  if (!wifiClient.connected()) {
      if (!wifiClient.connect("benzhou.tech", 443)) {
          // Handle error
          return;
      }
  }
  ```
</CodeGroup>

Connections are only established when needed and reused across requests.

## Customization opportunities

### Adjust update intervals

Modify timing constants to change update frequencies:

<CodeGroup>
  ```cpp Timing customization theme={null}
  const unsigned long RSSIDelay = 10000;    // Change WiFi check interval
  const unsigned long currentDelay = 5000;   // Change track sync interval
  const unsigned long timeDelay = 1000;      // Change progress update rate
  const unsigned long fadeDelay = 4;         // Change LED fade speed
  ```
</CodeGroup>

### Change LED brightness

Adjust LED brightness in `SMBCredentials.h`:

<CodeGroup>
  ```cpp Brightness adjustment theme={null}
  #define BRIGHTNESS 230  // 0-255 (230 is ~90%)
  ```
</CodeGroup>

### Modify button actions

Reorder or replace button functions:

<CodeGroup>
  ```cpp Custom button mapping theme={null}
  void (*funcs[7])() = {
      shuffle,        // Button on GPIO 4
      volumeDecrease, // Button on GPIO 5
      volumeIncrease, // Button on GPIO 12
      repeat,         // Button on GPIO 13
      back,           // Button on GPIO 14
      pausePlay,      // Button on GPIO 25
      skip            // Button on GPIO 26
  };
  ```
</CodeGroup>

## Next steps

Now that you understand the code structure:

<CardGroup cols={2}>
  <Card title="Hardware assembly" icon="screwdriver-wrench" href="/hardware/assembly">
    Build the physical MacroBoard
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/reference/troubleshooting">
    Resolve common issues
  </Card>
</CardGroup>
