# repo_index

file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\src\wifi_manager.cpp
repo_name: wheelie
function_name: initializeWiFi
last_modified: 2025-12-23 20:04:44.173353
content:
  #include "wifi_manager.h"
  #include "credentials.h" // <-- FIX #3: Include credentials
  #include "ota_manager.h" // <-- ADD THIS
  #include "web_server.h"  // For starting the web server after WiFi connects

  // ═══════════════════════════════════════════════════════════════════════════
  // WIFI MANAGEMENT IMPLEMENTATION - Worker Module
  // ═══════════════════════════════════════════════════════════════════════════
  //
  // This module acts as a worker that manages WiFi connection and updates the
  // global sysStatus struct. It does not expose getter functions to maintain
  // the single source of truth principle.
  //
  // ═══════════════════════════════════════════════════════════════════════════

  // Access to global system status
  extern SystemStatus sysStatus;

  // Internal state variables
  static WiFiStatus currentWiFiStatus = WIFI_DISCONNECTED;
  static unsigned long lastConnectionAttempt = 0;

  void initializeWiFi() {
    Serial.println("📶 INITIALIZING WIFI SUBSYSTEM");
    // Set WiFi mode to station (client)
    WiFi.mode(WIFI_STA);
    // Set hostname
    WiFi.setHostname(OTA_HOSTNAME);
    Serial.print("📡 Target Network: ");
    Serial.println(WIFI_SSID);
    
    // Initialize sysStatus fields
    sysStatus.wifiConnected = false;
    strncpy(sysStatus.ipAddress, "Connecting...", sizeof(sysStatus.ipAddress));
    
    // Start the FIRST connection attempt
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    currentWiFiStatus = WIFI_CONNECTING;
    lastConnectionAttempt = millis();
  }

  /**
   * @brief Main update function for WiFi. Call this in the main loop.
   * This is a NON-BLOCKING state machine.
   */
  void checkWiFiConnection() {
      // 1. Handle the "Connecting" state
      if (currentWiFiStatus == WIFI_CONNECTING) {
          wl_status_t status = WiFi.status();

          if (status == WL_CONNECTED) {
              // --- We are now connected ---
              currentWiFiStatus = WIFI_CONNECTED;
              sysStatus.wifiConnected = true;
              
              // Safely copy the IP address
              String ipStr = WiFi.localIP().toString();
              strncpy(sysStatus.ipAddress, ipStr.c_str(), sizeof(sysStatus.ipAddress) - 1);
              sysStatus.ipAddress[sizeof(sysStatus.ipAddress) - 1] = '\0'; // Ensure null termination
              
              Serial.println("\n✅ WiFi Connected!");
              Serial.print("   IP Address: ");
              Serial.println(sysStatus.ipAddress);

              // --- Initialize OTA service now that we have WiFi ---
              initializeOTA();

              // --- Initialize the Web Server now that we have an IP ---
              initializeWebServer();
          }
          else if (millis() - lastConnectionAttempt > WIFI_TIMEOUT) {
              // --- Connection timed out ---
              currentWiFiStatus = WIFI_CONNECTION_FAILED;
              sysStatus.wifiConnected = false;
              strncpy(sysStatus.ipAddress, "Failed", sizeof(sysStatus.ipAddress));
              Serial.println("\n❌ WiFi Connection Failed (Timeout)");
              lastConnectionAttempt = millis(); // Reset timer for next retry
          }
          // else: Still connecting, do nothing and let the loop run
      }
      
      // 2. Handle the "Connected" state (check for drops)
      else if (currentWiFiStatus == WIFI_CONNECTED) {
          if (WiFi.status() != WL_CONNECTED) {
              // --- We just lost connection ---
              currentWiFiStatus = WIFI_CONNECTION_LOST;
              sysStatus.wifiConnected = false;
              strncpy(sysStatus.ipAddress, "Lost", sizeof(sysStatus.ipAddress));
              Serial.println("⚠️ WiFi Connection Lost!");
              lastConnectionAttempt = millis(); // Start retry timer
          }
          // else: Still connected, do nothing
      }

      // 3. Handle "Failed" or "Lost" states (retry logic)
      else if (currentWiFiStatus == WIFI_CONNECTION_FAILED || currentWiFiStatus == WIFI_CONNECTION_LOST) {
          if (millis() - lastConnectionAttempt > WIFI_RETRY_INTERVAL) {
              // --- Time to retry ---
              Serial.println("🔄 Attempting WiFi reconnection...");
              WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
              currentWiFiStatus = WIFI_CONNECTING;
              lastConnectionAttempt = millis();
          }
      }
  }

  // -------------------------------------------------------------------
  // ALL FUNCTIONS BELOW THIS LINE ARE NOW DELETED
  //
  // - handleWiFiReconnection() (logic is now inside checkWiFiConnection)
  // - printWiFiInfo() (info is printed on connect)
  // - indicateWiFiStatus() (VIOLATES SoC, a job for indicators.cpp)
  // - showWiFiConnectionProgress() (VIOLATES SoC)
  //
  // -------------------------------------------------------------------


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\I2C_bus_diagnostic_tool.cpp
repo_name: wheelie
function_name: repeatChar
last_modified: 2025-12-23 20:04:44.177661
content:
  #include <Arduino.h>
  #include <Wire.h>
  #include "pins.h"

  // Utility function to repeat a character n times and return as String
  String repeatChar(char c, int count) {
    String result = "";
    for (int i = 0; i < count; i++) {
      result += c;
    }
    return result;
  }

  // Known I2C device addresses for WHEELIE
  struct I2CDevice {
    uint8_t address;
    const char* name;
    const char* description;
  };

  const I2CDevice knownDevices[] = {
    {0x29, "VL53L0X", "Time-of-Flight Distance Sensor"},
    {0x68, "MPU6050", "6-Axis IMU (Gyro + Accel)"},
    {0x69, "MPU6050", "6-Axis IMU (AD0=HIGH)"},
    {0x76, "BMP280", "Pressure/Temperature Sensor"},
    {0x77, "BMP280", "Pressure/Temperature (Alt)"},
    {0x1E, "HMC5883L", "3-Axis Magnetometer"},
    {0x0C, "AK8963", "3-Axis Magnetometer"},
    {0x20, "PCF8574", "I2C I/O Expander"},
    {0x27, "LCD", "I2C LCD Display"},
    {0x3C, "OLED", "I2C OLED Display (SSD1306)"},
    {0x3D, "OLED", "I2C OLED Display (Alt)"},
    {0x48, "ADS1115", "16-bit ADC"},
    {0x50, "EEPROM", "I2C EEPROM (24C32)"}
  };

  const int numKnownDevices = sizeof(knownDevices) / sizeof(knownDevices[0]);

  // Test results tracking
  int devicesFound = 0;
  uint8_t foundAddresses[128];
  bool hasMPU6050 = false;
  bool hasVL53L0X = false;

  void setup() {
    Serial.begin(115200);
    
    // Wait for serial with timeout
    unsigned long startTime = millis();
    Serial.println(repeatChar('─', 60));
    while (!Serial && millis() - startTime < 3000) {
      delay(10);
    }
    
    printHeader();
    
    // Initialize I2C
    Serial.println("\n[STEP 1] Initializing I2C Bus");
    Serial.println(repeatChar('─', 60));
    
    Wire.begin(I2C_SDA, I2C_SCL);
    Serial.print("  SDA Pin: GPIO ");
    Serial.println(I2C_SDA);
    Serial.print("  SCL Pin: GPIO ");
    Serial.println(I2C_SCL);
    
    // Test different I2C speeds
    testI2CSpeeds();
    
    // Perform comprehensive scan
    Serial.println("\n[STEP 2] Comprehensive I2C Device Scan");
    Serial.println(repeatChar('─', 60));
    scanI2CBus();
    
    // Identify devices
    Serial.println("\n[STEP 3] Device Identification");
    Serial.println(repeatChar('─', 60));
    identifyDevices();
    
    // Verify critical WHEELIE sensors
    Serial.println("\n[STEP 4] WHEELIE Sensor Verification");
    Serial.println(repeatChar('─', 60));
    verifyWHEELIESensors();
    
    // Test device communication
    Serial.println("\n[STEP 5] Device Communication Test");
    Serial.println(repeatChar('─', 60));
    testDeviceCommunication();
    
    // Final summary
    printSummary();
    
    // Interactive mode
    Serial.println("\n[STEP 6] Entering Interactive Mode");
    Serial.println(repeatChar('─', 60));
    printInteractiveMenu();
  }

  void printHeader() {
    Serial.println("\n" + repeatChar('═', 60));
    Serial.println("       WHEELIE I2C Bus Diagnostic Tool v2.0");
    Serial.println(repeatChar('═', 60));
    Serial.println("  Purpose: Scan, identify, and test I2C devices");
    Serial.println("  Author: For WHEELIE Robot Project");
    Serial.println(repeatChar('═', 60));
  }

  void testI2CSpeeds() {
    const uint32_t speeds[] = {100000, 200000, 400000}; // 100kHz, 200kHz, 400kHz
    const char* speedNames[] = {"100kHz (Standard)", "200kHz (Fast)", "400kHz (Fast Mode)"};
    
    Serial.println("\n  Testing I2C Clock Speeds:");
    Serial.println("  " + repeatChar('─', 56));
    
    for (int i = 0; i < 3; i++) {
      Wire.setClock(speeds[i]);
      delay(50); // Let bus stabilize
      
      Serial.print("    ");
      Serial.print(speedNames[i]);
      Serial.print(": ");
      
      // Quick scan at this speed
      int deviceCount = quickScanCount();
      
      if (deviceCount > 0) {
        Serial.print("✓ OK (found ");
        Serial.print(deviceCount);
        Serial.println(" device(s))");
      } else {
        Serial.println("✗ No devices detected");
      }
    }
    
    // Set back to reliable 100kHz
    Wire.setClock(100000);
    Serial.println("\n  → Using 100kHz for remaining tests (most reliable)");
  }

  int quickScanCount() {
    int count = 0;
    
    for (uint8_t addr = 1; addr < 127; addr++) {
      Wire.beginTransmission(addr);
      if (Wire.endTransmission() == 0) {
        count++;
      }
      delay(1); // Small delay between attempts
    }
    
    return count;
  }

  void scanI2CBus() {
    Serial.println("  Scanning all addresses (0x01 to 0x7E)...\n");
    
    devicesFound = 0;
    
    Serial.println("       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
    Serial.println("      ──────────────────────────────────────────────────");
    
    for (uint8_t row = 0; row < 8; row++) {
      Serial.print("  ");
      Serial.print(row, HEX);
      Serial.print("0: ");
      
      for (uint8_t col = 0; col < 16; col++) {
        uint8_t address = (row * 16) + col;
        
        // Skip reserved addresses
        if (address < 0x08 || address > 0x77) {
          Serial.print("   ");
          continue;
        }
        
        Wire.beginTransmission(address);
        uint8_t error = Wire.endTransmission();
        
        if (error == 0) {
          // Device found!
          if (address < 0x10) Serial.print("0");
          Serial.print(address, HEX);
          Serial.print(" ");
          
          foundAddresses[devicesFound] = address;
          devicesFound++;
          
          // Track specific devices
          if (address == 0x68 || address == 0x69) hasMPU6050 = true;
          if (address == 0x29) hasVL53L0X = true;
          
        } else {
          Serial.print("-- ");
        }
      }
      Serial.println();
    }
    
    Serial.println("\n  " + repeatChar('─', 56));
    Serial.print("  Total devices found: ");
    Serial.println(devicesFound);
  }

  void identifyDevices() {
    if (devicesFound == 0) {
      Serial.println("  ✗ No devices found to identify");
      return;
    }
    
    Serial.println("  Identified devices:\n");
    
    for (int i = 0; i < devicesFound; i++) {
      uint8_t addr = foundAddresses[i];
      
      Serial.print("    0x");
      if (addr < 0x10) Serial.print("0");
      Serial.print(addr, HEX);
      Serial.print(" → ");
      
      // Look up in known devices
      bool identified = false;
      for (int j = 0; j < numKnownDevices; j++) {
        if (knownDevices[j].address == addr) {
          Serial.print(knownDevices[j].name);
          Serial.print(" (");
          Serial.print(knownDevices[j].description);
          Serial.println(")");
          identified = true;
          break;
        }
      }
      
      if (!identified) {
        Serial.println("Unknown Device");
      }
    }
  }

  void verifyWHEELIESensors() {
    Serial.println("  Checking critical WHEELIE sensors:\n");
    
    // Check MPU6050
    Serial.print("    MPU6050 (IMU):        ");
    if (hasMPU6050) {
      Serial.println("✓ DETECTED");
    } else {
      Serial.println("✗ NOT FOUND");
      Serial.println("      → Check VCC, GND, SDA, SCL connections");
      Serial.println("      → Verify AD0 pin (LOW=0x68, HIGH=0x69)");
    }
    
    // Check VL53L0X
    Serial.print("    VL53L0X (ToF):        ");
    if (hasVL53L0X) {
      Serial.println("✓ DETECTED");
    } else {
      Serial.println("✗ NOT FOUND");
      Serial.println("      → Check VCC, GND, SDA, SCL connections");
      Serial.println("      → Verify XSHUT pin (should be HIGH or floating)");
    }
    
    // Overall status
    Serial.println();
    if (hasMPU6050 && hasVL53L0X) {
      Serial.println("  ✓ All critical WHEELIE sensors detected!");
    } else {
      Serial.println("  ⚠ Some WHEELIE sensors missing - check wiring");
    }
  }

  void testDeviceCommunication() {
    if (devicesFound == 0) {
      Serial.println("  ✗ No devices to test");
      return;
    }
    
    Serial.println("  Testing read/write capability:\n");
    
    for (int i = 0; i < devicesFound; i++) {
      uint8_t addr = foundAddresses[i];
      
      Serial.print("    0x");
      if (addr < 0x10) Serial.print("0");
      Serial.print(addr, HEX);
      Serial.print(": ");
      
      // Test specific devices
      if (addr == 0x68 || addr == 0x69) {
        testMPU6050(addr);
      } else if (addr == 0x29) {
        testVL53L0X(addr);
      } else {
        // Generic read test
        Wire.beginTransmission(addr);
        Wire.write(0x00); // Try to read register 0x00
        uint8_t error = Wire.endTransmission(false);
        
        if (error == 0) {
          Wire.requestFrom(addr, (uint8_t)1);
          if (Wire.available()) {
            uint8_t data = Wire.read();
            Serial.print("Read OK (0x");
            if (data < 0x10) Serial.print("0");
            Serial.print(data, HEX);
            Serial.println(")");
          } else {
            Serial.println("Read failed (no data)");
          }
        } else {
          Serial.println("Communication error");
        }
      }
    }
  }

  void testMPU6050(uint8_t addr) {
    // Read WHO_AM_I register (0x75)
    Wire.beginTransmission(addr);
    Wire.write(0x75);
    Wire.endTransmission(false);
    Wire.requestFrom(addr, (uint8_t)1);
    
    if (Wire.available()) {
      uint8_t whoAmI = Wire.read();
      
      if (whoAmI == 0x68) {
        Serial.println("✓ MPU6050 responding correctly");
        
        // Check if it's in sleep mode
        Wire.beginTransmission(addr);
        Wire.write(0x6B); // PWR_MGMT_1
        Wire.endTransmission(false);
        Wire.requestFrom(addr, (uint8_t)1);
        
        if (Wire.available()) {
          uint8_t pwrMgmt = Wire.read();
          if (pwrMgmt & 0x40) {
            Serial.println("      ⚠ Device in SLEEP mode (needs wake-up)");
          } else {
            Serial.println("      ✓ Device AWAKE and ready");
          }
        }
        
      } else {
        Serial.print("⚠ Unexpected WHO_AM_I: 0x");
        if (whoAmI < 0x10) Serial.print("0");
        Serial.println(whoAmI, HEX);
      }
    } else {
      Serial.println("✗ No response from WHO_AM_I register");
    }
  }

  void testVL53L0X(uint8_t addr) {
    // Read model ID register (0xC0)
    Wire.beginTransmission(addr);
    Wire.write(0xC0);
    Wire.endTransmission(false);
    Wire.requestFrom(addr, (uint8_t)1);
    
    if (Wire.available()) {
      uint8_t modelId = Wire.read();
      
      if (modelId == 0xEE) {
        Serial.println("✓ VL53L0X responding correctly");
        Serial.println("      ✓ Model ID verified (0xEE)");
      } else {
        Serial.print("⚠ Unexpected Model ID: 0x");
        if (modelId < 0x10) Serial.print("0");
        Serial.println(modelId, HEX);
      }
    } else {
      Serial.println("✗ No response from Model ID register");
    }
  }

  void printSummary() {
    Serial.println("\n" + repeatChar('═', 60));
    Serial.println("DIAGNOSTIC SUMMARY");
    Serial.println(repeatChar('═', 60));
    
    Serial.print("\n  I2C Bus Status:        ");
    if (devicesFound > 0) {
      Serial.println("✓ OPERATIONAL");
    } else {
      Serial.println("✗ NO DEVICES DETECTED");
    }
    
    Serial.print("  Devices Found:         ");
    Serial.println(devicesFound);
    
    Serial.print("  MPU6050 (IMU):         ");
    Serial.println(hasMPU6050 ? "✓ DETECTED" : "✗ MISSING");
    
    Serial.print("  VL53L0X (ToF):         ");
    Serial.println(hasVL53L0X ? "✓ DETECTED" : "✗ MISSING");
    
    Serial.println();
    
    if (devicesFound == 0) {
      Serial.println("⚠ TROUBLESHOOTING STEPS:");
      Serial.println("  1. Check all power connections (VCC, GND)");
      Serial.println("  2. Verify SDA and SCL are on correct GPIO pins");
      Serial.println("  3. Check for loose breadboard connections");
      Serial.println("  4. Try different I2C pullup resistors (4.7kΩ)");
      Serial.println("  5. Test sensors individually");
    } else if (!hasMPU6050 || !hasVL53L0X) {
      Serial.println("⚠ MISSING SENSORS - Check:");
      if (!hasMPU6050) {
        Serial.println("  • MPU6050: VCC, GND, SDA, SCL, AD0 pin");
      }
      if (!hasVL53L0X) {
        Serial.println("  • VL53L0X: VCC, GND, SDA, SCL, XSHUT pin");
      }
    } else {
      Serial.println("✓ WHEELIE I2C bus is fully operational!");
      Serial.println("  All critical sensors detected and responding.");
    }
    
    Serial.println(repeatChar('═', 60));
  }

  void printInteractiveMenu() {
    Serial.println("\n  Available Commands:");
    Serial.println("    s - Re-scan I2C bus");
    Serial.println("    m - Test MPU6050 communication");
    Serial.println("    t - Test VL53L0X communication");
    Serial.println("    d - Dump device registers (debug)");
    Serial.println("    p - Test different I2C speeds");
    Serial.println("    h - Show this help menu");
    Serial.println();
  }

  void loop() {
    if (Serial.available()) {
      char cmd = Serial.read();
      while (Serial.available()) Serial.read(); // Clear buffer
      
      Serial.println("\n" + repeatChar('─', 60));
      
      // Declare variables that may be initialized in case blocks before the switch
      String addrStr;
      uint8_t dumpAddr;

      switch (cmd) {
        case 's':
        case 'S':
          Serial.println("Re-scanning I2C bus...\n");
          devicesFound = 0;
          hasMPU6050 = false;
          hasVL53L0X = false;
          scanI2CBus();
          identifyDevices();
          break;
          
        case 'm':
        case 'M':
          if (hasMPU6050) {
            Serial.println("Testing MPU6050 detailed communication...\n");
            testMPU6050Detailed();
          } else {
            Serial.println("✗ MPU6050 not detected. Run scan first.");
          }
          break;
          
        case 't':
        case 'T':
          if (hasVL53L0X) {
            Serial.println("Testing VL53L0X detailed communication...\n");
            testVL53L0XDetailed();
          } else {
            Serial.println("✗ VL53L0X not detected. Run scan first.");
          }
          break;
          
        case 'd':
        case 'D':
          Serial.println("Register dump mode");
          Serial.println("Enter device address in hex (e.g., 68): ");
          // Wait for address input
          while (!Serial.available()) delay(10);
          
          addrStr = Serial.readStringUntil('\n');
          addrStr.trim();
          dumpAddr = (uint8_t)strtol(addrStr.c_str(), NULL, 16);
          
          dumpRegisters(dumpAddr);
          break;
          
        case 'p':
        case 'P':
          testI2CSpeeds();
          Wire.setClock(100000); // Reset to 100kHz
          break;
          
        case 'h':
        case 'H':
          printInteractiveMenu();
          break;
          
        default:
          Serial.println("⚠ Unknown command. Press 'h' for help.");
          break;
      }
      
      Serial.println(repeatChar('─', 60));
      Serial.println();
    }
  }

  void testMPU6050Detailed() {
    uint8_t addr = hasMPU6050 ? 0x68 : 0x69;
    
    Serial.println("  MPU6050 Register Tests:");
    
    // WHO_AM_I
    Serial.print("    WHO_AM_I (0x75):      ");
    uint8_t whoAmI = readRegister(addr, 0x75);
    Serial.print("0x");
    if (whoAmI < 0x10) Serial.print("0");
    Serial.print(whoAmI, HEX);
    Serial.println(whoAmI == 0x68 ? " ✓" : " ✗");
    
    // Power Management
    Serial.print("    PWR_MGMT_1 (0x6B):    ");
    uint8_t pwrMgmt = readRegister(addr, 0x6B);
    Serial.print("0x");
    if (pwrMgmt < 0x10) Serial.print("0");
    Serial.print(pwrMgmt, HEX);
    if (pwrMgmt & 0x40) {
      Serial.println(" (SLEEP MODE)");
    } else {
      Serial.println(" (AWAKE)");
    }
    
    // Gyro Config
    Serial.print("    GYRO_CONFIG (0x1B):   ");
    uint8_t gyroConfig = readRegister(addr, 0x1B);
    Serial.print("0x");
    if (gyroConfig < 0x10) Serial.print("0");
    Serial.print(gyroConfig, HEX);
    int gyroRange = (gyroConfig >> 3) & 0x03;
    Serial.print(" (±");
    Serial.print(250 * (1 << gyroRange));
    Serial.println("°/s)");
    
    // Accel Config
    Serial.print("    ACCEL_CONFIG (0x1C):  ");
    uint8_t accelConfig = readRegister(addr, 0x1C);
    Serial.print("0x");
    if (accelConfig < 0x10) Serial.print("0");
    Serial.print(accelConfig, HEX);
    int accelRange = (accelConfig >> 3) & 0x03;
    Serial.print(" (±");
    Serial.print(2 * (1 << accelRange));
    Serial.println("g)");
    
    Serial.println("\n  ✓ MPU6050 communication verified");
  }

  void testVL53L0XDetailed() {
    uint8_t addr = 0x29;
    
    Serial.println("  VL53L0X Register Tests:");
    
    // Model ID
    Serial.print("    Model ID (0xC0):      ");
    uint8_t modelId = readRegister(addr, 0xC0);
    Serial.print("0x");
    if (modelId < 0x10) Serial.print("0");
    Serial.print(modelId, HEX);
    Serial.println(modelId == 0xEE ? " ✓" : " ✗");
    
    // Module Type
    Serial.print("    Module Type (0xC1):   ");
    uint8_t moduleType = readRegister(addr, 0xC1);
    Serial.print("0x");
    if (moduleType < 0x10) Serial.print("0");
    Serial.println(moduleType, HEX);
    
    // Revision ID
    Serial.print("    Revision ID (0xC2):   ");
    uint8_t revId = readRegister(addr, 0xC2);
    Serial.print("0x");
    if (revId < 0x10) Serial.print("0");
    Serial.println(revId, HEX);
    
    Serial.println("\n  ✓ VL53L0X communication verified");
  }

  uint8_t readRegister(uint8_t deviceAddr, uint8_t regAddr) {
    Wire.beginTransmission(deviceAddr);
    Wire.write(regAddr);
    Wire.endTransmission(false);
    Wire.requestFrom(deviceAddr, (uint8_t)1);
    
    if (Wire.available()) {
      return Wire.read();
    }
    return 0xFF;
  }

  void dumpRegisters(uint8_t addr) {
    Serial.println("\n  Register Dump for 0x" + String(addr, HEX) + ":");
    Serial.println("  " + repeatChar('─', 56));
    Serial.println("       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
    Serial.println("      ──────────────────────────────────────────────────");
    
    for (int row = 0; row < 16; row++) {
      Serial.print("  ");
      if (row < 10) Serial.print(" ");
      Serial.print(row, HEX);
      Serial.print("0: ");
      
      for (int col = 0; col < 16; col++) {
        uint8_t regAddr = (row * 16) + col;
        uint8_t value = readRegister(addr, regAddr);
        
        if (value < 0x10) Serial.print("0");
        Serial.print(value, HEX);
        Serial.print(" ");
      }
      Serial.println();
    }
    
    Serial.println();
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\master_test.cpp
repo_name: wheelie
function_name: runBuzzerTest
last_modified: 2025-12-23 20:04:44.182280
content:
  #include <Arduino.h>
  #include "pins.h"
  #include "credentials.h" // For WiFi test
  #include <WiFi.h>
  #include <WebServer.h>
  #include <esp_now.h>
  // Add other required sensor/actuator libraries here

  // --- Test Step Enum ---
  enum TestStep {
    TEST_BUZZER,
    TEST_RGB_LED,
    TEST_MOTORS,
    TEST_ENCODERS,
    TEST_SOUND_SENSOR,
    TEST_TOF,
    TEST_MPU6050,
    TEST_WIFI,
    TEST_ESPNOW,
    TEST_OTA,
    TEST_DONE
  };

  TestStep currentTest = TEST_BUZZER;
  unsigned long nextStepTime = 0;
  const unsigned long stepDelay = 2000; // 2 seconds between tests

  // --- Buzzer Test (Non-blocking) ---
  void runBuzzerTest(bool begin, bool &done) {
    static int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};
    static int note = 0;
    static unsigned long noteStart = 0;
    static bool playing = false;
    if (begin) {
      Serial.println("[TEST] KY-006 Passive Buzzer");
      note = 0;
      noteStart = millis();
      playing = true;
      tone(BUZZER_PIN, melody[note], 120);
    }
    if (playing && millis() - noteStart >= 150) {
      note++;
      if (note < 4) {
        tone(BUZZER_PIN, melody[note], 120);
        noteStart = millis();
      } else {
        noTone(BUZZER_PIN);
        playing = false;
        done = true;
      }
    }
  }

  // --- Motors Test (Stub) ---
  // --- Motors Control Functions ---
  void setLeftMotor(int pwmA, int pwmB) {
    analogWrite(IN1_PIN, pwmA);
    analogWrite(IN2_PIN, pwmB);
  }
  void setRightMotor(int pwmA, int pwmB) {
    analogWrite(IN3_PIN, pwmA);
    analogWrite(IN4_PIN, pwmB);
  }
  void stopMotors() {
    setLeftMotor(0, 0);
    setRightMotor(0, 0);
  }

  // --- Motors Test (Non-blocking) ---
  void runMotorsTest(bool begin, bool &done) {
    static int phase = 0;
    static unsigned long phaseStart = 0;
    if (begin) {
      Serial.println("\n[TEST] Motors");
      pinMode(IN1_PIN, OUTPUT);
      pinMode(IN2_PIN, OUTPUT);
      pinMode(IN3_PIN, OUTPUT);
      pinMode(IN4_PIN, OUTPUT);
      stopMotors();
      phase = 0;
      phaseStart = millis();
      Serial.println("Left motor forward");
      setLeftMotor(200, 0);
    }
    if (phase == 0 && millis() - phaseStart > 600) {
      Serial.println("Left motor reverse");
      setLeftMotor(0, 200);
      phase = 1;
      phaseStart = millis();
    } else if (phase == 1 && millis() - phaseStart > 600) {
      setLeftMotor(0, 0);
      Serial.println("Right motor forward");
      setRightMotor(200, 0);
      phase = 2;
      phaseStart = millis();
    } else if (phase == 2 && millis() - phaseStart > 600) {
      Serial.println("Right motor reverse");
      setRightMotor(0, 200);
      phase = 3;
      phaseStart = millis();
    } else if (phase == 3 && millis() - phaseStart > 600) {
      setRightMotor(0, 0);
      done = true;
    }
  }

  // --- Encoders Test (Stub) ---
  // --- Encoder Interrupt Handlers ---
  volatile long leftCount = 0;
  volatile long rightCount = 0;
  volatile int8_t leftDir = 0;
  volatile int8_t rightDir = 0;
  volatile unsigned long leftLastTime = 0;
  volatile unsigned long rightLastTime = 0;

  void onLeftEncoder() {
    int b = digitalRead(ENCODER_B_PIN);
    leftDir = b ? 1 : -1;
    leftCount += leftDir;
    leftLastTime = micros();
  }
  void onRightEncoder() {
    int a = digitalRead(ENCODER_A_PIN);
    rightDir = a ? 1 : -1;
    rightCount += rightDir;
    rightLastTime = micros();
  }

  // --- Encoders Test (Non-blocking) ---
  void runEncodersTest(bool begin, bool &done) {
    static bool attached = false;
    if (begin) {
      Serial.println("\n[TEST] LM393 H2010 Encoders");
      pinMode(ENCODER_A_PIN, INPUT_PULLUP);
      pinMode(ENCODER_B_PIN, INPUT_PULLUP);
      if (!attached) {
        attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), onLeftEncoder, RISING);
        attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), onRightEncoder, RISING);
        attached = true;
      }
      Serial.println("Move wheels by hand. Counts, direction, and timing will be displayed.");
      Serial.println("Debug: Stuck detection every 2s, direction: +1=forward, -1=reverse");
    }
    static unsigned long lastPrint = 0;
    if (millis() - lastPrint > 500) {
      Serial.print("Left: "); Serial.print(leftCount);
      Serial.print(" (dir "); Serial.print(leftDir); Serial.print(")  ");
      Serial.print("Right: "); Serial.print(rightCount);
      Serial.print(" (dir "); Serial.print(rightDir); Serial.print(")  ");
      Serial.println();
      lastPrint = millis();
    }
    // End after 4 seconds
    static unsigned long start = 0;
    if (begin) start = millis();
    if (millis() - start > 4000) done = true;
  }

  // --- RGB LED Test (Stub) ---
  // --- RGB LED Test (Non-blocking) ---
  void runRGBLEDTest(bool begin, bool &done) {
    static int phase = 0;
    static unsigned long phaseStart = 0;
    if (begin) {
      Serial.println("[TEST] KY-009 RGB LED");
      pinMode(LED_RED_PIN, OUTPUT);
      pinMode(LED_GREEN_PIN, OUTPUT);
      pinMode(LED_BLUE_PIN, OUTPUT);
      phase = 0;
      phaseStart = millis();
      analogWrite(LED_RED_PIN, 255);
      analogWrite(LED_GREEN_PIN, 0);
      analogWrite(LED_BLUE_PIN, 0);
      Serial.println("  - Red");
    }
    if (phase == 0 && millis() - phaseStart > 600) {
      analogWrite(LED_RED_PIN, 0);
      analogWrite(LED_GREEN_PIN, 255);
      analogWrite(LED_BLUE_PIN, 0);
      Serial.println("  - Green");
      phase = 1;
      phaseStart = millis();
    } else if (phase == 1 && millis() - phaseStart > 600) {
      analogWrite(LED_RED_PIN, 0);
      analogWrite(LED_GREEN_PIN, 0);
      analogWrite(LED_BLUE_PIN, 255);
      Serial.println("  - Blue");
      phase = 2;
      phaseStart = millis();
    } else if (phase == 2 && millis() - phaseStart > 600) {
      analogWrite(LED_RED_PIN, 0);
      analogWrite(LED_GREEN_PIN, 0);
      analogWrite(LED_BLUE_PIN, 0);
      done = true;
    }
  }

  // --- Sound Sensor Test (Stub) ---
  // --- Sound Sensor Test (Non-blocking) ---
  void runSoundSensorTest(bool begin, bool &done) {
    static unsigned long start = 0;
    if (begin) {
      Serial.println("\n[TEST] LM393 Sound Sensor");
      pinMode(SOUND_SENSOR_PIN, INPUT);
      Serial.println("Digital value: 0 = quiet, 1 = sound detected");
      start = millis();
    }
    int sound = digitalRead(SOUND_SENSOR_PIN);
    Serial.print("  - Sound Detected: ");
    Serial.println(sound);
    if (millis() - start > 1000) done = true;
  }

  // --- ToF Sensor Test (Stub) ---
  // --- ToF Sensor Test (Non-blocking) ---
  #include <Wire.h>
  #include <VL53L0X.h>
  void runToFTest(bool begin, bool &done) {
    static VL53L0X tof;
    static bool tofInitialized = false;
    static unsigned long lastRead = 0;
    if (begin) {
      Serial.println("\n[TEST] VL53L0X ToF Sensor");
      if (!tofInitialized) {
        Wire.begin(I2C_SDA, I2C_SCL);
        tof.setTimeout(500);
        if (tof.init()) {
          Serial.println("✅ ToF sensor initialized successfully.");
          tofInitialized = true;
        } else {
          Serial.println("❌ ToF sensor initialization failed!");
          done = true;
          return;
        }
      }
      lastRead = millis();
    }
    if (millis() - lastRead > 300) {
      int dist = tof.readRangeSingleMillimeters();
      if (tof.timeoutOccurred()) {
        Serial.println("  - [ToF] Timeout!");
      } else {
        Serial.print("  - [ToF] Distance: ");
        Serial.print(dist);
        Serial.println(" mm");
      }
      done = true;
    }
  }

  // --- MPU6050 Test (Stub) ---
  #include <Wire.h>
  #include <MPU6050_light.h>

  // --- MPU6050 Test (Non-blocking) ---
  #include <MPU6050_light.h>
  void runMPU6050Test(bool begin, bool &done) {
    static MPU6050 mpu(Wire);
    static bool mpuInitialized = false;
    static unsigned long lastRead = 0;
    if (begin) {
      Serial.println("\n[TEST] MPU6050 IMU");
      if (!mpuInitialized) {
        Wire.begin(I2C_SDA, I2C_SCL);
        Serial.println("Initializing MPU6050 IMU...");
        byte status = mpu.begin();
        if (status == 0) {
          Serial.println("✅ IMU initialized successfully.");
          mpuInitialized = true;
        } else {
          Serial.print("❌ IMU initialization failed! Status: ");
          Serial.println(status);
          done = true;
          return;
        }
        Serial.println("Calibrating IMU...");
        mpu.calcOffsets(true, true); // gyro and accel
        Serial.println("IMU calibration complete.");
      }
      lastRead = millis();
    }
    if (millis() - lastRead > 300) {
      mpu.update();
      Serial.print("  - Angle X: "); Serial.print(mpu.getAngleX());
      Serial.print(" | Angle Y: "); Serial.print(mpu.getAngleY());
      Serial.print(" | Angle Z: "); Serial.println(mpu.getAngleZ());
      done = true;
    }
  }

  // --- WiFi Test (Stub) ---
  // --- WiFi Test (Non-blocking) ---
  void runWiFiTest(bool begin, bool &done) {
    static bool wifiInitialized = false;
    static unsigned long startAttempt = 0;
    if (begin) {
      Serial.println("\n[TEST] WiFi Connection");
      wifiInitialized = false;
      WiFi.mode(WIFI_STA);
      WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
      Serial.print("Connecting to WiFi");
      startAttempt = millis();
    }
    if (!wifiInitialized && WiFi.status() == WL_CONNECTED) {
      Serial.println();
      Serial.print("WiFi connected! IP address: ");
      Serial.println(WiFi.localIP());
      wifiInitialized = true;
      done = true;
    } else if (!wifiInitialized && millis() - startAttempt > 10000) {
      Serial.println();
      Serial.println("[ERROR] WiFi connection timed out.");
      done = true;
    } else if (!wifiInitialized) {
      if ((millis() - startAttempt) % 500 < 50) Serial.print(".");
    } else {
      Serial.print("  - IP: "); Serial.println(WiFi.localIP());
      Serial.print("  - RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm");
      done = true;
    }
  }

  // --- ESP-NOW Test (Non-blocking) ---
  void runESPNOWTest(bool begin, bool &done) {
    static bool espnowInitialized = false;
    static int sendSuccessCount = 0;
    static int sendFailCount = 0;
    static int receivedCount = 0;
    static char lastReceivedData[64] = {0};
    static uint8_t lastReceivedMac[6] = {0};
    static int lastReceivedLen = 0;
    static unsigned long lastSend = 0;
    static bool sent = false;
    static bool cbRegistered = false;
    static const char *testMsg = "Hello ESP-NOW!";
    if (begin) {
      Serial.println("\n[TEST] ESP-NOW");
      WiFi.mode(WIFI_STA);
      if (!espnowInitialized) {
        if (esp_now_init() != ESP_OK) {
          Serial.println("[ESP-NOW] Error initializing ESP-NOW");
          done = true;
          return;
        }
        espnowInitialized = true;
      }
      if (!cbRegistered) {
        esp_now_register_send_cb([](const uint8_t *mac_addr, esp_now_send_status_t status) {
          if (status == ESP_NOW_SEND_SUCCESS) {
            sendSuccessCount++;
            Serial.print("[ESP-NOW] Last Packet Send Status: Success\n");
          } else {
            sendFailCount++;
            Serial.print("[ESP-NOW] Last Packet Send Status: Fail\n");
          }
        });
        esp_now_register_recv_cb([](const uint8_t *mac_addr, const uint8_t *data, int len) {
          receivedCount++;
          int copyLen = len > 63 ? 63 : len;
          memcpy(lastReceivedData, data, copyLen);
          lastReceivedData[copyLen] = '\0';
          Serial.print("  - [ESP-NOW] Data received | Data: ");
          Serial.print(lastReceivedData);
          Serial.print(" | Length: ");
          Serial.println(copyLen);
        });
        cbRegistered = true;
      }
      esp_now_peer_info_t peerInfo = {};
      uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
      memcpy(peerInfo.peer_addr, broadcastAddress, 6);
      peerInfo.channel = 0;
      peerInfo.encrypt = false;
      esp_now_add_peer(&peerInfo);
      lastSend = millis();
      sent = false;
    }
    if (!sent && millis() - lastSend > 300) {
      uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
      esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)testMsg, strlen(testMsg));
      if (result == ESP_OK) {
        Serial.println("  - [ESP-NOW] Test message sent (broadcast)");
      } else {
        Serial.print("  - [ESP-NOW] Error sending test message: ");
        Serial.println(result);
      }
      sent = true;
      lastSend = millis();
    }
    if (sent && millis() - lastSend > 600) {
      Serial.print("  - Send Success: "); Serial.print(sendSuccessCount);
      Serial.print(" | Send Fail: "); Serial.print(sendFailCount);
      Serial.print(" | Received: "); Serial.println(receivedCount);
      done = true;
    }
  }

  // --- OTA Test (Stub) ---
  // --- OTA Test (Non-blocking, still placeholder) ---
  void runOTATest(bool begin, bool &done) {
    if (begin) {
      Serial.println("\n[TEST] OTA (Over-The-Air Update)");
      Serial.println("This is a placeholder for OTA update test.");
      Serial.println("Implement OTA logic here (e.g., ArduinoOTA, HTTPUpdate, etc.)");
      done = true;
    }
  }

  void setup() {
    Serial.begin(115200);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(IN1_PIN, OUTPUT);
    pinMode(IN2_PIN, OUTPUT);
    pinMode(IN3_PIN, OUTPUT);
    pinMode(IN4_PIN, OUTPUT);
    pinMode(ENCODER_A_PIN, INPUT_PULLUP);
    pinMode(ENCODER_B_PIN, INPUT_PULLUP);
    pinMode(LED_RED_PIN, OUTPUT);
    pinMode(LED_GREEN_PIN, OUTPUT);
    pinMode(LED_BLUE_PIN, OUTPUT);
    pinMode(SOUND_SENSOR_PIN, INPUT);
    Serial.println("[MASTER TEST] Starting hardware and connection tests...");
  }

  void loop() {
    static TestStep lastTest = TEST_DONE;
    static bool beginStep = true;
    static bool doneStep = false;
    static unsigned long lastStepEnd = 0;
    if (currentTest == TEST_DONE) {
      Serial.println("\n[MASTER TEST] All tests complete. Restart to run again.");
      while (1) { delay(1000); }
    }
    if (beginStep) {
      // Stop previous test's hardware if necessary
      stopMotors();
      analogWrite(LED_RED_PIN, 0);
      analogWrite(LED_GREEN_PIN, 0);
      analogWrite(LED_BLUE_PIN, 0);
      doneStep = false;
    }
    switch (currentTest) {
      case TEST_BUZZER:
        runBuzzerTest(beginStep, doneStep);
        break;
      case TEST_RGB_LED:
        runRGBLEDTest(beginStep, doneStep);
        break;
      case TEST_MOTORS:
        runMotorsTest(beginStep, doneStep);
        break;
      case TEST_ENCODERS:
        runEncodersTest(beginStep, doneStep);
        break;
      case TEST_SOUND_SENSOR:
        runSoundSensorTest(beginStep, doneStep);
        break;
      case TEST_TOF:
        runToFTest(beginStep, doneStep);
        break;
      case TEST_MPU6050:
        runMPU6050Test(beginStep, doneStep);
        break;
      case TEST_WIFI:
        runWiFiTest(beginStep, doneStep);
        break;
      case TEST_ESPNOW:
        runESPNOWTest(beginStep, doneStep);
        break;
      case TEST_OTA:
        runOTATest(beginStep, doneStep);
        break;
      default:
        break;
    }
    if (doneStep) {
      lastTest = currentTest;
      currentTest = (TestStep)((int)currentTest + 1);
      beginStep = true;
      lastStepEnd = millis();
      delay(10); // allow serial flush
    } else {
      if (beginStep) beginStep = false;
      // Add a small delay between steps
      if (millis() - lastStepEnd < 200) return;
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\ota_test.cpp
repo_name: wheelie
function_name: setRGB
last_modified: 2025-12-23 20:04:44.184041
content:
  // OTA-only test: RGB LED visual confirmation
  #include <Arduino.h>
  #include "pins.h"
  #include <WiFi.h>
  // Add OTA library includes here (e.g., ArduinoOTA, HTTPUpdate)

  // Helper to set RGB LED color
  void setRGB(int r, int g, int b) {
    analogWrite(LED_RED_PIN, r);
    analogWrite(LED_GREEN_PIN, g);
    analogWrite(LED_BLUE_PIN, b);
  }

  void setup() {
    Serial.begin(115200);
    pinMode(LED_RED_PIN, OUTPUT);
    pinMode(LED_GREEN_PIN, OUTPUT);
    pinMode(LED_BLUE_PIN, OUTPUT);
    // Red flash at boot
    setRGB(255, 0, 0);

    Serial.println("[OTA TEST] Starting OTA (Over-The-Air Update) test...");
    // WiFi connection setup (required for OTA)
    #include "credentials.h"
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Connecting to WiFi");
    unsigned long startAttempt = millis();
    // This initial connection is allowed to be blocking for a test script.
    while (WiFi.status() != WL_CONNECTED && millis() - startAttempt < 10000) {
      delay(500);
      Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println();
      Serial.print("WiFi connected! IP address: ");
      Serial.println(WiFi.localIP());
    } else {
      Serial.println();
      Serial.println("[ERROR] WiFi connection timed out. OTA test cannot proceed.");
      while (1) { /* Halt */ }
    }

    // --- OTA logic placeholder ---
    Serial.println("This is a placeholder for OTA update test.");
    Serial.println("Implement OTA logic here (e.g., ArduinoOTA, HTTPUpdate, etc.)");
    // Simulate OTA success: turn RGB LED green
    setRGB(0, 255, 0);
    Serial.println("[OTA TEST] RGB LED is GREEN: OTA test success indicator.");
  }

  void loop() {
    // If using ArduinoOTA or similar, call OTA handler here
    // ArduinoOTA.handle();
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_buzzer_only.cpp
repo_name: wheelie
function_name: setup
last_modified: 2025-12-23 20:04:44.185043
content:
  #include <Arduino.h>
  #include "pins.h"

  // Minimal test for KY-006 Passive Buzzer
  // Connect BUZZER_PIN to signal pin of buzzer

  void setup() {
    Serial.begin(115200);
    pinMode(BUZZER_PIN, OUTPUT);
    Serial.println("KY-006 Passive Buzzer Test");
    Serial.println("Plays tones and beeps. Listen for sound.");
  }

  int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};
  int currentNote = 0;
  unsigned long nextNoteTime = 0;
  const unsigned long noteDuration = 150;
  const unsigned long pauseDuration = 50;

  void loop() {
    if (millis() < nextNoteTime) {
      return;
    }

    if (currentNote == 0) {
      Serial.println("Playing melody...");
    }

    if (currentNote < 8) {
      tone(BUZZER_PIN, melody[currentNote], noteDuration - pauseDuration);
      currentNote++;
      nextNoteTime = millis() + noteDuration;
    } else {
      // Melody finished, wait before restarting
      currentNote = 0;
      nextNoteTime = millis() + 2000; // 2-second pause
      Serial.println("Melody complete. Restarting soon...");
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_encoder_only.cpp
repo_name: wheelie
function_name: setup
last_modified: 2025-12-23 20:04:44.187865
content:
  #include <Arduino.h>
  #include "pins.h"

  // Minimal test for LM393 H2010 encoders (manual wheel movement)
  volatile long leftCount = 0;
  volatile long rightCount = 0;
  volatile int8_t leftDir = 0;   // +1 or -1
  volatile int8_t rightDir = 0;
  volatile unsigned long leftLastTime = 0;
  volatile unsigned long rightLastTime = 0;

  // For direction: sample B pin on A edge (simple, not quadrature)
  void IRAM_ATTR onLeftEncoder() {
    int b = digitalRead(ENCODER_B_PIN);
    leftDir = b ? 1 : -1;
    leftCount += leftDir;
    leftLastTime = micros();
  }

  void IRAM_ATTR onRightEncoder() {
    int a = digitalRead(ENCODER_A_PIN);
    rightDir = a ? 1 : -1;
    rightCount += rightDir;
    rightLastTime = micros();
  }

  void setup() {
    Serial.begin(115200);
    pinMode(ENCODER_A_PIN, INPUT_PULLUP);
    pinMode(ENCODER_B_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), onLeftEncoder, RISING);
    attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), onRightEncoder, RISING);
    Serial.println("LM393 H2010 Encoder Test");
    Serial.println("Move wheels by hand. Counts, direction, and timing will be displayed.");
    Serial.println("Debug: Stuck detection every 2s, direction: +1=forward, -1=reverse");
  }

  void loop() {
    static long lastLeft = 0, lastRight = 0;
    static unsigned long lastPrint = 0;
    static unsigned long lastCheck = 0;
    static unsigned long lastLeftTime = 0, lastRightTime = 0;
    static int stuckWarned = 0;

    // Print on change or every 500ms
    if (leftCount != lastLeft || rightCount != lastRight || millis() - lastPrint > 500) {
      noInterrupts();
      long l = leftCount, r = rightCount;
      int8_t ld = leftDir, rd = rightDir;
      unsigned long lTime = leftLastTime, rTime = rightLastTime;
      interrupts();
      Serial.print("Left: "); Serial.print(l);
      Serial.print(" (dir: "); Serial.print(ld); Serial.print(", dt: ");
      Serial.print(lTime - lastLeftTime); Serial.print("us) | ");
      Serial.print("Right: "); Serial.print(r);
      Serial.print(" (dir: "); Serial.print(rd); Serial.print(", dt: ");
      Serial.print(rTime - lastRightTime); Serial.println("us)");
      lastLeft = l; lastRight = r;
      lastLeftTime = lTime; lastRightTime = rTime;
      lastPrint = millis();
      stuckWarned = 0;
    }

    // Fallback: stuck detection (no change for 2s)
    if ((millis() - lastCheck > 2000) && !stuckWarned) {
      if (leftCount == lastLeft && rightCount == lastRight) {
        Serial.println("Warning: Encoder counts stuck for 2 seconds!");
        stuckWarned = 1;
      }
      lastCheck = millis();
    }
    // No delay needed, loop runs continuously
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_motors_and_encoders.cpp
repo_name: wheelie
function_name: setLeftMotor
last_modified: 2025-12-23 20:04:44.188869
content:
  #include <Arduino.h>
  #include "pins.h"

  // Combined test for motors and encoders
  // Runs each motor in sequence and prints encoder counts, direction, and timing live

  // Motor control
  void setLeftMotor(int pwmA, int pwmB) {
    analogWrite(IN1_PIN, pwmA);
    analogWrite(IN2_PIN, pwmB);
  }
  void setRightMotor(int pwmA, int pwmB) {
    analogWrite(IN3_PIN, pwmA);
    analogWrite(IN4_PIN, pwmB);
  }
  void stopMotors() {
    setLeftMotor(0, 0);
    setRightMotor(0, 0);
  }

  // Encoder variables
  volatile long leftCount = 0;
  volatile long rightCount = 0;
  volatile int8_t leftDir = 0;   // +1 or -1
  volatile int8_t rightDir = 0;
  volatile unsigned long leftLastTime = 0;
  volatile unsigned long rightLastTime = 0;

  void IRAM_ATTR onLeftEncoder() {
    int b = digitalRead(ENCODER_B_PIN);
    leftDir = b ? 1 : -1;
    leftCount += leftDir;
    leftLastTime = micros();
  }
  void IRAM_ATTR onRightEncoder() {
    int a = digitalRead(ENCODER_A_PIN);
    rightDir = a ? 1 : -1;
    rightCount += rightDir;
    rightLastTime = micros();
  }

  void setup() {
    Serial.begin(115200);
    pinMode(IN1_PIN, OUTPUT);
    pinMode(IN2_PIN, OUTPUT);
    pinMode(IN3_PIN, OUTPUT);
    pinMode(IN4_PIN, OUTPUT);
    stopMotors();
    pinMode(ENCODER_A_PIN, INPUT_PULLUP);
    pinMode(ENCODER_B_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), onLeftEncoder, RISING);
    attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), onRightEncoder, RISING);
    Serial.println("Motor + Encoder Test");
    Serial.println("Motors will run in sequence. Encoder counts, direction, and timing will be displayed live.");
  }

  void printEncoders() {
    static long lastLeft = 0, lastRight = 0;
    static unsigned long lastPrint = 0;
    static unsigned long lastLeftTime = 0, lastRightTime = 0;
    noInterrupts();
    long l = leftCount, r = rightCount;
    int8_t ld = leftDir, rd = rightDir;
    unsigned long lTime = leftLastTime, rTime = rightLastTime;
    interrupts();
    if (l != lastLeft || r != lastRight || millis() - lastPrint > 300) {
      Serial.print("Left: "); Serial.print(l);
      Serial.print(" (dir: "); Serial.print(ld); Serial.print(", dt: ");
      Serial.print(lTime - lastLeftTime); Serial.print("us) | ");
      Serial.print("Right: "); Serial.print(r);
      Serial.print(" (dir: "); Serial.print(rd); Serial.print(", dt: ");
      Serial.print(rTime - lastRightTime); Serial.println("us)");
      lastLeft = l; lastRight = r;
      lastLeftTime = lTime; lastRightTime = rTime;
      lastPrint = millis();
    }
  }

  void leftForward() { setLeftMotor(200, 0); setRightMotor(0, 0); }
  void leftReverse() { setLeftMotor(0, 200); setRightMotor(0, 0); }
  void rightForward() { setLeftMotor(0, 0); setRightMotor(200, 0); }
  void rightReverse() { setLeftMotor(0, 0); setRightMotor(0, 200); }
  void bothForward() { setLeftMotor(200, 0); setRightMotor(200, 0); }
  void bothReverse() { setLeftMotor(0, 200); setRightMotor(0, 200); }

  enum MotorEncoderTestStep {
    ME_LEFT_FWD, ME_LEFT_REV, ME_RIGHT_FWD, ME_RIGHT_REV, ME_BOTH_FWD, ME_BOTH_REV, ME_PAUSE
  };
  MotorEncoderTestStep me_currentStep = ME_LEFT_FWD;
  unsigned long me_nextActionTime = 0;
  const unsigned long me_runDuration = 1500;
  const unsigned long me_pauseDuration = 500;

  void loop() {
    if (millis() >= me_nextActionTime) {
      stopMotors();
      me_currentStep = (MotorEncoderTestStep)(((int)me_currentStep + 1) % 7);
      me_nextActionTime = millis() + (me_currentStep == ME_PAUSE ? me_pauseDuration : me_runDuration);

      if(me_currentStep == ME_LEFT_FWD) Serial.println("\n--- Left Fwd ---");
      if(me_currentStep == ME_LEFT_REV) Serial.println("\n--- Left Rev ---");
      if(me_currentStep == ME_RIGHT_FWD) Serial.println("\n--- Right Fwd ---");
      if(me_currentStep == ME_RIGHT_REV) Serial.println("\n--- Right Rev ---");
      if(me_currentStep == ME_BOTH_FWD) Serial.println("\n--- Both Fwd ---");
      if(me_currentStep == ME_BOTH_REV) Serial.println("\n--- Both Rev ---");
    }

    if(me_currentStep == ME_LEFT_FWD) leftForward();
    if(me_currentStep == ME_LEFT_REV) leftReverse();
    if(me_currentStep == ME_RIGHT_FWD) rightForward();
    if(me_currentStep == ME_RIGHT_REV) rightReverse();
    if(me_currentStep == ME_BOTH_FWD) bothForward();
    if(me_currentStep == ME_BOTH_REV) bothReverse();

    printEncoders();
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_motors_only.cpp
repo_name: wheelie
function_name: setLeftMotor
last_modified: 2025-12-23 20:04:44.190621
content:
  #include <Arduino.h>
  #include "pins.h"

  // Minimal test for dual motor driver (FASIZI L298N or MOSFET H-Bridge)
  // Cycles each motor forward, reverse, and stop at different speeds

  void setLeftMotor(int pwmA, int pwmB) {
    analogWrite(IN1_PIN, pwmA);
    analogWrite(IN2_PIN, pwmB);
  }

  void setRightMotor(int pwmA, int pwmB) {
    analogWrite(IN3_PIN, pwmA);
    analogWrite(IN4_PIN, pwmB);
  }

  void stopMotors() {
    setLeftMotor(0, 0);
    setRightMotor(0, 0);
  }

  void setup() {
    Serial.begin(115200);
    pinMode(IN1_PIN, OUTPUT);
    pinMode(IN2_PIN, OUTPUT);
    pinMode(IN3_PIN, OUTPUT);
    pinMode(IN4_PIN, OUTPUT);
    stopMotors();
    Serial.println("Motor Driver Test");
    Serial.println("Each motor will run forward, reverse, and stop. Observe wheels.");
  }

  enum MotorTestStep {
    LEFT_FWD, LEFT_REV, RIGHT_FWD, RIGHT_REV, BOTH_FWD, BOTH_REV, PAUSE, DONE
  };
  MotorTestStep currentStep = LEFT_FWD;
  unsigned long nextActionTime = 0;
  const unsigned long runDuration = 1500;
  const unsigned long pauseDuration = 500;

  void loop() {
    if (millis() < nextActionTime) {
      return; // Not time yet
    }

    stopMotors();
    nextActionTime = millis() + (currentStep == PAUSE ? pauseDuration : runDuration);

    switch (currentStep) {
      case LEFT_FWD:
        Serial.println("Left motor forward");
        setLeftMotor(200, 0);
        currentStep = PAUSE;
        break;
      case LEFT_REV:
        Serial.println("Left motor reverse");
        setLeftMotor(0, 200);
        currentStep = PAUSE;
        break;
      case RIGHT_FWD:
        Serial.println("Right motor forward");
        setRightMotor(200, 0);
        currentStep = PAUSE;
        break;
      case RIGHT_REV:
        Serial.println("Right motor reverse");
        setRightMotor(0, 200);
        currentStep = PAUSE;
        break;
      case BOTH_FWD:
        Serial.println("Both motors forward");
        setLeftMotor(200, 0); setRightMotor(200, 0);
        currentStep = PAUSE;
        break;
      case BOTH_REV:
        Serial.println("Both motors reverse");
        setLeftMotor(0, 200); setRightMotor(0, 200);
        currentStep = PAUSE;
        break;
      case PAUSE:
        currentStep = (MotorTestStep)((int)currentStep + 1);
        break;
      case DONE:
        Serial.println("Test complete. Restarting...");
        currentStep = LEFT_FWD;
        nextActionTime = millis() + 2000; // Longer pause before restart
        break;
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_mpu6050_only.cpp
repo_name: wheelie
function_name: setup
last_modified: 2025-12-23 20:04:44.192812
content:
  #include <Arduino.h>
  #include <Wire.h>
  #include "pins.h"
  #include <MPU6050_light.h>
  #include <VL53L0X.h>

  MPU6050 mpu(Wire);
  VL53L0X tof;

  void setup() {
    // Initialize ToF sensor
    Serial.println("Initializing VL53L0X ToF sensor...");
    tof.setTimeout(500);
    if (tof.init()) {
      Serial.println("✅ ToF sensor initialized successfully.");
    } else {
      Serial.println("❌ ToF sensor initialization failed!");
    }

    // Initialize I2C with correct pins from pins.h
    Wire.begin(I2C_SDA, I2C_SCL);

    Serial.println("MPU6050 IMU Test - Only IMU enabled");

    // Initialize MPU6050
    byte status = mpu.begin();
    if (status == 0) {
      Serial.println("✅ IMU initialized successfully.");
    } else {
      Serial.print("❌ IMU initialization failed! Status: ");
      Serial.println(status);
      while (1) delay(1000); // Halt if IMU fails
    }

    Serial.println("Calibrating IMU...");
    mpu.calcOffsets(true, true); // gyro and accel
    Serial.println("IMU calibration complete.");
    Serial.println("Type 'c' and press Enter to recalibrate IMU at any time.");
  }

  unsigned long lastPrintTime = 0;
  const unsigned long printInterval = 500;

  void loop() {
    if (millis() - lastPrintTime >= printInterval) {
      mpu.update();
      Serial.print("IMU: TiltX=");
      Serial.print(mpu.getAngleX());
      Serial.print("°, TiltY=");
      Serial.print(mpu.getAngleY());
      Serial.print("°, Heading=");
      Serial.print(mpu.getAngleZ());
      Serial.print("°");

      // ToF sensor reading
      Serial.print(" | ToF: ");
      uint16_t distance = tof.readRangeSingleMillimeters();
      if (tof.timeoutOccurred()) {
        Serial.print("Timeout");
      } else {
        Serial.print(distance);
        Serial.print(" mm");
      }
      Serial.println("");

      lastPrintTime = millis();
    }

    if (Serial.available()) {
      char cmd = Serial.read();
      if (cmd == 'c' || cmd == 'C') {
        Serial.println("\nRecalibrating IMU... Keep device still.");
        mpu.calcOffsets(true, true);
        Serial.println("IMU recalibration complete.");
      }
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_rgb_led_only.cpp
repo_name: wheelie
function_name: setColor
last_modified: 2025-12-23 20:04:44.194446
content:
  #include <Arduino.h>
  #include "pins.h"

  // Minimal test for KY-009 RGB LED module
  // Connect LED_RED_PIN, LED_GREEN_PIN, LED_BLUE_PIN to RGB LED

  void setColor(int r, int g, int b) {
    analogWrite(LED_RED_PIN, r);
    analogWrite(LED_GREEN_PIN, g);
    analogWrite(LED_BLUE_PIN, b);
  }

  void setup() {
    Serial.begin(115200);
    pinMode(LED_RED_PIN, OUTPUT);
    pinMode(LED_GREEN_PIN, OUTPUT);
    pinMode(LED_BLUE_PIN, OUTPUT);
    Serial.println("KY-009 RGB LED Test");
    Serial.println("Cycling through colors. Observe the LED.");
  }

  enum ColorStep { RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA, WHITE, OFF };
  ColorStep currentColorStep = RED;
  unsigned long nextColorTime = 0;
  const unsigned long colorInterval = 1000; // 1 second per color

  void loop() {
    if (millis() < nextColorTime) {
      return;
    }

    nextColorTime = millis() + colorInterval;

    switch (currentColorStep) {
      case RED:
        Serial.println("Red"); setColor(255, 0, 0);
        break;
      case GREEN:
        Serial.println("Green"); setColor(0, 255, 0);
        break;
      case BLUE:
        Serial.println("Blue"); setColor(0, 0, 255);
        break;
      case YELLOW:
        Serial.println("Yellow"); setColor(255, 255, 0);
        break;
      case CYAN:
        Serial.println("Cyan"); setColor(0, 255, 255);
        break;
      case MAGENTA:
        Serial.println("Magenta"); setColor(255, 0, 255);
        break;
      case WHITE:
        Serial.println("White"); setColor(255, 255, 255);
        break;
      case OFF:
        Serial.println("Off"); setColor(0, 0, 0);
        break;
    }

    currentColorStep = (ColorStep)(((int)currentColorStep + 1) % 8);
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_sound_sensor_only.cpp
repo_name: wheelie
function_name: setup
last_modified: 2025-12-23 20:04:44.195634
content:
  #include <Arduino.h>
  #include "pins.h"

  // LM393 Sound Sensor Test
  // Connect OUT pin of sensor to SOUND_SENSOR_PIN (see pins.h)

  void setup() {
    Serial.begin(115200);
    pinMode(SOUND_SENSOR_PIN, INPUT);
    Serial.println("LM393 Sound Sensor Test - Only Sound Sensor enabled");
    Serial.println("Digital value: 0 = quiet, 1 = sound detected");
  }

  unsigned long lastCheck = 0;
  const unsigned long checkInterval = 200;

  void loop() {
    if (millis() - lastCheck >= checkInterval) {
      int sound = digitalRead(SOUND_SENSOR_PIN);
      Serial.print("Sound Detected: ");
      Serial.println(sound);
      lastCheck = millis();
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_tof_only.cpp
repo_name: wheelie
function_name: calibrateToF
last_modified: 2025-12-23 20:04:44.197854
content:
  #include <Arduino.h>
  #include <Wire.h>
  #include "pins.h"
  #include <VL53L0X.h>

  VL53L0X tof;

  int16_t tof_offset = 0; // Calibration offset
  const int AVERAGE_SAMPLES = 8;

  // State for non-blocking calibration
  enum CalibState { NOT_CALIBRATING, WAITING_FOR_DIST, SAMPLING };
  CalibState calibState = NOT_CALIBRATING;
  int refDist = 0;
  long calibSum = 0;
  int calibSampleCount = 0;
  unsigned long nextCalibSampleTime = 0;

  void calibrateToF() {
    if (calibState == NOT_CALIBRATING) {
      Serial.println("\n--- ToF Calibration ---");
      Serial.println("1. Place sensor at a known reference distance.");
      Serial.println("2. Type the true distance in mm, then press Enter.");
      calibState = WAITING_FOR_DIST;
    }
    if (calibState == WAITING_FOR_DIST && Serial.available()) {
      refDist = Serial.parseInt();
      Serial.printf("Reference distance entered: %d mm. Now sampling...\n", refDist);
      calibSum = 0;
      calibSampleCount = 0;
      calibState = SAMPLING;
      nextCalibSampleTime = millis();
    }
    if (calibState == SAMPLING && millis() >= nextCalibSampleTime) {
      if (calibSampleCount < AVERAGE_SAMPLES) {
        calibSum += tof.readRangeSingleMillimeters();
        calibSampleCount++;
        nextCalibSampleTime = millis() + 50; // 50ms between samples
      } else {
        int measured = calibSum / AVERAGE_SAMPLES;
        tof_offset = measured - refDist;
        Serial.printf("Calibration complete. Measured avg: %d mm. Offset set to: %d\n", measured, tof_offset);
        calibState = NOT_CALIBRATING;
      }
    }
  }

  void setup() {
    Serial.begin(115200);

    // Initialize I2C with correct pins from pins.h
    Wire.begin(I2C_SDA, I2C_SCL);

    Serial.println("VL53L0X ToF Test - Only ToF enabled");

    // Initialize ToF sensor
    tof.setTimeout(500);
    if (tof.init()) {
      Serial.println("✅ ToF sensor initialized successfully.");
    } else {
      Serial.println("❌ ToF sensor initialization failed!");
      while (1) delay(1000); // Halt if ToF fails
    }
    Serial.println("Type 'c' and Enter to calibrate ToF offset at any time.");
  }

  unsigned long lastReadingTime = 0;
  const unsigned long readingInterval = 500;

  void loop() {
    // Serial-triggered calibration
    if (Serial.available()) {
      char cmd = Serial.read();
      if (cmd == 'c' || cmd == 'C') {
        calibState = NOT_CALIBRATING; // Reset and start calibration process
        // Clear any extra input
        while (Serial.available()) Serial.read();
      }
    }

    if (millis() - lastReadingTime >= readingInterval) {
      // Non-blocking averaging
      static int sampleCount = 0;
      static long sampleSum = 0;
      static int validCount = 0;

      uint16_t d = tof.readRangeSingleMillimeters();
      if (!tof.timeoutOccurred() && d > 0 && d < 2000) { // 2m max reasonable
        sampleSum += d;
        validCount++;
      }
      sampleCount++;

      if (sampleCount >= AVERAGE_SAMPLES) {
        Serial.print("ToF: ");
        if (validCount == 0) {
          Serial.println("No valid readings");
        } else {
          int avg = sampleSum / validCount;
          int calibrated = avg - tof_offset;
          Serial.printf("avg=%d mm, calibrated=%d mm (%d/%d valid samples)\n", avg, calibrated, validCount, AVERAGE_SAMPLES);
        }
        // Reset for next burst
        sampleCount = 0;
        sampleSum = 0;
        validCount = 0;
        lastReadingTime = millis();
      }
    }

    // Handle the interactive calibration state machine
    if (calibState != NOT_CALIBRATING) {
      calibrateToF();
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_ultrasonic_led.cpp
repo_name: wheelie
function_name: setup
last_modified: 2025-12-23 20:04:44.199865
content:
  #include <Arduino.h>

  // Pin definitions from your request
  #define FRONT_ULTRASONIC_TRIG_PIN 16
  #define FRONT_ULTRASONIC_ECHO_PIN 32

  // Speed of sound in cm/microsecond
  #define SOUND_SPEED 0.0343

  // --- Internal State for Ultrasonic Filtering ---
  const int ULTRASONIC_FILTER_SIZE = 5;
  float ultrasonicReadings[ULTRASONIC_FILTER_SIZE] = {0};
  int ultrasonicReadingIndex = 0;
  bool ultrasonicFilterPrimed = false;

  // --- Internal State for Non-Blocking Ultrasonic ---
  enum UltrasonicState { US_IDLE, US_TRIGGERED, US_ECHO_IN_PROGRESS };
  UltrasonicState ultrasonicState = US_IDLE;
  unsigned long usTriggerTime = 0;
  unsigned long usEchoStartTime = 0;
  const unsigned long US_TIMEOUT_US = 38000;

  void setup() {
    Serial.begin(115200);
    Serial.println("\n--- HC-SR04 Basic pulseIn Test ---");
    
    // Set pin modes
    pinMode(FRONT_ULTRASONIC_TRIG_PIN, OUTPUT);
    pinMode(FRONT_ULTRASONIC_ECHO_PIN, INPUT);
  }

  unsigned long nextReadingTime = 0;
  const unsigned long readingInterval = 100; // ms

  void loop() {
    unsigned long currentMicros = micros();

    // State 1: Time to start a new reading
    if (ultrasonicState == US_IDLE && millis() >= nextReadingTime) {
        digitalWrite(FRONT_ULTRASONIC_TRIG_PIN, HIGH);
        usTriggerTime = currentMicros;
        ultrasonicState = US_TRIGGERED;
    }

    // State 2: End the trigger pulse after 10us
    if (ultrasonicState == US_TRIGGERED && currentMicros - usTriggerTime >= 10) {
        digitalWrite(FRONT_ULTRASONIC_TRIG_PIN, LOW);
        ultrasonicState = US_ECHO_IN_PROGRESS;
        usEchoStartTime = currentMicros; // Start timeout timer
    }

    // State 3: Wait for the echo pulse to finish or time out
    if (ultrasonicState == US_ECHO_IN_PROGRESS) {
        if (digitalRead(FRONT_ULTRASONIC_ECHO_PIN) == LOW) {
            long duration_us = currentMicros - usEchoStartTime;
            if (duration_us > 100) { // Ignore noise
                float newReading = (duration_us * SOUND_SPEED) / 2.0;
                if (newReading > 2.0) {
                    ultrasonicReadings[ultrasonicReadingIndex] = newReading;
                    ultrasonicReadingIndex = (ultrasonicReadingIndex + 1) % ULTRASONIC_FILTER_SIZE;
                    if (!ultrasonicFilterPrimed && ultrasonicReadingIndex == 0) {
                        ultrasonicFilterPrimed = true;
                    }
                }
            }
            ultrasonicState = US_IDLE;
            nextReadingTime = millis() + readingInterval;
        } else if (currentMicros - usEchoStartTime > US_TIMEOUT_US) {
            // Timeout
            ultrasonicState = US_IDLE;
            nextReadingTime = millis() + readingInterval;
        }
    }

    // Print the result periodically
    static unsigned long lastPrint = 0;
    if (millis() - lastPrint > readingInterval) {
      float total = 0;
      int numReadings = ultrasonicFilterPrimed ? ULTRASONIC_FILTER_SIZE : ultrasonicReadingIndex;
      if (numReadings > 0) {
          for (int i = 0; i < numReadings; i++) {
              total += ultrasonicReadings[i];
          }
          float averageDistance = total / numReadings;
          Serial.printf("Filtered Distance: %.2f cm\n", averageDistance);
      }
      lastPrint = millis();
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\test_vl53l0x_tof.cpp
repo_name: wheelie
function_name: printMainMenu
last_modified: 2025-12-23 20:04:44.202429
content:
  #include <Arduino.h>
  #include <Wire.h>
  #include "pins.h"
  #include <VL53L0X.h>

  VL53L0X tof;

  // ============================================================================
  // CONFIGURATION
  // ============================================================================

  #define SERIAL_BAUD 115200
  #define I2C_CLOCK 100000  // 100kHz for reliability

  // Calibration tracking
  struct CalibrationData {
    int16_t offsetMm = 0;
    float crossTalkMcps = 0;
    bool isOffsetCalibrated = false;
    bool isCrossTalkCalibrated = false;
    unsigned long timestamp = 0;
    
    // Calibration quality metrics
    float stdDevMm = 0;
    int sampleCount = 0;
    float minReading = 0;
    float maxReading = 0;
    float avgReading = 0;
  };

  CalibrationData calibration;

  // Performance tracking
  struct PerformanceMetrics {
    unsigned long totalMeasurements = 0;
    unsigned long successfulMeasurements = 0;
    unsigned long timeouts = 0;
    unsigned long outOfRange = 0;
    unsigned long startTime = 0;
    
    float minDistance = 9999;
    float maxDistance = 0;
    float totalDistance = 0;
    
    // Timing statistics
    unsigned long minMeasurementTime = 9999;
    unsigned long maxMeasurementTime = 0;
    unsigned long totalMeasurementTime = 0;
  };

  PerformanceMetrics metrics;

  // Test modes
  enum TestMode {
    MODE_LIVE_DISTANCE,
    MODE_HISTOGRAM,
    MODE_CONTINUOUS_FAST,
    MODE_HIGH_ACCURACY,
    MODE_RANGE_PROFILE
  };

  TestMode currentMode = MODE_LIVE_DISTANCE;

  // Measurement timing budgets (in microseconds)
  const uint32_t TIMING_BUDGET_FAST = 20000;      // 20ms - fast but less accurate
  const uint32_t TIMING_BUDGET_BALANCED = 33000;  // 33ms - balanced (default)
  const uint32_t TIMING_BUDGET_ACCURATE = 200000; // 200ms - slow but accurate

  // Histogram for distance distribution
  const int HISTOGRAM_BINS = 20;
  const int HISTOGRAM_RANGE_MM = 2000; // 0-2000mm
  int histogram[HISTOGRAM_BINS] = {0};

  // ============================================================================
  // SETUP
  // ============================================================================

  // Forward declaration for printMainMenu
  void printMainMenu();

  void setup() {
    Serial.begin(SERIAL_BAUD);
    
    // Wait for serial with timeout
    unsigned long startTime = millis();
    while (!Serial && millis() - startTime < 3000) {
      delay(10);
    }
    
    printWelcomeBanner();
    
    // Initialize I2C
    initializeI2C();
    
    // Scan I2C bus
    scanI2CBus();
    
    // Initialize VL53L0X
    initializeVL53L0X();
    
    // Configure VL53L0X
    configureVL53L0X();
    
    // Perform initial offset calibration
    Serial.println("\n⚠ Note: Offset calibration recommended for best accuracy");
    Serial.println("  Press 'c' in menu to perform calibration");
    
    // Initialize metrics
    metrics.startTime = millis();
    
    // Show menu
    printMainMenu();
    
    Serial.println("\n📊 Starting live distance monitoring...");
    Serial.println("Press 'h' for command menu\n");
  }

  // ============================================================================
  // INITIALIZATION FUNCTIONS
  // ============================================================================

  String repeatChar(char c, int count) {
    String result = "";
    for (int i = 0; i < count; i++) {
      result += c;
    }
    return result;
  }

  void printWelcomeBanner() {
    Serial.println("\n" + repeatChar('═', 70));
    Serial.println("      VL53L0X COMPREHENSIVE TEST & CALIBRATION SYSTEM");
    Serial.println(repeatChar('═', 70));
    Serial.println("  Purpose: Complete testing suite for VL53L0X ToF sensor");
    Serial.println("  Features: Calibration, accuracy testing, performance analysis");
    Serial.println("  Project: WHEELIE Robot Obstacle Detection System");
    Serial.println(repeatChar('═', 70));
  }

  void initializeI2C() {
    Serial.println("\n[STEP 1] Initializing I2C Bus");
    Serial.println(repeatChar('─', 70));
    
    Wire.begin(I2C_SDA, I2C_SCL);
    Wire.setClock(I2C_CLOCK);
    
    Serial.print("  SDA Pin: GPIO ");
    Serial.println(I2C_SDA);
    Serial.print("  SCL Pin: GPIO ");
    Serial.println(I2C_SCL);
    Serial.print("  Clock Speed: ");
    Serial.print(I2C_CLOCK / 1000);
    Serial.println(" kHz");
    
    delay(100); // Bus stabilization
    
    Serial.println("  ✓ I2C bus initialized successfully");
  }

  void scanI2CBus() {
    Serial.println("\n[STEP 2] Scanning I2C Bus");
    Serial.println(repeatChar('─', 70));
    
    bool vl53Found = false;
    int deviceCount = 0;
    
    Serial.println("  Scanning addresses 0x08 to 0x77...\n");
    
    for (uint8_t addr = 0x08; addr < 0x78; addr++) {
      Wire.beginTransmission(addr);
      uint8_t error = Wire.endTransmission();
      
      if (error == 0) {
        Serial.print("  ✓ Device found at 0x");
        if (addr < 0x10) Serial.print("0");
        Serial.print(addr, HEX);
        
        if (addr == 0x29) {
          Serial.println(" ← VL53L0X ToF Sensor");
          vl53Found = true;
        } else {
          Serial.println();
        }
        
        deviceCount++;
      }
    }
    
    Serial.println();
    Serial.print("  Total devices found: ");
    Serial.println(deviceCount);
    
    if (!vl53Found) {
      Serial.println("\n  ✗ ERROR: VL53L0X not detected!");
      Serial.println("  → Check power (VCC=2.6-3.5V, GND)");
      Serial.println("  → Check I2C connections (SDA, SCL)");
      Serial.println("  → Verify XSHUT pin is HIGH or floating");
      Serial.println("  → Check GPIO0/GPIO1 not interfering");
      while (1) delay(1000);
    }
    
    Serial.println("  ✓ VL53L0X detected successfully");
  }

  void initializeVL53L0X() {
    Serial.println("\n[STEP 3] Initializing VL53L0X");
    Serial.println(String('─') * 70);
    
    tof.setTimeout(500);
    
    if (!tof.init()) {
      Serial.println("  ✗ ERROR: VL53L0X initialization failed!");
      Serial.println("\n  Possible causes:");
      Serial.println("  → Device not responding properly");
      Serial.println("  → Incorrect I2C communication");
      Serial.println("  → Device in unexpected state");
      Serial.println("  → Power supply issues");
      while (1) delay(1000);
    }
    
    Serial.println("  ✓ VL53L0X initialized successfully");
    
    // Read device information
    Serial.println("\n  Device Information:");
    Serial.println("  " + String('─') * 66);
    
    // Read model ID
    uint8_t modelId = readRegister(0xC0);
    Serial.print("    Model ID: 0x");
    if (modelId < 0x10) Serial.print("0");
    Serial.print(modelId, HEX);
    if (modelId == 0xEE) {
      Serial.println(" ✓ (VL53L0X verified)");
    } else {
      Serial.println(" ⚠ (Unexpected value)");
    }
    
    // Read module type
    uint8_t moduleType = readRegister(0xC1);
    Serial.print("    Module Type: 0x");
    if (moduleType < 0x10) Serial.print("0");
    Serial.println(moduleType, HEX);
    
    // Read revision ID
    uint8_t revisionId = readRegister(0xC2);
    Serial.print("    Revision ID: 0x");
    if (revisionId < 0x10) Serial.print("0");
    Serial.println(revisionId, HEX);
    
    Serial.println("\n  Sensor Specifications:");
    Serial.println("  " + String('─') * 66);
    Serial.println("    Technology: Time-of-Flight (ToF) laser ranging");
    Serial.println("    Range: 30mm - 2000mm (3cm - 2m)");
    Serial.println("    Field of View: 25° cone");
    Serial.println("    Wavelength: 940nm (infrared)");
    Serial.println("    Accuracy: ±3% (typ), ±5% (max)");
  }

  void configureVL53L0X() {
    Serial.println("\n[STEP 4] Configuring VL53L0X");
    Serial.println(String('─') * 70);
    
    // Set timing budget (default: 33ms for balanced performance)
    tof.setMeasurementTimingBudget(TIMING_BUDGET_BALANCED);
    
    Serial.println("  Configuration Applied:");
    Serial.println("  " + String('─') * 66);
    Serial.println("    Timing Budget: 33ms (~30Hz)");
    Serial.println("    Mode: Single-shot ranging");
    Serial.println("    Signal Rate Limit: Default");
    Serial.println("    Sigma Estimator: Default");
    
    Serial.println("\n  Available Timing Budgets:");
    Serial.println("    • 20ms (Fast) - Less accurate, ~50Hz");
    Serial.println("    • 33ms (Balanced) - Good accuracy, ~30Hz ← Current");
    Serial.println("    • 200ms (Accurate) - Best accuracy, ~5Hz");
    
    Serial.println("\n  ✓ Configuration complete");
  }

  uint8_t readRegister(uint8_t reg) {
    Wire.beginTransmission(0x29);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom((uint8_t)0x29, (uint8_t)1);
    
    if (Wire.available()) {
      return Wire.read();
    }
    Serial.println("\n" + repeatChar('═', 70));
    Serial.println("VL53L0X OFFSET CALIBRATION");
    Serial.println(repeatChar('═', 70));
  // ============================================================================
  // CALIBRATION FUNCTIONS
  // ============================================================================

  void performOffsetCalibration() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("VL53L0X OFFSET CALIBRATION");
    Serial.println(String('═') * 70);

    Serial.println("\n🎯 SETUP REQUIREMENTS:");
    Serial.println("  1. WHITE, FLAT target (paper or cardboard)");
    Serial.println("  2. Target EXACTLY at known distance");
    Serial.println("  3. Target PERPENDICULAR to sensor");
    Serial.println("  4. No other objects in field of view (25° cone)");
    Serial.println("  5. Normal room lighting (not direct sunlight)");
    Serial.println("  6. Stable mounting - no vibration");

    Serial.println("\n📏 Recommended Calibration Distances:");
    Serial.println("  • 100mm - Best for close-range work (recommended)");
    Serial.println("  • 200mm - Good for medium-range");
    Serial.println("  • 400mm - For long-range applications");

    Serial.print("\nEnter actual target distance (mm, 50-1000) [100]: ");
    int actualDistanceMm = readIntFromSerial(50, 1000, 100);

    Serial.println("\n📝 Calibration Setup Checklist:");
    Serial.println("  [ ] White target positioned at " + String(actualDistanceMm) + "mm");
    Serial.println("  [ ] Target perpendicular to sensor");
    Serial.println("  [ ] Area behind target is clear");
    Serial.println("  [ ] Stable mounting (no movement)");
    Serial.println("  [ ] Normal indoor lighting");

    Serial.println("\nPress any key when ready to start calibration...");
    waitForSerialInput();

    // The rest of the function implementation should follow here.
    // Make sure readIntFromSerial and waitForSerialInput are implemented above this function.
  }
    
    // Configuration for calibration
    const int numSamples = 100;
    int validSamples = 0;
    long sumDistances = 0;
    float distances[numSamples];
    
    Serial.println("\n⏱ Collecting " + String(numSamples) + " measurements...");
    Serial.println("(Keep target still and stable)\n");
    
    unsigned long startTime = millis();
    
    for (int i = 0; i < numSamples; i++) {
      uint16_t distance = tof.readRangeSingleMillimeters();
      
      if (!tof.timeoutOccurred() && distance < 8190) {
        distances[validSamples] = distance;
        sumDistances += distance;
        validSamples++;
        
        // Progress indicator
        if ((i + 1) % 10 == 0) {
          Serial.print("  Progress: ");
          Serial.print(((i + 1) * 100) / numSamples);
          Serial.print("% | Last reading: ");
          Serial.print(distance);
          Serial.println("mm");
        }
      } else {
        Serial.print("  Sample ");
        Serial.print(i + 1);
        Serial.println(": Failed (timeout or out of range)");
      }
      
      delay(50); // 20Hz sampling
    }
    
    unsigned long duration = millis() - startTime;
    
    // Check if we have enough valid samples
    if (validSamples < numSamples * 0.8) {
      Serial.println("\n✗ CALIBRATION FAILED");
      Serial.println("  Too many failed measurements (" + String(validSamples) + "/" + String(numSamples) + ")");
      Serial.println("\n  Possible causes:");
      Serial.println("  • Target too far or too close");
      Serial.println("  • Target not in field of view");
      Serial.println("  • Target material too dark/reflective");
      Serial.println("  • Ambient light too bright");
      Serial.println("  • Sensor malfunction");
      return;
    }
    
    // Calculate statistics
    float avgMeasured = (float)sumDistances / validSamples;
    
    // Calculate standard deviation
    float variance = 0;
    for (int i = 0; i < validSamples; i++) {
      float diff = distances[i] - avgMeasured;
      variance += diff * diff;
    }
    variance /= validSamples;
    float stdDev = sqrt(variance);
    
    // Find min/max
    float minDist = 9999;
    float maxDist = 0;
    for (int i = 0; i < validSamples; i++) {
    Serial.println("\n" + repeatChar('═', 70));
    Serial.println("OFFSET CALIBRATION RESULTS");
    Serial.println(repeatChar('═', 70));
    
    // Calculate offset
    int16_t calculatedOffset = (int16_t)(avgMeasured - actualDistanceMm);
    
    // Display results
    Serial.println("\n" + String('═') * 70);
    Serial.println("OFFSET CALIBRATION RESULTS");
    Serial.println(String('═') * 70);
    
    Serial.print("\nCalibration Duration: ");
    Serial.print(duration / 1000.0, 2);
    Serial.println(" seconds");
    
    Serial.print("Valid Samples: ");
    Serial.print(validSamples);
    Serial.print("/");
    Serial.print(numSamples);
    Serial.print(" (");
    Serial.print((validSamples * 100) / numSamples);
    Serial.println("%)");
    
    Serial.println("\n┌─ MEASUREMENTS ──────────────────────────────────────────────────┐");
    Serial.print("│  Actual Distance:     ");
    Serial.print(actualDistanceMm);
    Serial.println(" mm                                     │");
    
    Serial.print("│  Average Measured:    ");
    Serial.print(avgMeasured, 2);
    Serial.println(" mm                                  │");
    
    Serial.print("│  Standard Deviation:  ");
    Serial.print(stdDev, 2);
    Serial.println(" mm                                   │");
    
    Serial.print("│  Minimum Reading:     ");
    Serial.print(minDist, 0);
    Serial.println(" mm                                     │");
    Serial.println("\n[ERROR] VL53L0X register read failed");
    return 0;
    Serial.println(" mm                                     │");
  // ============================================================================
  // CALIBRATION FUNCTIONS
  // ============================================================================

  void performOffsetCalibration() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("VL53L0X OFFSET CALIBRATION");
    Serial.println(String('═') * 70);

    Serial.println("\n🎯 SETUP REQUIREMENTS:");
    Serial.println("  1. WHITE, FLAT target (paper or cardboard)");
    Serial.println("  2. Target EXACTLY at known distance");
    Serial.println("  3. Target PERPENDICULAR to sensor");
    Serial.println("  4. No other objects in field of view (25° cone)");
    Serial.println("  5. Normal room lighting (not direct sunlight)");
    Serial.println("  6. Stable mounting - no vibration");

    Serial.println("\n📏 Recommended Calibration Distances:");
    Serial.println("  • 100mm - Best for close-range work (recommended)");
    Serial.println("  • 200mm - Good for medium-range");
    Serial.println("  • 400mm - For long-range applications");

    Serial.print("\nEnter actual target distance (mm, 50-1000) [100]: ");
    int actualDistanceMm = readIntFromSerial(50, 1000, 100);

    Serial.println("\n📝 Calibration Setup Checklist:");
    Serial.println("  [ ] White target positioned at " + String(actualDistanceMm) + "mm");
    Serial.println("  [ ] Target perpendicular to sensor");
    Serial.println("  [ ] Area behind target is clear");
    Serial.println("  [ ] Stable mounting (no movement)");
    Serial.println("  [ ] Normal indoor lighting");

    Serial.println("\nPress any key when ready to start calibration...");
    waitForSerialInput();

    // Configuration for calibration
    const int numSamples = 100;
    int validSamples = 0;
    long sumDistances = 0;
    float distances[numSamples];

    Serial.println("\n⏱ Collecting " + String(numSamples) + " measurements...");
    Serial.println("(Keep target still and stable)\n");

    unsigned long startTime = millis();

    for (int i = 0; i < numSamples; i++) {
      uint16_t distance = tof.readRangeSingleMillimeters();

      if (!tof.timeoutOccurred() && distance < 8190) {
        distances[validSamples] = distance;
        sumDistances += distance;
        validSamples++;

        // Progress indicator
        if ((i + 1) % 10 == 0) {
          Serial.print("  Progress: ");
          Serial.print(((i + 1) * 100) / numSamples);
          Serial.print("% | Last reading: ");
          Serial.print(distance);
          Serial.println("mm");
        }
      } else {
        Serial.print("  Sample ");
        Serial.print(i + 1);
        Serial.println(": Failed (timeout or out of range)");
      }

      delay(50); // 20Hz sampling
    }

    unsigned long duration = millis() - startTime;

    // Check if we have enough valid samples
    if (validSamples < numSamples * 0.8) {
      Serial.println("\n✗ CALIBRATION FAILED");
      Serial.println("  Too many failed measurements (" + String(validSamples) + "/" + String(numSamples) + ")");
      Serial.println("\n  Possible causes:");
      Serial.println("  • Target too far or too close");
      Serial.println("  • Target not in field of view");
      Serial.println("  • Target material too dark/reflective");
      Serial.println("  • Ambient light too bright");
      Serial.println("  • Sensor malfunction");
      return;
    }

    // Calculate statistics
    float avgMeasured = (float)sumDistances / validSamples;

    // Calculate standard deviation
    float variance = 0;
    for (int i = 0; i < validSamples; i++) {
      float diff = distances[i] - avgMeasured;
      variance += diff * diff;
    }
    variance /= validSamples;
    float stdDev = sqrt(variance);

    // Find min/max
    float minDist = 9999;
    float maxDist = 0;
    for (int i = 0; i < validSamples; i++) {
      if (distances[i] < minDist) minDist = distances[i];
      if (distances[i] > maxDist) maxDist = distances[i];
    }

    // Calculate offset
    int16_t calculatedOffset = (int16_t)(avgMeasured - actualDistanceMm);

    // Display results
    Serial.println("\n" + String('═') * 70);
    Serial.println("OFFSET CALIBRATION RESULTS");
    Serial.println(String('═') * 70);

    Serial.print("\nCalibration Duration: ");
    Serial.print(duration / 1000.0, 2);
    Serial.println(" seconds");

    Serial.print("Valid Samples: ");
    Serial.print(validSamples);
    Serial.print("/");
    Serial.print(numSamples);
    Serial.print(" (");
    Serial.print((validSamples * 100) / numSamples);
    Serial.println("%)");

    Serial.println("\n┌─ MEASUREMENTS ──────────────────────────────────────────────────┐");
    Serial.print("│  Actual Distance:     ");
    Serial.print(actualDistanceMm);
    Serial.println(" mm                                     │");

    Serial.print("│  Average Measured:    ");
    Serial.print(avgMeasured, 2);
    Serial.println(" mm                                  │");

    Serial.print("│  Standard Deviation:  ");
    Serial.print(stdDev, 2);
    Serial.println(" mm                                   │");

    Serial.print("│  Minimum Reading:     ");
    Serial.print(minDist, 0);
    Serial.println(" mm                                     │");

    Serial.print("│  Maximum Reading:     ");
    Serial.print(maxDist, 0);
    Serial.println(" mm                                     │");

    Serial.print("│  Range (max-min):     ");
    Serial.print(maxDist - minDist, 0);
    Serial.println(" mm                                      │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");

    Serial.println("\n┌─ CALIBRATION ───────────────────────────────────────────────────┐");
    Serial.print("│  Calculated Offset:   ");
    Serial.print(calculatedOffset);
    Serial.println(" mm                                       │");

    Serial.print("│  Error (uncalibrated): ");
    Serial.print(abs(calculatedOffset));
    Serial.print(" mm (");
    Serial.print((abs(calculatedOffset) / (float)actualDistanceMm) * 100.0, 1);
    Serial.println("%)                  │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");

    // Quality assessment
    Serial.println("\n┌─ QUALITY ASSESSMENT ────────────────────────────────────────────┐");
    Serial.println("│                                                                  │");

    bool qualityGood = true;

    if (stdDev < 2.0) {
      Serial.println("│  ✓ Excellent consistency (std dev < 2mm)                        │");
    } else if (stdDev < 5.0) {
      Serial.println("│  ✓ Good consistency (std dev < 5mm)                             │");
    } else if (stdDev < 10.0) {
      Serial.println("│  ⚠ Fair consistency (std dev < 10mm)                            │");
      Serial.println("│    Consider recalibrating with more stable setup                │");
      qualityGood = false;
    } else {
      Serial.println("│  ✗ Poor consistency (std dev > 10mm)                            │");
      Serial.println("│    Target likely moved or environment unstable                  │");
      Serial.println("│    Recalibration strongly recommended                           │");
      qualityGood = false;
    }

    if ((maxDist - minDist) < 20) {
      Serial.println("│  ✓ Low measurement spread (< 20mm range)                        │");
    } else if ((maxDist - minDist) < 50) {
      Serial.println("│  ⚠ Moderate measurement spread (20-50mm range)                  │");
      qualityGood = false;
    } else {
      Serial.println("│  ✗ High measurement spread (> 50mm range)                       │");
      qualityGood = false;
    }

    Serial.println("│                                                                  │");

    if (qualityGood) {
      Serial.println("│  Overall: ✓ CALIBRATION PASSED - High Quality                  │");
    } else {
      Serial.println("│  Overall: ⚠ CALIBRATION QUESTIONABLE - Consider Retry          │");
    }

    Serial.println("│                                                                  │
    Serial.println("└──────────────────────────────────────────────────────────────────┘");

    // Ask to apply calibration
    Serial.println("\n" + String('═') * 70);
    Serial.print("Apply this calibration? (y/n) [y]: ");

    char response = readCharFromSerial('y');

    if (response == 'y' || response == 'Y') {
      calibration.offsetMm = calculatedOffset;
      calibration.isOffsetCalibrated = true;
      calibration.timestamp = millis();
      calibration.stdDevMm = stdDev;
      calibration.sampleCount = validSamples;
      calibration.minReading = minDist;
      calibration.maxReading = maxDist;
      calibration.avgReading = avgMeasured;

      Serial.println("\n✓ Calibration applied successfully!");
      Serial.println("  All distance readings will now be offset-corrected.");
    } else {
      Serial.println("\n⚠ Calibration NOT applied");
      Serial.println("  Readings will remain uncalibrated.");
    }

    Serial.println(String('═') * 70);
  }
      float avgAbsError = totalAbsError / validTests;
      
      Serial.println("\n┌─ SUMMARY ───────────────────────────────────────────────────────┐");
      Serial.print("│  Average Absolute Error: ");
      Serial.print(avgAbsError, 2);
      Serial.println(" mm                               │");
      
      if (avgAbsError < 10) {
        Serial.println("│  Overall Assessment: ✓ Excellent Calibration                   │");
      } else if (avgAbsError < 20) {
        Serial.println("│  Overall Assessment: ✓ Good Calibration                        │");
      } else if (avgAbsError < 40) {
        Serial.println("│  Overall Assessment: ⚠ Fair - Consider Recalibration           │");
      } else {
        Serial.println("│  Overall Assessment: ✗ Poor - Recalibration Recommended        │");
      }
      
      Serial.println("└──────────────────────────────────────────────────────────────────┘");
    }
    
    Serial.println("\n" + String('═') * 70);
  }

  // ============================================================================
  // TEST MODE FUNCTIONS
  // ============================================================================

  void runRangeProfileTest() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("RANGE PROFILE TEST");
    Serial.println(String('═') * 70);
    
    Serial.println("\nThis test measures accuracy across the full sensor range.");
    Serial.println("Move an object from near to far while monitoring readings.");
    
    Serial.print("\nTest duration (seconds, 10-120) [30]: ");
    int duration = readIntFromSerial(10, 120, 30);
    
    Serial.println("\nStarting range profile test...");
    Serial.println("Move object slowly from close to far distance");
    Serial.println();
    
    delay(2000);
    
    // Reset histogram
    for (int i = 0; i < HISTOGRAM_BINS; i++) {
      histogram[i] = 0;
    }
    
    unsigned long startTime = millis();
    unsigned long lastPrint = 0;
    
    int measurementCount = 0;
    float minDist = 9999;
    float maxDist = 0;
    long sumDist = 0;
    
    while (millis() - startTime < duration * 1000UL) {
      uint16_t rawDist = tof.readRangeSingleMillimeters();
      
      if (!tof.timeoutOccurred() && rawDist < 8190) {
        int16_t distance = rawDist;
        
        if (calibration.isOffsetCalibrated) {
          distance -= calibration.offsetMm;
        }
        
        // Update histogram
        if (distance >= 0 && distance < HISTOGRAM_RANGE_MM) {
          int bin = (distance * HISTOGRAM_BINS) / HISTOGRAM_RANGE_MM;
          if (bin >= 0 && bin < HISTOGRAM_BINS) {
            histogram[bin]++;
          }
        }
        
        // Track statistics
        if (distance < minDist) minDist = distance;
        if (distance > maxDist) maxDist = distance;
        sumDist += distance;
        measurementCount++;
        
        // Print periodically
        if (millis() - lastPrint >= 500) {
          Serial.print("Distance: ");
          printPaddedInt(distance, 4);
          Serial.print("mm | Samples: ");
          printPaddedInt(measurementCount, 4);
          Serial.print(" | Range: ");
          printPaddedInt((int)minDist, 4);
          Serial.print("-");
          printPaddedInt((int)maxDist, 4);
          Serial.println("mm");
          
          lastPrint = millis();
        }
      }
      
      delay(20); // ~50Hz
    }
    
    // Display results
    float avgDist = (float)sumDist / measurementCount;
    
    Serial.println("\n" + String('═') * 70);
    Serial.println("RANGE PROFILE RESULTS");
    Serial.println(String('═') * 70);
    
    Serial.print("\nTotal Measurements: ");
    Serial.println(measurementCount);
    
    Serial.print("Minimum Distance: ");
    Serial.print(minDist, 0);
    Serial.println("mm");
    
    Serial.print("Maximum Distance: ");
    Serial.print(maxDist, 0);
    Serial.println("mm");
    
    Serial.print("Average Distance: ");
    Serial.print(avgDist, 1);
    Serial.println("mm");
    
    Serial.print("Range Covered: ");
    Serial.print(maxDist - minDist, 0);
    Serial.println("mm");
    
    // Display histogram
    Serial.println("\n┌─ DISTANCE DISTRIBUTION ─────────────────────────────────────────┐");
    Serial.println("│                                                                  │");
    
    int maxCount = 0;
    for (int i = 0; i < HISTOGRAM_BINS; i++) {
      if (histogram[i] > maxCount) maxCount = histogram[i];
    }
    
    for (int i = 0; i < HISTOGRAM_BINS; i++) {
      int rangeStart = (i * HISTOGRAM_RANGE_MM) / HISTOGRAM_BINS;
      int rangeEnd = ((i + 1) * HISTOGRAM_RANGE_MM) / HISTOGRAM_BINS;
      
      Serial.print("│  ");
      printPaddedInt(rangeStart, 4);
      Serial.print("-");
      printPaddedInt(rangeEnd, 4);
      Serial.print("mm: ");
      
      // Bar graph
      int barLength = (histogram[i] * 40) / (maxCount > 0 ? maxCount : 1);
      for (int b = 0; b < barLength; b++) {
        Serial.print("█");
      }
      for (int b = barLength; b < 40; b++) {
        Serial.print(" ");
      }
      
      Serial.print(" ");
      Serial.print(histogram[i]);
      Serial.println(" │");
    }
    
    Serial.println("│                                                                  │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n" + String('═') * 70);
  }

  void runMaterialResponseTest() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("MATERIAL RESPONSE TEST");
    Serial.println(String('═') * 70);
    
    Serial.println("\nThis test measures sensor response to different materials.");
    Serial.println("You will test multiple surface types at a fixed distance.");
    
    Serial.print("\nTest distance (mm, 100-500) [200]: ");
    int testDistance = readIntFromSerial(100, 500, 200);
    
    Serial.print("Number of materials to test (1-10) [5]: ");
    int numMaterials = readIntFromSerial(1, 10, 5);
    
    struct MaterialResult {
      String name;
      float avgDistance;
      float stdDev;
      int failedSamples;
    };
    
    MaterialResult results[10];
    
    for (int m = 0; m < numMaterials; m++) {
      Serial.println("\n" + String('─') * 70);
      Serial.print("Material ");
      Serial.print(m + 1);
      Serial.print(" of ");
      Serial.println(numMaterials);
      Serial.println(String('─') * 70);
      
      Serial.print("\nEnter material name (e.g., white paper, black card): ");
      String materialName = readStringFromSerial();
      
      Serial.println("\n📝 Position " + materialName + " at " + String(testDistance) + "mm");
      Serial.println("Press any key when ready...");
      waitForSerialInput();
      
      // Take measurements
      const int samples = 50;
      float distances[samples];
      int validCount = 0;
      long sum = 0;
      
      Serial.println("Measuring... (50 samples)");
      
      for (int i = 0; i < samples; i++) {
        uint16_t rawDist = tof.readRangeSingleMillimeters();
        
        if (!tof.timeoutOccurred() && rawDist < 8190) {
          int16_t dist = rawDist;
          if (calibration.isOffsetCalibrated) {
            dist -= calibration.offsetMm;
          }
          
          distances[validCount] = dist;
          sum += dist;
          validCount++;
          
          if ((i + 1) % 10 == 0) {
            Serial.print("  ");
            Serial.print(validCount);
            Serial.print("/");
            Serial.print(i + 1);
            Serial.print(" valid");
            if (validCount > 0) {
              Serial.print(" - Current avg: ");
              Serial.print((float)sum / validCount, 1);
              Serial.print("mm");
            }
            Serial.println();
          }
        }
        
        delay(40);
      }
      
      // Calculate statistics
      if (validCount > 0) {
        float avg = (float)sum / validCount;
        
        // Standard deviation
        float variance = 0;
        for (int i = 0; i < validCount; i++) {
          float diff = distances[i] - avg;
          variance += diff * diff;
        }
        variance /= validCount;
        float stdDev = sqrt(variance);
        
        results[m].name = materialName;
        results[m].avgDistance = avg;
        results[m].stdDev = stdDev;
        results[m].failedSamples = samples - validCount;
        
        Serial.print("\n  Average: ");
        Serial.print(avg, 1);
        Serial.print("mm | Std Dev: ");
        Serial.print(stdDev, 2);
        Serial.print("mm | Failed: ");
        Serial.println(samples - validCount);
      } else {
        Serial.println("\n  ✗ All measurements failed!");
        results[m].name = materialName;
        results[m].avgDistance = -1;
        results[m].stdDev = 0;
        results[m].failedSamples = samples;
      }
    }
    
    // Display comparison
    Serial.println("\n" + String('═') * 70);
    Serial.println("MATERIAL RESPONSE COMPARISON");
    Serial.println(String('═') * 70);
    Serial.print("\nTest Distance: ");
    Serial.print(testDistance);
    Serial.println("mm");
    Serial.println();
    
    Serial.println("┌──────────────────────────────────────────────────────────────────┐");
    Serial.println("│  Material              Measured    Error    Std Dev    Failed    │");
    Serial.println("├──────────────────────────────────────────────────────────────────┤");
    
    for (int i = 0; i < numMaterials; i++) {
      if (results[i].avgDistance >= 0) {
        Serial.print("│  ");
        
        // Material name (truncate if too long)
        String name = results[i].name;
        if (name.length() > 20) name = name.substring(0, 17) + "...";
        Serial.print(name);
        for (int s = name.length(); s < 20; s++) Serial.print(" ");
        
        Serial.print("  ");
        printPaddedFloat(results[i].avgDistance, 7, 1);
        Serial.print("  ");
        
        float error = results[i].avgDistance - testDistance;
        printPaddedFloat(error, 6, 1);
        Serial.print("  ");
        
        printPaddedFloat(results[i].stdDev, 6, 2);
        Serial.print("  ");
        
        printPaddedInt(results[i].failedSamples, 5);
        Serial.println("  │");
      } else {
        Serial.print("│  ");
        String name = results[i].name;
        if (name.length() > 20) name = name.substring(0, 17) + "...";
        Serial.print(name);
        for (int s = name.length(); s < 20; s++) Serial.print(" ");
        Serial.println("  ALL FAILED                          │");
      }
    }
    
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n💡 Interpretation:");
    Serial.println("  • White/light materials: Most accurate (highest reflectance)");
    Serial.println("  • Dark materials: May under-read (low reflectance)");
    Serial.println("  • Shiny materials: May over-read (specular reflection)");
    Serial.println("  • Failed samples: Material absorbed/scattered too much light");
    
    Serial.println("\n" + String('═') * 70);
  }

  void runSpeedTest() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("MEASUREMENT SPEED TEST");
    Serial.println(String('═') * 70);
    
    Serial.println("\nThis test measures sensor update rate and timing consistency.");
    
    Serial.print("\nNumber of measurements to collect (100-1000) [500]: ");
    int numMeasurements = readIntFromSerial(100, 1000, 500);
    
    Serial.println("\nTesting at different timing budgets...");
    
    uint32_t timingBudgets[] = {TIMING_BUDGET_FAST, TIMING_BUDGET_BALANCED, TIMING_BUDGET_ACCURATE};
    const char* budgetNames[] = {"Fast (20ms)", "Balanced (33ms)", "Accurate (200ms)"};
    
    for (int b = 0; b < 3; b++) {
      Serial.println("\n" + String('─') * 70);
      Serial.print("Testing: ");
      Serial.println(budgetNames[b]);
      Serial.println(String('─') * 70);
      
      tof.setMeasurementTimingBudget(timingBudgets[b]);
      delay(100);
      
      unsigned long measurements[1000];
      int validCount = 0;
      
      Serial.println("Measuring...");
      
      unsigned long startTime = millis();
      
      for (int i = 0; i < numMeasurements; i++) {
        unsigned long measureStart = micros();
        
        uint16_t distance = tof.readRangeSingleMillimeters();
        
        unsigned long measureTime = micros() - measureStart;
        
        if (!tof.timeoutOccurred() && distance < 8190) {
          measurements[validCount] = measureTime;
          validCount++;
        }
        
        if ((i + 1) % 100 == 0) {
          Serial.print("  ");
          Serial.print(i + 1);
          Serial.print("/");
          Serial.println(numMeasurements);
        }
      }
      
      unsigned long totalTime = millis() - startTime;
      
      if (validCount > 0) {
        // Calculate statistics
        unsigned long sum = 0;
        unsigned long minTime = 999999;
        unsigned long maxTime = 0;
        
        for (int i = 0; i < validCount; i++) {
          sum += measurements[i];
          if (measurements[i] < minTime) minTime = measurements[i];
          if (measurements[i] > maxTime) maxTime = measurements[i];
        }
        
        float avgTime = (float)sum / validCount / 1000.0; // Convert to ms
        float actualRate = (validCount * 1000.0) / totalTime; // Hz
        
        Serial.println("\n  Results:");
        Serial.print("    Valid measurements: ");
        Serial.print(validCount);
        Serial.print("/");
        Serial.println(numMeasurements);
        
        Serial.print("    Average measurement time: ");
        Serial.print(avgTime, 2);
        Serial.println("ms");
        
        Serial.print("    Min time: ");
        Serial.print(minTime / 1000.0, 2);
        Serial.println("ms");
        
        Serial.print("    Max time: ");
        Serial.print(maxTime / 1000.0, 2);
        Serial.println("ms");
        
        Serial.print("    Actual update rate: ");
        Serial.print(actualRate, 1);
        Serial.println("Hz");
        
        Serial.print("    Total test duration: ");
        Serial.print(totalTime / 1000.0, 2);
        Serial.println("s");
      } else {
        Serial.println("\n  ✗ All measurements failed!");
      }
    }
    
    // Reset to balanced mode
    tof.setMeasurementTimingBudget(TIMING_BUDGET_BALANCED);
    
    Serial.println("\n" + String('═') * 70);
    Serial.println("✓ Speed test complete");
    Serial.println("  Timing budget reset to Balanced (33ms)");
    Serial.println(String('═') * 70);
  }

  // ============================================================================
  // DISPLAY FUNCTIONS
  // ============================================================================

  void displayLiveDistance() {
    unsigned long measureStart = micros();
    uint16_t rawDistance = tof.readRangeSingleMillimeters();
    unsigned long measureTime = micros() - measureStart;
    
    // Update metrics
    metrics.totalMeasurements++;
    metrics.totalMeasurementTime += measureTime;
    
    if (measureTime < metrics.minMeasurementTime) {
      metrics.minMeasurementTime = measureTime;
    }
    if (measureTime > metrics.maxMeasurementTime) {
      metrics.maxMeasurementTime = measureTime;
    }
    
    if (tof.timeoutOccurred()) {
      metrics.timeouts++;
      Serial.println("Distance: TIMEOUT                                    ");
      return;
    }
    
    if (rawDistance >= 8190) {
      metrics.outOfRange++;
      Serial.println("Distance: OUT OF RANGE (>2m)                         ");
      return;
    }
    
    // Apply calibration
    int16_t distance = rawDistance;
    if (calibration.isOffsetCalibrated) {
      distance -= calibration.offsetMm;
    }
    
    metrics.successfulMeasurements++;
    metrics.totalDistance += distance;
    
    if (distance < metrics.minDistance) metrics.minDistance = distance;
    if (distance > metrics.maxDistance) metrics.maxDistance = distance;
    
    // Display based on mode
    switch (currentMode) {
      case MODE_LIVE_DISTANCE:
        displayStandardDistance(distance, rawDistance, measureTime);
        break;
        
      case MODE_CONTINUOUS_FAST:
        displayFastDistance(distance);
        break;
        
      case MODE_HIGH_ACCURACY:
        displayHighAccuracyDistance(distance, measureTime);
        break;
        
      default:
        displayStandardDistance(distance, rawDistance, measureTime);
        break;
    }
  }

  void displayStandardDistance(int16_t distance, uint16_t raw, unsigned long timeUs) {
    Serial.print("Distance: ");
    printPaddedInt(distance, 4);
    Serial.print("mm");
    
    if (calibration.isOffsetCalibrated) {
      Serial.print(" (raw: ");
      printPaddedInt(raw, 4);
      Serial.print("mm, offset: ");
      Serial.print(calibration.offsetMm);
      Serial.print("mm)");
    }
    
    // Visual bar
    if (distance < 1000) {
      int barLength = map(distance, 0, 1000, 0, 30);
      barLength = constrain(barLength, 0, 30);
      
      Serial.print(" [");
      for (int i = 0; i < barLength; i++) Serial.print("█");
      for (int i = barLength; i < 30; i++) Serial.print(" ");
      Serial.print("]");
    }
    
    // Measurement time
    Serial.print(" ");
    Serial.print(timeUs / 1000.0, 1);
    Serial.print("ms");
    
    Serial.println();
  }

  void displayFastDistance(int16_t distance) {
    Serial.print(distance);
    Serial.print("mm ");
    
    // Simple bar
    int bars = distance / 100;
    for (int i = 0; i < bars && i < 20; i++) {
      Serial.print("█");
    }
    
    Serial.println();
  }

  void displayHighAccuracyDistance(int16_t distance, unsigned long timeUs) {
    // Take multiple samples for averaging
    static int sampleBuffer[10];
    static int sampleIndex = 0;
    static int sampleCount = 0;
    
    sampleBuffer[sampleIndex] = distance;
    sampleIndex = (sampleIndex + 1) % 10;
    if (sampleCount < 10) sampleCount++;
    
    // Calculate average
    long sum = 0;
    for (int i = 0; i < sampleCount; i++) {
      sum += sampleBuffer[i];
    }
    float avg = (float)sum / sampleCount;
    
    Serial.print("Distance: ");
    printPaddedFloat(avg, 7, 2);
    Serial.print("mm (");
    Serial.print(sampleCount);
    Serial.print("-sample avg) | Current: ");
    printPaddedInt(distance, 4);
    Serial.print("mm | Time: ");
    Serial.print(timeUs / 1000.0, 2);
    Serial.println("ms");
  }

  void displayStatistics() {
    unsigned long uptime = (millis() - metrics.startTime) / 1000;
    float successRate = (metrics.successfulMeasurements * 100.0) / 
                        (metrics.totalMeasurements > 0 ? metrics.totalMeasurements : 1);
    float avgDistance = metrics.totalDistance / 
                       (metrics.successfulMeasurements > 0 ? metrics.successfulMeasurements : 1);
    float avgMeasureTime = (float)metrics.totalMeasurementTime / 
                          (metrics.totalMeasurements > 0 ? metrics.totalMeasurements : 1) / 1000.0;
    
    Serial.println("\n" + String('═') * 70);
    Serial.println("VL53L0X PERFORMANCE STATISTICS");
    Serial.println(String('═') * 70);
    
    Serial.println("\n┌─ SYSTEM ────────────────────────────────────────────────────────┐");
    Serial.print("│  Uptime: ");
    Serial.print(uptime);
    Serial.print(" seconds (");
    Serial.print(uptime / 60);
    Serial.println(" minutes)                           │");
    
    Serial.print("│  Measurement Count: ");
    Serial.print(metrics.totalMeasurements);
    Serial.println("                                          │");
    
    Serial.print("│  Successful: ");
    Serial.print(metrics.successfulMeasurements);
    Serial.print(" (");
    Serial.print(successRate, 1);
    Serial.println("%)                                     │");
    
    Serial.print("│  Timeouts: ");
    Serial.print(metrics.timeouts);
    Serial.println("                                                   │");
    
    Serial.print("│  Out of Range: ");
    Serial.print(metrics.outOfRange);
    Serial.println("                                             │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n┌─ CALIBRATION ───────────────────────────────────────────────────┐");
    Serial.print("│  Offset Calibration: ");
    Serial.print(calibration.isOffsetCalibrated ? "✓ Active" : "✗ Not Calibrated");
    Serial.println("                                      │");
    
    if (calibration.isOffsetCalibrated) {
      Serial.print("│    Offset: ");
      Serial.print(calibration.offsetMm);
      Serial.println(" mm                                                 │");
      
      Serial.print("│    Quality Std Dev: ");
      Serial.print(calibration.stdDevMm, 2);
      Serial.println(" mm                                      │");
      
      unsigned long calAge = (millis() - calibration.timestamp) / 1000;
      Serial.print("│    Age: ");
      Serial.print(calAge / 60);
      Serial.print(" minutes ");
      Serial.print(calAge % 60);
      Serial.println(" seconds                                   │");
    }
    
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    if (metrics.successfulMeasurements > 0) {
      Serial.println("\n┌─ DISTANCE STATISTICS ───────────────────────────────────────────┐");
      Serial.print("│  Current: ");
      
      uint16_t current = tof.readRangeSingleMillimeters();
      if (!tof.timeoutOccurred() && current < 8190) {
        int16_t calibrated = current - (calibration.isOffsetCalibrated ? calibration.offsetMm : 0);
        Serial.print(calibrated);
        Serial.println(" mm                                                  │");
      } else {
        Serial.println("N/A                                                  │");
      }
      
      Serial.print("│  Average: ");
      Serial.print(avgDistance, 1);
      Serial.println(" mm                                               │");
      
      Serial.print("│  Minimum: ");
      Serial.print(metrics.minDistance, 0);
      Serial.println(" mm                                                │");
      
      Serial.print("│  Maximum: ");
      Serial.print(metrics.maxDistance, 0);
      Serial.println(" mm                                               │");
      
      Serial.print("│  Range: ");
      Serial.print(metrics.maxDistance - metrics.minDistance, 0);
      Serial.println(" mm                                                  │");
      Serial.println("└──────────────────────────────────────────────────────────────────┘");
    }
    
    Serial.println("\n┌─ TIMING STATISTICS ─────────────────────────────────────────────┐");
    Serial.print("│  Average Measurement Time: ");
    Serial.print(avgMeasureTime, 2);
    Serial.println(" ms                           │");
    
    Serial.print("│  Minimum: ");
    Serial.print(metrics.minMeasurementTime / 1000.0, 2);
    Serial.println(" ms                                            │");
    
    Serial.print("│  Maximum: ");
    Serial.print(metrics.maxMeasurementTime / 1000.0, 2);
    Serial.println(" ms                                            │");
    
    float theoreticalRate = 1000.0 / avgMeasureTime;
    Serial.print("│  Theoretical Max Rate: ");
    Serial.print(theoreticalRate, 1);
    Serial.println(" Hz                                 │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n" + String('═') * 70);
  }

  // ============================================================================
  // MENU AND COMMAND HANDLING
  // ============================================================================

  void printMainMenu() {
    Serial.println("\n" + String('═') * 70);
    Serial.println("COMMAND MENU");
    Serial.println(String('═') * 70);
    
    Serial.println("\n┌─ CALIBRATION ───────────────────────────────────────────────────┐");
    Serial.println("│  c - Offset calibration (measures systematic error)             │");
    Serial.println("│  v - Verify calibration accuracy (multi-distance test)          │");
    Serial.println("│  x - Clear calibration (reset to uncalibrated)                  │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n┌─ DISPLAY MODES ─────────────────────────────────────────────────┐");
    Serial.println("│  1 - Standard distance display (with bar graph)                 │");
    Serial.println("│  2 - Fast mode (minimal output)                                 │");
    Serial.println("│  3 - High accuracy mode (10-sample averaging)                   │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n┌─ TESTS ─────────────────────────────────────────────────────────┐");
    Serial.println("│  r - Range profile test (scan full range)                       │");
    Serial.println("│  m - Material response test (compare surfaces)                  │");
    Serial.println("│  t - Speed test (measure update rates)                          │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n┌─ CONFIGURATION ─────────────────────────────────────────────────┐");
    Serial.println("│  f - Set timing budget FAST (20ms, ~50Hz)                       │");
    Serial.println("│  b - Set timing budget BALANCED (33ms, ~30Hz)                   │");
    Serial.println("│  a - Set timing budget ACCURATE (200ms, ~5Hz)                   │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n┌─ UTILITIES ─────────────────────────────────────────────────────┐");
    Serial.println("│  s - Show statistics                                             │");
    Serial.println("│  z - Reset metrics                                               │");
    Serial.println("│  h - Show this help menu                                         │");
    Serial.println("└──────────────────────────────────────────────────────────────────┘");
    
    Serial.println("\n" + String('═') * 70);
    Serial.println();
  }

  void handleSerialCommand(char cmd) {
    switch (cmd) {
      case 'c':
      case 'C':
        performOffsetCalibration();
        break;
        
      case 'v':
      case 'V':
        testCalibrationAccuracy();
        break;
        
      case 'x':
      case 'X':
        calibration.offsetMm = 0;
        calibration.isOffsetCalibrated = false;
        Serial.println("\n✓ Calibration cleared");
        Serial.println("  Sensor now operating without offset correction");
        break;
        
      case '1':
        currentMode = MODE_LIVE_DISTANCE;
        Serial.println("\n→ Mode: Standard Distance Display");
        break;
        
      case '2':
        currentMode = MODE_CONTINUOUS_FAST;
        Serial.println("\n→ Mode: Fast Display (minimal output)");
        break;
        
      case '3':
        currentMode = MODE_HIGH_ACCURACY;
        Serial.println("\n→ Mode: High Accuracy (10-sample averaging)");
        break;
        
      case 'r':
      case 'R':
        runRangeProfileTest();
        break;
        
      case 'm':
      case 'M':
        runMaterialResponseTest();
        break;
        
      case 't':
      case 'T':
        runSpeedTest();
        break;
        
      case 'f':
      case 'F':
        tof.setMeasurementTimingBudget(TIMING_BUDGET_FAST);
        Serial.println("\n✓ Timing budget: FAST (20ms, ~50Hz)");
        Serial.println("  Trade-off: Faster updates, less accurate");
        break;
        
      case 'b':
      case 'B':
        tof.setMeasurementTimingBudget(TIMING_BUDGET_BALANCED);
        Serial.println("\n✓ Timing budget: BALANCED (33ms, ~30Hz)");
        Serial.println("  Trade-off: Good balance of speed and accuracy");
        break;
        
      case 'a':
      case 'A':
        tof.setMeasurementTimingBudget(TIMING_BUDGET_ACCURATE);
        Serial.println("\n✓ Timing budget: ACCURATE (200ms, ~5Hz)");
        Serial.println("  Trade-off: Most accurate, slower updates");
        break;
        
      case 's':
      case 'S':
        displayStatistics();
        break;
        
      case 'z':
      case 'Z':
        metrics.totalMeasurements = 0;
        metrics.successfulMeasurements = 0;
        metrics.timeouts = 0;
        metrics.outOfRange = 0;
        metrics.startTime = millis();
        metrics.minDistance = 9999;
        metrics.maxDistance = 0;
        metrics.totalDistance = 0;
        metrics.minMeasurementTime = 9999;
        metrics.maxMeasurementTime = 0;
        metrics.totalMeasurementTime = 0;
        
        // Reset histogram
        for (int i = 0; i < HISTOGRAM_BINS; i++) {
          histogram[i] = 0;
        }
        
        Serial.println("\n✓ All metrics reset");
        break;
        
      case 'h':
      case 'H':
        printMainMenu();
        break;
        
      default:
        Serial.println("\n⚠ Unknown command. Press 'h' for help.");
        break;
    }
  }

  // ============================================================================
  // UTILITY FUNCTIONS
  // ============================================================================

  void printPaddedInt(int value, int width) {
    String str = String(value);
    for (int i = str.length(); i < width; i++) {
      Serial.print(" ");
    }
    Serial.print(str);
  }

  void printPaddedFloat(float value, int width, int decimals) {
    char buffer[20];
    dtostrf(value, width, decimals, buffer);
    Serial.print(buffer);
  }

  void waitForSerialInput() {
    while (!Serial.available()) {
      delay(10);
    }
    while (Serial.available()) {
      Serial.read(); // Clear buffer
    }
  }

  int readIntFromSerial(int minVal, int maxVal, int defaultVal) {
    unsigned long timeout = millis() + 10000;
    String input = "";
    
    while (millis() < timeout) {
      if (Serial.available()) {
        char c = Serial.read();
        
        if (c == '\n' || c == '\r') {
          if (input.length() == 0) {
            Serial.println(defaultVal);
            return defaultVal;
          }
          
          int value = input.toInt();
          
          if (value >= minVal && value <= maxVal) {
            Serial.println(value);
            return value;
          } else {
            Serial.println("\n⚠ Out of range. Using default: " + String(defaultVal));
            return defaultVal;
          }
        } else if (c >= '0' && c <= '9') {
          input += c;
          Serial.print(c);
        }
      }
      delay(10);
    }
    
    Serial.println("\n⏱ Timeout. Using default: " + String(defaultVal));
    return defaultVal;
  }

  char readCharFromSerial(char defaultChar) {
    unsigned long timeout = millis() + 10000;
    
    while (millis() < timeout) {
      if (Serial.available()) {
        char c = Serial.read();
        while (Serial.available()) Serial.read(); // Clear buffer
        
        Serial.println(c);
        return c;
      }
      delay(10);
    }
    
    Serial.println(defaultChar);
    return defaultChar;
  }

  String readStringFromSerial() {
    String input = "";
    unsigned long timeout = millis() + 30000; // 30 second timeout
    
    while (millis() < timeout) {
      if (Serial.available()) {
        char c = Serial.read();
        
        if (c == '\n' || c == '\r') {
          Serial.println();
          return input.length() > 0 ? input : "Unknown";
        } else if (c >= 32 && c <= 126) { // Printable characters
          input += c;
          Serial.print(c);
        }
      }
      delay(10);
    }
    
    Serial.println("\n⏱ Timeout");
    return input.length() > 0 ? input : "Unknown";
  }

  // ============================================================================
  // MAIN LOOP
  // ============================================================================

  unsigned long lastDisplayUpdate = 0;
  const unsigned long displayInterval = 100; // 10Hz default

  void loop() {
    // Update display at controlled rate
    if (millis() - lastDisplayUpdate >= displayInterval) {
      displayLiveDistance();
      lastDisplayUpdate = millis();
    }
    
    // Handle serial commands
    if (Serial.available()) {
      char cmd = Serial.read();
      while (Serial.available()) Serial.read(); // Clear buffer
      
      handleSerialCommand(cmd);
    }
  }


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\tests\wifi_test.cpp
repo_name: wheelie
function_name: printWiFiStatus
last_modified: 2025-12-23 20:04:44.202748
content:
  #include <Arduino.h>
  #include <WiFi.h>
  #include <WebServer.h>
  #include "credentials.h"

  WebServer server(80);

  unsigned long lastReconnectAttempt = 0;
  const unsigned long reconnectInterval = 10000; // 10 seconds
  int reconnectAttempts = 0;

  void printWiFiStatus() {
    int status = WiFi.status();
    Serial.print("[DEBUG] WiFi.status() = ");
    Serial.print(status);
    switch (status) {
      case WL_IDLE_STATUS: Serial.print(" (IDLE_STATUS)"); break;
      case WL_NO_SSID_AVAIL: Serial.print(" (NO_SSID_AVAIL)"); break;
      case WL_SCAN_COMPLETED: Serial.print(" (SCAN_COMPLETED)"); break;
      case WL_CONNECTED: Serial.print(" (CONNECTED)"); break;
      case WL_CONNECT_FAILED: Serial.print(" (CONNECT_FAILED)"); break;
      case WL_CONNECTION_LOST: Serial.print(" (CONNECTION_LOST)"); break;
      case WL_DISCONNECTED: Serial.print(" (DISCONNECTED)"); break;
      default: Serial.print(" (UNKNOWN)"); break;
    }
    Serial.println();
  }

  void handleRoot() {
    String html = "<html><body><h2>ESP32 WiFi Test: Connected!</h2>";
    html += "<p>IP: " + WiFi.localIP().toString() + "</p>";
    html += "<p>RSSI: " + String(WiFi.RSSI()) + " dBm</p>";
    html += "<p>Reconnects: " + String(reconnectAttempts) + "</p>";
    html += "</body></html>";
    server.send(200, "text/html", html);
  }

  void connectToWiFi() {
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Connecting to WiFi");
    unsigned long startAttempt = millis();
    // This initial connection is allowed to be blocking for a test script.
    while (WiFi.status() != WL_CONNECTED && millis() - startAttempt < 15000) {
      delay(500);
      Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println();
      Serial.print("WiFi connected! IP address: ");
      Serial.println(WiFi.localIP());
    } else {
      Serial.println();
      Serial.println("[ERROR] WiFi connection timed out. Will retry in loop.");
    }
  }

  void setup() {
    Serial.begin(115200);
    delay(500);
    Serial.println("\n[WiFi Robust Test] Connecting...");
    connectToWiFi();
    server.on("/", handleRoot);
    server.begin();
    Serial.println("Web server started.");
  }

  void loop() {
    server.handleClient();
    if (WiFi.status() != WL_CONNECTED) {
      unsigned long now = millis();
      if (now - lastReconnectAttempt > reconnectInterval) {
        Serial.println("[WARN] WiFi lost. Attempting reconnect...");
        reconnectAttempts++;
        connectToWiFi();
        lastReconnectAttempt = now;
      }
    }
  }


repo_name: wheelie
function_name: read
last_modified: 2025-09-22 21:18:10
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\Adafruit_BusIO_Register.h

content:
  #ifndef Adafruit_BusIO_Register_h
  #define Adafruit_BusIO_Register_h

  #include <Arduino.h>

  #if !defined(SPI_INTERFACES_COUNT) ||                                          \
      (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))

  #include <Adafruit_GenericDevice.h>
  #include <Adafruit_I2CDevice.h>
  #include <Adafruit_SPIDevice.h>

  typedef enum _Adafruit_BusIO_SPIRegType {
    ADDRBIT8_HIGH_TOREAD = 0,
    /*!<
     * ADDRBIT8_HIGH_TOREAD
     * When reading a register you must actually send the value 0x80 + register
     * address to the device. e.g. To read the register 0x0B the register value
     * 0x8B is sent and to write 0x0B is sent.
     */
    AD8_HIGH_TOREAD_AD7_HIGH_TOINC = 1,

    /*!<
     * ADDRBIT8_HIGH_TOWRITE
     * When writing to a register you must actually send the value 0x80 +
     * the register address to the device. e.g. To write to the register 0x19 the
     * register value 0x99 is sent and to read 0x19 is sent.
     */
    ADDRBIT8_HIGH_TOWRITE = 2,

    /*!<
     * ADDRESSED_OPCODE_LOWBIT_TO_WRITE
     * Used by the MCP23S series, we send 0x40 |'rd with the opcode
     * Then set the lowest bit to write
     */
    ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE = 3,

  } Adafruit_BusIO_SPIRegType;

  /*!
   * @brief The class which defines a device register (a location to read/write
   * data from)
   */
  class Adafruit_BusIO_Register {
  public:
    Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, uint16_t reg_addr,
                            uint8_t width = 1, uint8_t byteorder = LSBFIRST,
                            uint8_t address_width = 1);

    Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice, uint16_t reg_addr,
                            Adafruit_BusIO_SPIRegType type, uint8_t width = 1,
                            uint8_t byteorder = LSBFIRST,
                            uint8_t address_width = 1);

    Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice,
                            Adafruit_SPIDevice *spidevice,
                            Adafruit_BusIO_SPIRegType type, uint16_t reg_addr,
                            uint8_t width = 1, uint8_t byteorder = LSBFIRST,
                            uint8_t address_width = 1);

    Adafruit_BusIO_Register(Adafruit_GenericDevice *genericdevice,
                            uint16_t reg_addr, uint8_t width = 1,
                            uint8_t byteorder = LSBFIRST,
                            uint8_t address_width = 1);

    bool read(uint8_t *buffer, uint8_t len);
    bool read(uint8_t *value);
    bool read(uint16_t *value);
    uint32_t read(void);
    uint32_t readCached(void);
    bool write(uint8_t *buffer, uint8_t len);
    bool write(uint32_t value, uint8_t numbytes = 0);

    uint8_t width(void);

    void setWidth(uint8_t width);
    void setAddress(uint16_t address);
    void setAddressWidth(uint16_t address_width);

  #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
    void print(Stream *s = &Serial);
    void println(Stream *s = &Serial);
  #else
    void print(Stream *s);
    void println(Stream *s);
  #endif

  private:
    Adafruit_I2CDevice *_i2cdevice;
    Adafruit_SPIDevice *_spidevice;
    Adafruit_GenericDevice *_genericdevice;
    Adafruit_BusIO_SPIRegType _spiregtype;
    uint16_t _address;
    uint8_t _width, _addrwidth, _byteorder;
    uint8_t _buffer[4]; // we won't support anything larger than uint32 for
                        // non-buffered read
    uint32_t _cached = 0;
  };

  /*!
   * @brief The class which defines a slice of bits from within a device register
   * (a location to read/write data from)
   */
  class Adafruit_BusIO_RegisterBits {
  public:
    Adafruit_BusIO_RegisterBits(Adafruit_BusIO_Register *reg, uint8_t bits,
                                uint8_t shift);
    bool write(uint32_t value);
    uint32_t read(void);

  private:
    Adafruit_BusIO_Register *_register;
    uint8_t _bits, _shift;
  };

  #endif // SPI exists
  #endif // BusIO_Register_h


repo_name: wheelie
function_name: begin
last_modified: 2025-09-22 21:18:10
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\Adafruit_GenericDevice.h

content:
  #ifndef ADAFRUIT_GENERICDEVICE_H
  #define ADAFRUIT_GENERICDEVICE_H

  #include <Arduino.h>

  typedef bool (*busio_genericdevice_read_t)(void *obj, uint8_t *buffer,
                                             size_t len);
  typedef bool (*busio_genericdevice_write_t)(void *obj, const uint8_t *buffer,
                                              size_t len);
  typedef bool (*busio_genericdevice_readreg_t)(void *obj, uint8_t *addr_buf,
                                                uint8_t addrsiz, uint8_t *data,
                                                uint16_t datalen);
  typedef bool (*busio_genericdevice_writereg_t)(void *obj, uint8_t *addr_buf,
                                                 uint8_t addrsiz,
                                                 const uint8_t *data,
                                                 uint16_t datalen);

  /*!
   * @brief Class for communicating with a device via generic read/write functions
   */
  class Adafruit_GenericDevice {
  public:
    Adafruit_GenericDevice(
        void *obj, busio_genericdevice_read_t read_func,
        busio_genericdevice_write_t write_func,
        busio_genericdevice_readreg_t readreg_func = nullptr,
        busio_genericdevice_writereg_t writereg_func = nullptr);

    bool begin(void);
    void end(void);

    bool read(uint8_t *buffer, size_t len);
    bool write(const uint8_t *buffer, size_t len);
    bool readRegister(uint8_t *addr_buf, uint8_t addrsiz, uint8_t *buf,
                      uint16_t bufsiz);
    bool writeRegister(uint8_t *addr_buf, uint8_t addrsiz, const uint8_t *buf,
                       uint16_t bufsiz);

  protected:
    /*! @brief Function pointer for reading raw data from the device */
    busio_genericdevice_read_t _read_func;
    /*! @brief Function pointer for writing raw data to the device */
    busio_genericdevice_write_t _write_func;
    /*! @brief Function pointer for reading a 'register' from the device */
    busio_genericdevice_readreg_t _readreg_func;
    /*! @brief Function pointer for writing a 'register' to the device */
    busio_genericdevice_writereg_t _writereg_func;

    bool _begun; ///< whether we have initialized yet (in case the function needs
                 ///< to do something)

  private:
    void *_obj; ///< Pointer to object instance
  };

  #endif // ADAFRUIT_GENERICDEVICE_H


repo_name: wheelie
function_name: begin
last_modified: 2025-09-22 21:18:10
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\Adafruit_I2CDevice.h

content:
  #ifndef Adafruit_I2CDevice_h
  #define Adafruit_I2CDevice_h

  #include <Arduino.h>
  #include <Wire.h>

  ///< The class which defines how we will talk to this device over I2C
  class Adafruit_I2CDevice {
  public:
    Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire = &Wire);
    uint8_t address(void);
    bool begin(bool addr_detect = true);
    void end(void);
    bool detected(void);

    bool read(uint8_t *buffer, size_t len, bool stop = true);
    bool write(const uint8_t *buffer, size_t len, bool stop = true,
               const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
    bool write_then_read(const uint8_t *write_buffer, size_t write_len,
                         uint8_t *read_buffer, size_t read_len,
                         bool stop = false);
    bool setSpeed(uint32_t desiredclk);

    /*!   @brief  How many bytes we can read in a transaction
     *    @return The size of the Wire receive/transmit buffer */
    size_t maxBufferSize() { return _maxBufferSize; }

  private:
    uint8_t _addr;
    TwoWire *_wire;
    bool _begun;
    size_t _maxBufferSize;
    bool _read(uint8_t *buffer, size_t len, bool stop);
  };

  #endif // Adafruit_I2CDevice_h


repo_name: wheelie
function_name: begin
last_modified: 2025-09-22 21:18:10
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\Adafruit_SPIDevice.h

content:
  #ifndef Adafruit_SPIDevice_h
  #define Adafruit_SPIDevice_h

  #include <Arduino.h>

  #if !defined(SPI_INTERFACES_COUNT) ||                                          \
      (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
  // HW SPI available
  #include <SPI.h>
  #define BUSIO_HAS_HW_SPI
  #else
  // SW SPI ONLY
  enum { SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3 };
  typedef uint8_t SPIClass;
  #endif

  // some modern SPI definitions don't have BitOrder enum
  #if (defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)) ||                    \
      defined(ESP8266) || defined(TEENSYDUINO) || defined(SPARK) ||              \
      defined(ARDUINO_ARCH_SPRESENSE) || defined(MEGATINYCORE) ||                \
      defined(DXCORE) || defined(ARDUINO_AVR_ATmega4809) ||                      \
      defined(ARDUINO_AVR_ATmega4808) || defined(ARDUINO_AVR_ATmega3209) ||      \
      defined(ARDUINO_AVR_ATmega3208) || defined(ARDUINO_AVR_ATmega1609) ||      \
      defined(ARDUINO_AVR_ATmega1608) || defined(ARDUINO_AVR_ATmega809) ||       \
      defined(ARDUINO_AVR_ATmega808) || defined(ARDUINO_ARCH_ARC32) ||           \
      defined(ARDUINO_ARCH_XMC)

  typedef enum _BitOrder {
    SPI_BITORDER_MSBFIRST = MSBFIRST,
    SPI_BITORDER_LSBFIRST = LSBFIRST,
  } BusIOBitOrder;

  #elif defined(ESP32) || defined(__ASR6501__) || defined(__ASR6502__)

  // some modern SPI definitions don't have BitOrder enum and have different SPI
  // mode defines
  typedef enum _BitOrder {
    SPI_BITORDER_MSBFIRST = SPI_MSBFIRST,
    SPI_BITORDER_LSBFIRST = SPI_LSBFIRST,
  } BusIOBitOrder;

  #else
  // Some platforms have a BitOrder enum but its named MSBFIRST/LSBFIRST
  #define SPI_BITORDER_MSBFIRST MSBFIRST
  #define SPI_BITORDER_LSBFIRST LSBFIRST
  typedef BitOrder BusIOBitOrder;
  #endif

  #if defined(__IMXRT1062__) // Teensy 4.x
  // *Warning* I disabled the usage of FAST_PINIO as the set/clear operations
  // used in the cpp file are not atomic and can effect multiple IO pins
  // and if an interrupt happens in between the time the code reads the register
  //  and writes out the updated value, that changes one or more other IO pins
  // on that same IO port, those change will be clobbered when the updated
  // values are written back.  A fast version can be implemented that uses the
  // ports set and clear registers which are atomic.
  // typedef volatile uint32_t BusIO_PortReg;
  // typedef uint32_t BusIO_PortMask;
  // #define BUSIO_USE_FAST_PINIO

  #elif defined(__MBED__) || defined(__ZEPHYR__)
  // Boards based on RTOS cores like mbed or Zephyr are not going to expose the
  // low level registers needed for fast pin manipulation
  #undef BUSIO_USE_FAST_PINIO

  #elif defined(ARDUINO_ARCH_XMC)
  #undef BUSIO_USE_FAST_PINIO

  #elif defined(__AVR__) || defined(TEENSYDUINO)
  typedef volatile uint8_t BusIO_PortReg;
  typedef uint8_t BusIO_PortMask;
  #define BUSIO_USE_FAST_PINIO

  #elif defined(ESP8266) || defined(ESP32) || defined(__SAM3X8E__) ||            \
      defined(ARDUINO_ARCH_SAMD)
  typedef volatile uint32_t BusIO_PortReg;
  typedef uint32_t BusIO_PortMask;
  #define BUSIO_USE_FAST_PINIO

  #elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) &&                      \
      !defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_SILABS) &&               \
      !defined(ARDUINO_UNOR4_MINIMA) && !defined(ARDUINO_UNOR4_WIFI) &&          \
      !defined(PORTDUINO)
  typedef volatile uint32_t BusIO_PortReg;
  typedef uint32_t BusIO_PortMask;
  #if !defined(__ASR6501__) && !defined(__ASR6502__)
  #define BUSIO_USE_FAST_PINIO
  #endif

  #else
  #undef BUSIO_USE_FAST_PINIO
  #endif

  /**! The class which defines how we will talk to this device over SPI **/
  class Adafruit_SPIDevice {
  public:
  #ifdef BUSIO_HAS_HW_SPI
    Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000,
                       BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
                       uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI);
  #else
    Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000,
                       BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
                       uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = nullptr);
  #endif
    Adafruit_SPIDevice(int8_t cspin, int8_t sck, int8_t miso, int8_t mosi,
                       uint32_t freq = 1000000,
                       BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
                       uint8_t dataMode = SPI_MODE0);
    ~Adafruit_SPIDevice();

    bool begin(void);
    bool read(uint8_t *buffer, size_t len, uint8_t sendvalue = 0xFF);
    bool write(const uint8_t *buffer, size_t len,
               const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
    bool write_then_read(const uint8_t *write_buffer, size_t write_len,
                         uint8_t *read_buffer, size_t read_len,
                         uint8_t sendvalue = 0xFF);
    bool write_and_read(uint8_t *buffer, size_t len);

    uint8_t transfer(uint8_t send);
    void transfer(uint8_t *buffer, size_t len);
    void beginTransaction(void);
    void endTransaction(void);
    void beginTransactionWithAssertingCS();
    void endTransactionWithDeassertingCS();

  private:
  #ifdef BUSIO_HAS_HW_SPI
    SPIClass *_spi = nullptr;
    SPISettings *_spiSetting = nullptr;
  #else
    uint8_t *_spi = nullptr;
    uint8_t *_spiSetting = nullptr;
  #endif
    uint32_t _freq;
    BusIOBitOrder _dataOrder;
    uint8_t _dataMode;
    void setChipSelect(int value);

    int8_t _cs, _sck, _mosi, _miso;
  #ifdef BUSIO_USE_FAST_PINIO
    BusIO_PortReg *mosiPort, *clkPort, *misoPort, *csPort;
    BusIO_PortMask mosiPinMask, misoPinMask, clkPinMask, csPinMask;
  #endif
    bool _begun;
  };

  #endif // Adafruit_SPIDevice_h


repo_name: wheelie
function_name: drawPixel
last_modified: 2025-11-11 15:52:59
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\Adafruit_GFX.h

content:
  #ifndef _ADAFRUIT_GFX_H
  #define _ADAFRUIT_GFX_H

  #if ARDUINO >= 100
  #include "Arduino.h"
  #include "Print.h"
  #else
  #include "WProgram.h"
  #endif
  #include "gfxfont.h"

  #include <Adafruit_I2CDevice.h>
  #include <Adafruit_SPIDevice.h>

  /// A generic graphics superclass that can handle all sorts of drawing. At a
  /// minimum you can subclass and provide drawPixel(). At a maximum you can do a
  /// ton of overriding to optimize. Used for any/all Adafruit displays!
  class Adafruit_GFX : public Print {

  public:
    Adafruit_GFX(int16_t w, int16_t h); // Constructor

    /**********************************************************************/
    /*!
      @brief  Draw to the screen/framebuffer/etc.
      Must be overridden in subclass.
      @param  x    X coordinate in pixels
      @param  y    Y coordinate in pixels
      @param color  16-bit pixel color.
    */
    /**********************************************************************/
    virtual void drawPixel(int16_t x, int16_t y, uint16_t color) = 0;

    // TRANSACTION API / CORE DRAW API
    // These MAY be overridden by the subclass to provide device-specific
    // optimized code.  Otherwise 'generic' versions are used.
    virtual void startWrite(void);
    virtual void writePixel(int16_t x, int16_t y, uint16_t color);
    virtual void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                               uint16_t color);
    virtual void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    virtual void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    virtual void writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                           uint16_t color);
    virtual void endWrite(void);

    // CONTROL API
    // These MAY be overridden by the subclass to provide device-specific
    // optimized code.  Otherwise 'generic' versions are used.
    virtual void setRotation(uint8_t r);
    virtual void invertDisplay(bool i);

    // BASIC DRAW API
    // These MAY be overridden by the subclass to provide device-specific
    // optimized code.  Otherwise 'generic' versions are used.

    // It's good to implement those, even if using transaction API
    virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                          uint16_t color);
    virtual void fillScreen(uint16_t color);
    // Optional and probably not necessary to change
    virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                          uint16_t color);
    virtual void drawRect(int16_t x, int16_t y, int16_t w, int16_t h,
                          uint16_t color);

    // These exist only with Adafruit_GFX (no subclass overrides)
    void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
    void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
                          uint16_t color);
    void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
    void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
                          int16_t delta, uint16_t color);
    void drawEllipse(int16_t x0, int16_t y0, int16_t rw, int16_t rh,
                     uint16_t color);
    void fillEllipse(int16_t x0, int16_t y0, int16_t rw, int16_t rh,
                     uint16_t color);
    void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
                      int16_t y2, uint16_t color);
    void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
                      int16_t y2, uint16_t color);
    void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
                       int16_t radius, uint16_t color);
    void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
                       int16_t radius, uint16_t color);
    void drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w,
                    int16_t h, uint16_t color);
    void drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w,
                    int16_t h, uint16_t color, uint16_t bg);
    void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h,
                    uint16_t color);
    void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h,
                    uint16_t color, uint16_t bg);
    void drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w,
                     int16_t h, uint16_t color);
    void drawGrayscaleBitmap(int16_t x, int16_t y, const uint8_t bitmap[],
                             int16_t w, int16_t h);
    void drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w,
                             int16_t h);
    void drawGrayscaleBitmap(int16_t x, int16_t y, const uint8_t bitmap[],
                             const uint8_t mask[], int16_t w, int16_t h);
    void drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, uint8_t *mask,
                             int16_t w, int16_t h);
    void drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w,
                       int16_t h);
    void drawRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap, int16_t w,
                       int16_t h);
    void drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[],
                       const uint8_t mask[], int16_t w, int16_t h);
    void drawRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap, uint8_t *mask,
                       int16_t w, int16_t h);
    void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                  uint16_t bg, uint8_t size);
    void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                  uint16_t bg, uint8_t size_x, uint8_t size_y);
    void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1,
                       int16_t *y1, uint16_t *w, uint16_t *h);
    void getTextBounds(const __FlashStringHelper *s, int16_t x, int16_t y,
                       int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
    void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1,
                       int16_t *y1, uint16_t *w, uint16_t *h);
    void setTextSize(uint8_t s);
    void setTextSize(uint8_t sx, uint8_t sy);
    void setFont(const GFXfont *f = NULL);

    /**********************************************************************/
    /*!
      @brief  Set text cursor location
      @param  x    X coordinate in pixels
      @param  y    Y coordinate in pixels
    */
    /**********************************************************************/
    void setCursor(int16_t x, int16_t y) {
      cursor_x = x;
      cursor_y = y;
    }

    /**********************************************************************/
    /*!
      @brief   Set text font color with transparant background
      @param   c   16-bit 5-6-5 Color to draw text with
      @note    For 'transparent' background, background and foreground
               are set to same color rather than using a separate flag.
    */
    /**********************************************************************/
    void setTextColor(uint16_t c) { textcolor = textbgcolor = c; }

    /**********************************************************************/
    /*!
      @brief   Set text font color with custom background color
      @param   c   16-bit 5-6-5 Color to draw text with
      @param   bg  16-bit 5-6-5 Color to draw background/fill with
    */
    /**********************************************************************/
    void setTextColor(uint16_t c, uint16_t bg) {
      textcolor = c;
      textbgcolor = bg;
    }

    /**********************************************************************/
    /*!
    @brief  Set whether text that is too long for the screen width should
            automatically wrap around to the next line (else clip right).
    @param  w  true for wrapping, false for clipping
    */
    /**********************************************************************/
    void setTextWrap(bool w) { wrap = w; }

    /**********************************************************************/
    /*!
      @brief  Enable (or disable) Code Page 437-compatible charset.
              There was an error in glcdfont.c for the longest time -- one
              character (#176, the 'light shade' block) was missing -- this
              threw off the index of every character that followed it.
              But a TON of code has been written with the erroneous
              character indices. By default, the library uses the original
              'wrong' behavior and old sketches will still work. Pass
              'true' to this function to use correct CP437 character values
              in your code.
      @param  x  true = enable (new behavior), false = disable (old behavior)
    */
    /**********************************************************************/
    void cp437(bool x = true) { _cp437 = x; }

    using Print::write;
  #if ARDUINO >= 100
    virtual size_t write(uint8_t);
  #else
    virtual void write(uint8_t);
  #endif

    /************************************************************************/
    /*!
      @brief      Get width of the display, accounting for current rotation
      @returns    Width in pixels
    */
    /************************************************************************/
    int16_t width(void) const { return _width; };

    /************************************************************************/
    /*!
      @brief      Get height of the display, accounting for current rotation
      @returns    Height in pixels
    */
    /************************************************************************/
    int16_t height(void) const { return _height; }

    /************************************************************************/
    /*!
      @brief      Get rotation setting for display
      @returns    0 thru 3 corresponding to 4 cardinal rotations
    */
    /************************************************************************/
    uint8_t getRotation(void) const { return rotation; }

    // get current cursor position (get rotation safe maximum values,
    // using: width() for x, height() for y)
    /************************************************************************/
    /*!
      @brief  Get text cursor X location
      @returns    X coordinate in pixels
    */
    /************************************************************************/
    int16_t getCursorX(void) const { return cursor_x; }

    /************************************************************************/
    /*!
      @brief      Get text cursor Y location
      @returns    Y coordinate in pixels
    */
    /************************************************************************/
    int16_t getCursorY(void) const { return cursor_y; };

  protected:
    void charBounds(unsigned char c, int16_t *x, int16_t *y, int16_t *minx,
                    int16_t *miny, int16_t *maxx, int16_t *maxy);
    int16_t WIDTH;        ///< This is the 'raw' display width - never changes
    int16_t HEIGHT;       ///< This is the 'raw' display height - never changes
    int16_t _width;       ///< Display width as modified by current rotation
    int16_t _height;      ///< Display height as modified by current rotation
    int16_t cursor_x;     ///< x location to start print()ing text
    int16_t cursor_y;     ///< y location to start print()ing text
    uint16_t textcolor;   ///< 16-bit background color for print()
    uint16_t textbgcolor; ///< 16-bit text color for print()
    uint8_t textsize_x;   ///< Desired magnification in X-axis of text to print()
    uint8_t textsize_y;   ///< Desired magnification in Y-axis of text to print()
    uint8_t rotation;     ///< Display rotation (0 thru 3)
    bool wrap;            ///< If set, 'wrap' text at right edge of display
    bool _cp437;          ///< If set, use correct CP437 charset (default is off)
    GFXfont *gfxFont;     ///< Pointer to special font
  };

  /// A simple drawn button UI element
  class Adafruit_GFX_Button {

  public:
    Adafruit_GFX_Button(void);
    // "Classic" initButton() uses center & size
    void initButton(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t w,
                    uint16_t h, uint16_t outline, uint16_t fill,
                    uint16_t textcolor, char *label, uint8_t textsize);
    void initButton(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t w,
                    uint16_t h, uint16_t outline, uint16_t fill,
                    uint16_t textcolor, char *label, uint8_t textsize_x,
                    uint8_t textsize_y);
    // New/alt initButton() uses upper-left corner & size
    void initButtonUL(Adafruit_GFX *gfx, int16_t x1, int16_t y1, uint16_t w,
                      uint16_t h, uint16_t outline, uint16_t fill,
                      uint16_t textcolor, char *label, uint8_t textsize);
    void initButtonUL(Adafruit_GFX *gfx, int16_t x1, int16_t y1, uint16_t w,
                      uint16_t h, uint16_t outline, uint16_t fill,
                      uint16_t textcolor, char *label, uint8_t textsize_x,
                      uint8_t textsize_y);
    void drawButton(bool inverted = false);
    bool contains(int16_t x, int16_t y);

    /**********************************************************************/
    /*!
      @brief    Sets button state, should be done by some touch function
      @param    p  True for pressed, false for not.
    */
    /**********************************************************************/
    void press(bool p) {
      laststate = currstate;
      currstate = p;
    }

    bool justPressed();
    bool justReleased();

    /**********************************************************************/
    /*!
      @brief    Query whether the button is currently pressed
      @returns  True if pressed
    */
    /**********************************************************************/
    bool isPressed(void) { return currstate; };

  private:
    Adafruit_GFX *_gfx;
    int16_t _x1, _y1; // Coordinates of top-left corner
    uint16_t _w, _h;
    uint8_t _textsize_x;
    uint8_t _textsize_y;
    uint16_t _outlinecolor, _fillcolor, _textcolor;
    char _label[10];

    bool currstate, laststate;
  };

  /// A GFX 1-bit canvas context for graphics
  class GFXcanvas1 : public Adafruit_GFX {
  public:
    GFXcanvas1(uint16_t w, uint16_t h, bool allocate_buffer = true);
    ~GFXcanvas1(void);
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    void fillScreen(uint16_t color);
    void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    bool getPixel(int16_t x, int16_t y) const;
    /**********************************************************************/
    /*!
      @brief    Get a pointer to the internal buffer memory
      @returns  A pointer to the allocated buffer
    */
    /**********************************************************************/
    uint8_t *getBuffer(void) const { return buffer; }

  protected:
    bool getRawPixel(int16_t x, int16_t y) const;
    void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    uint8_t *buffer;   ///< Raster data: no longer private, allow subclass access
    bool buffer_owned; ///< If true, destructor will free buffer, else it will do
                       ///< nothing

  private:
  #ifdef __AVR__
    // Bitmask tables of 0x80>>X and ~(0x80>>X), because X>>Y is slow on AVR
    static const uint8_t PROGMEM GFXsetBit[], GFXclrBit[];
  #endif
  };

  /// A GFX 8-bit canvas context for graphics
  class GFXcanvas8 : public Adafruit_GFX {
  public:
    GFXcanvas8(uint16_t w, uint16_t h, bool allocate_buffer = true);
    ~GFXcanvas8(void);
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    void fillScreen(uint16_t color);
    void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    uint8_t getPixel(int16_t x, int16_t y) const;
    /**********************************************************************/
    /*!
     @brief    Get a pointer to the internal buffer memory
     @returns  A pointer to the allocated buffer
    */
    /**********************************************************************/
    uint8_t *getBuffer(void) const { return buffer; }

  protected:
    uint8_t getRawPixel(int16_t x, int16_t y) const;
    void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    uint8_t *buffer;   ///< Raster data: no longer private, allow subclass access
    bool buffer_owned; ///< If true, destructor will free buffer, else it will do
                       ///< nothing
  };

  ///  A GFX 16-bit canvas context for graphics
  class GFXcanvas16 : public Adafruit_GFX {
  public:
    GFXcanvas16(uint16_t w, uint16_t h, bool allocate_buffer = true);
    ~GFXcanvas16(void);
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    void fillScreen(uint16_t color);
    void byteSwap(void);
    void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    uint16_t getPixel(int16_t x, int16_t y) const;
    /**********************************************************************/
    /*!
      @brief    Get a pointer to the internal buffer memory
      @returns  A pointer to the allocated buffer
    */
    /**********************************************************************/
    uint16_t *getBuffer(void) const { return buffer; }

  protected:
    uint16_t getRawPixel(int16_t x, int16_t y) const;
    void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    uint16_t *buffer;  ///< Raster data: no longer private, allow subclass access
    bool buffer_owned; ///< If true, destructor will free buffer, else it will do
                       ///< nothing
  };

  #endif // _ADAFRUIT_GFX_H


repo_name: wheelie
function_name: display
last_modified: 2025-11-11 15:52:59
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\Adafruit_GrayOLED.h

content:
  /*!
   * @file Adafruit_GrayOLED.h
   *
   * This is part of for Adafruit's GFX library, supplying generic support
   * for grayscale OLED displays: http://www.adafruit.com/category/63_98
   *
   * These displays use I2C or SPI to communicate. I2C requires 2 pins
   * (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
   * select, data/command) and optionally a reset pin. Hardware SPI or
   * 'bitbang' software SPI are both supported.
   *
   * Adafruit invests time and resources providing this open source code,
   * please support Adafruit and open-source hardware by purchasing
   * products from Adafruit!
   *
   * Written by Limor Fried/Ladyada for Adafruit Industries, with
   * contributions from the open source community.
   *
   * BSD license, all text above, and the splash screen header file,
   * must be included in any redistribution.
   *
   */

  #ifndef _Adafruit_GRAYOLED_H_
  #define _Adafruit_GRAYOLED_H_

  // Not for ATtiny, at all
  #if !defined(__AVR_ATtiny85__) && !defined(__AVR_ATtiny84__)

  #include <Adafruit_GFX.h>
  #include <Adafruit_I2CDevice.h>
  #include <Adafruit_SPIDevice.h>
  #include <SPI.h>
  #include <Wire.h>

  #define GRAYOLED_SETCONTRAST 0x81   ///< Generic contrast for almost all OLEDs
  #define GRAYOLED_NORMALDISPLAY 0xA6 ///< Generic non-invert for almost all OLEDs
  #define GRAYOLED_INVERTDISPLAY 0xA7 ///< Generic invert for almost all OLEDs

  #define MONOOLED_BLACK 0   ///< Default black 'color' for monochrome OLEDS
  #define MONOOLED_WHITE 1   ///< Default white 'color' for monochrome OLEDS
  #define MONOOLED_INVERSE 2 ///< Default inversion command for monochrome OLEDS

  /*!
      @brief  Class that stores state and functions for interacting with
              generic grayscale OLED displays.
  */
  class Adafruit_GrayOLED : public Adafruit_GFX {
  public:
    Adafruit_GrayOLED(uint8_t bpp, uint16_t w, uint16_t h, TwoWire *twi = &Wire,
                      int16_t rst_pin = -1, uint32_t preclk = 400000,
                      uint32_t postclk = 100000);
    Adafruit_GrayOLED(uint8_t bpp, uint16_t w, uint16_t h, int16_t mosi_pin,
                      int16_t sclk_pin, int16_t dc_pin, int16_t rst_pin,
                      int16_t cs_pin);
    Adafruit_GrayOLED(uint8_t bpp, uint16_t w, uint16_t h, SPIClass *spi,
                      int16_t dc_pin, int16_t rst_pin, int16_t cs_pin,
                      uint32_t bitrate = 8000000UL);

    ~Adafruit_GrayOLED(void);

    /**
     @brief The function that sub-classes define that writes out the buffer to
     the display over I2C or SPI
     **/
    virtual void display(void) = 0;
    void clearDisplay(void);
    void invertDisplay(bool i);
    void setContrast(uint8_t contrastlevel);
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    bool getPixel(int16_t x, int16_t y);
    uint8_t *getBuffer(void);

    void oled_command(uint8_t c);
    bool oled_commandList(const uint8_t *c, uint8_t n);

  protected:
    bool _init(uint8_t i2caddr = 0x3C, bool reset = true);

    Adafruit_SPIDevice *spi_dev = NULL; ///< The SPI interface BusIO device
    Adafruit_I2CDevice *i2c_dev = NULL; ///< The I2C interface BusIO device
    int32_t i2c_preclk = 400000,        ///< Configurable 'high speed' I2C rate
        i2c_postclk = 100000;           ///< Configurable 'low speed' I2C rate
    uint8_t *buffer = NULL; ///< Internal 1:1 framebuffer of display mem

    int16_t window_x1, ///< Dirty tracking window minimum x
        window_y1,     ///< Dirty tracking window minimum y
        window_x2,     ///< Dirty tracking window maximum x
        window_y2;     ///< Dirty tracking window maximum y

    int dcPin,  ///< The Arduino pin connected to D/C (for SPI)
        csPin,  ///< The Arduino pin connected to CS (for SPI)
        rstPin; ///< The Arduino pin connected to reset (-1 if unused)

    uint8_t _bpp = 1; ///< Bits per pixel color for this display
  private:
    TwoWire *_theWire = NULL; ///< The underlying hardware I2C
  };

  #endif // end __AVR_ATtiny85__ __AVR_ATtiny84__
  #endif // _Adafruit_GrayOLED_H_


repo_name: wheelie
function_name: dma_callback
last_modified: 2025-11-11 15:52:59
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\Adafruit_SPITFT.cpp

content:
  /*!
   * @file Adafruit_SPITFT.cpp
   *
   * @mainpage Adafruit SPI TFT Displays (and some others)
   *
   * @section intro_sec Introduction
   *
   * Part of Adafruit's GFX graphics library. Originally this class was
   * written to handle a range of color TFT displays connected via SPI,
   * but over time this library and some display-specific subclasses have
   * mutated to include some color OLEDs as well as parallel-interfaced
   * displays. The name's been kept for the sake of older code.
   *
   * Adafruit invests time and resources providing this open source code,
   * please support Adafruit and open-source hardware by purchasing
   * products from Adafruit!

   * @section dependencies Dependencies
   *
   * This library depends on
   * <a href="https://github.com/adafruit/Adafruit-GFX-Library">Adafruit_GFX</a>
   * being present on your system. Please make sure you have installed the latest
   * version before using this library.
   *
   * @section author Author
   *
   * Written by Limor "ladyada" Fried for Adafruit Industries,
   * with contributions from the open source community.
   *
   * @section license License
   *
   * BSD license, all text here must be included in any redistribution.
   */

  // Not for ATtiny, at all
  #if !defined(__AVR_ATtiny85__) && !defined(__AVR_ATtiny84__)

  #include "Adafruit_SPITFT.h"

  #if defined(__AVR__)
  #if defined(__AVR_XMEGA__) // only tested with __AVR_ATmega4809__
  #define AVR_WRITESPI(x)                                                        \
    for (SPI0_DATA = (x); (!(SPI0_INTFLAGS & _BV(SPI_IF_bp)));)
  #elif defined(__LGT8F__)
  #define AVR_WRITESPI(x)                                                        \
    SPDR = (x);                                                                  \
    asm volatile("nop");                                                         \
    while ((SPFR & _BV(RDEMPT)))                                                 \
      ;                                                                          \
    SPFR = _BV(RDEMPT) | _BV(WREMPT)
  #else
  #define AVR_WRITESPI(x) for (SPDR = (x); (!(SPSR & _BV(SPIF)));)
  #endif
  #endif

  #if defined(PORT_IOBUS)
  // On SAMD21, redefine digitalPinToPort() to use the slightly-faster
  // PORT_IOBUS rather than PORT (not needed on SAMD51).
  #undef digitalPinToPort
  #define digitalPinToPort(P) (&(PORT_IOBUS->Group[g_APinDescription[P].ulPort]))
  #endif // end PORT_IOBUS

  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
  // #pragma message ("GFX DMA IS ENABLED. HIGHLY EXPERIMENTAL.")
  #include "wiring_private.h" // pinPeripheral() function
  #include <Adafruit_ZeroDMA.h>
  #include <malloc.h>          // memalign() function
  #define tcNum 2              // Timer/Counter for parallel write strobe PWM
  #define wrPeripheral PIO_CCL // Use CCL to invert write strobe

  // DMA transfer-in-progress indicator and callback
  static volatile bool dma_busy = false;
  static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }

  #if defined(__SAMD51__)
  // Timer/counter info by index #
  static const struct {
    Tc *tc;   // -> Timer/Counter base address
    int gclk; // GCLK ID
    int evu;  // EVSYS user ID
  } tcList[] = {{TC0, TC0_GCLK_ID, EVSYS_ID_USER_TC0_EVU},
                {TC1, TC1_GCLK_ID, EVSYS_ID_USER_TC1_EVU},
                {TC2, TC2_GCLK_ID, EVSYS_ID_USER_TC2_EVU},
                {TC3, TC3_GCLK_ID, EVSYS_ID_USER_TC3_EVU},
  #if defined(TC4)
                {TC4, TC4_GCLK_ID, EVSYS_ID_USER_TC4_EVU},
  #endif
  #if defined(TC5)
                {TC5, TC5_GCLK_ID, EVSYS_ID_USER_TC5_EVU},
  #endif
  #if defined(TC6)
                {TC6, TC6_GCLK_ID, EVSYS_ID_USER_TC6_EVU},
  #endif
  #if defined(TC7)
                {TC7, TC7_GCLK_ID, EVSYS_ID_USER_TC7_EVU}
  #endif
  };
  #define NUM_TIMERS (sizeof tcList / sizeof tcList[0]) ///< # timer/counters
  #endif                                                // end __SAMD51__

  #endif // end USE_SPI_DMA

  // Possible values for Adafruit_SPITFT.connection:
  #define TFT_HARD_SPI 0 ///< Display interface = hardware SPI
  #define TFT_SOFT_SPI 1 ///< Display interface = software SPI
  #define TFT_PARALLEL 2 ///< Display interface = 8- or 16-bit parallel

  // CONSTRUCTORS ------------------------------------------------------------

  /*!
      @brief   Adafruit_SPITFT constructor for software (bitbang) SPI.
      @param   w     Display width in pixels at default rotation setting (0).
      @param   h     Display height in pixels at default rotation setting (0).
      @param   cs    Arduino pin # for chip-select (-1 if unused, tie CS low).
      @param   dc    Arduino pin # for data/command select (required).
      @param   mosi  Arduino pin # for bitbang SPI MOSI signal (required).
      @param   sck   Arduino pin # for bitbang SPI SCK signal (required).
      @param   rst   Arduino pin # for display reset (optional, display reset
                     can be tied to MCU reset, default of -1 means unused).
      @param   miso  Arduino pin # for bitbang SPI MISO signal (optional,
                     -1 default, many displays don't support SPI read).
      @note    Output pins are not initialized; application typically will
               need to call subclass' begin() function, which in turn calls
               this library's initSPI() function to initialize pins.
  */
  Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
                                   int8_t mosi, int8_t sck, int8_t rst,
                                   int8_t miso)
      : Adafruit_GFX(w, h), connection(TFT_SOFT_SPI), _rst(rst), _cs(cs),
        _dc(dc) {
    swspi._sck = sck;
    swspi._mosi = mosi;
    swspi._miso = miso;
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(CORE_TEENSY)
  #if !defined(KINETISK)
    dcPinMask = digitalPinToBitMask(dc);
    swspi.sckPinMask = digitalPinToBitMask(sck);
    swspi.mosiPinMask = digitalPinToBitMask(mosi);
  #endif
    dcPortSet = portSetRegister(dc);
    dcPortClr = portClearRegister(dc);
    swspi.sckPortSet = portSetRegister(sck);
    swspi.sckPortClr = portClearRegister(sck);
    swspi.mosiPortSet = portSetRegister(mosi);
    swspi.mosiPortClr = portClearRegister(mosi);
    if (cs >= 0) {
  #if !defined(KINETISK)
      csPinMask = digitalPinToBitMask(cs);
  #endif
      csPortSet = portSetRegister(cs);
      csPortClr = portClearRegister(cs);
    } else {
  #if !defined(KINETISK)
      csPinMask = 0;
  #endif
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
    }
    if (miso >= 0) {
      swspi.misoPort = portInputRegister(miso);
  #if !defined(KINETISK)
      swspi.misoPinMask = digitalPinToBitMask(miso);
  #endif
    } else {
      swspi.misoPort = portInputRegister(dc);
    }
  #else  // !CORE_TEENSY
    dcPinMask = digitalPinToBitMask(dc);
    swspi.sckPinMask = digitalPinToBitMask(sck);
    swspi.mosiPinMask = digitalPinToBitMask(mosi);
    dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
    dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
    swspi.sckPortSet = &(PORT->Group[g_APinDescription[sck].ulPort].OUTSET.reg);
    swspi.sckPortClr = &(PORT->Group[g_APinDescription[sck].ulPort].OUTCLR.reg);
    swspi.mosiPortSet = &(PORT->Group[g_APinDescription[mosi].ulPort].OUTSET.reg);
    swspi.mosiPortClr = &(PORT->Group[g_APinDescription[mosi].ulPort].OUTCLR.reg);
    if (cs >= 0) {
      csPinMask = digitalPinToBitMask(cs);
      csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
      csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
      csPinMask = 0;
    }
    if (miso >= 0) {
      swspi.misoPinMask = digitalPinToBitMask(miso);
      swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(miso));
    } else {
      swspi.misoPinMask = 0;
      swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(dc));
    }
  #endif // end !CORE_TEENSY
  #else  // !HAS_PORT_SET_CLR
    dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
    dcPinMaskSet = digitalPinToBitMask(dc);
    swspi.sckPort = (PORTreg_t)portOutputRegister(digitalPinToPort(sck));
    swspi.sckPinMaskSet = digitalPinToBitMask(sck);
    swspi.mosiPort = (PORTreg_t)portOutputRegister(digitalPinToPort(mosi));
    swspi.mosiPinMaskSet = digitalPinToBitMask(mosi);
    if (cs >= 0) {
      csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
      csPinMaskSet = digitalPinToBitMask(cs);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPort = dcPort;
      csPinMaskSet = 0;
    }
    if (miso >= 0) {
      swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(miso));
      swspi.misoPinMask = digitalPinToBitMask(miso);
    } else {
      swspi.misoPort = (PORTreg_t)portInputRegister(digitalPinToPort(dc));
      swspi.misoPinMask = 0;
    }
    csPinMaskClr = ~csPinMaskSet;
    dcPinMaskClr = ~dcPinMaskSet;
    swspi.sckPinMaskClr = ~swspi.sckPinMaskSet;
    swspi.mosiPinMaskClr = ~swspi.mosiPinMaskSet;
  #endif // !end HAS_PORT_SET_CLR
  #endif // end USE_FAST_PINIO
  }

  /*!
      @brief   Adafruit_SPITFT constructor for hardware SPI using the board's
               default SPI peripheral.
      @param   w     Display width in pixels at default rotation setting (0).
      @param   h     Display height in pixels at default rotation setting (0).
      @param   cs    Arduino pin # for chip-select (-1 if unused, tie CS low).
      @param   dc    Arduino pin # for data/command select (required).
      @param   rst   Arduino pin # for display reset (optional, display reset
                     can be tied to MCU reset, default of -1 means unused).
      @note    Output pins are not initialized; application typically will
               need to call subclass' begin() function, which in turn calls
               this library's initSPI() function to initialize pins.
  */
  #if defined(ESP8266) // See notes below
  Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
                                   int8_t rst)
      : Adafruit_GFX(w, h), connection(TFT_HARD_SPI), _rst(rst), _cs(cs),
        _dc(dc) {
    hwspi._spi = &SPI;
  }
  #else  // !ESP8266
  Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
                                   int8_t rst)
      : Adafruit_SPITFT(w, h, &SPI, cs, dc, rst) {
    // This just invokes the hardware SPI constructor below,
    // passing the default SPI device (&SPI).
  }
  #endif // end !ESP8266

  #if !defined(ESP8266)
  // ESP8266 compiler freaks out at this constructor -- it can't disambiguate
  // beteween the SPIClass pointer (argument #3) and a regular integer.
  // Solution here it to just not offer this variant on the ESP8266. You can
  // use the default hardware SPI peripheral, or you can use software SPI,
  // but if there's any library out there that creates a 'virtual' SPIClass
  // peripheral and drives it with software bitbanging, that's not supported.
  /*!
      @brief   Adafruit_SPITFT constructor for hardware SPI using a specific
               SPI peripheral.
      @param   w         Display width in pixels at default rotation (0).
      @param   h         Display height in pixels at default rotation (0).
      @param   spiClass  Pointer to SPIClass type (e.g. &SPI or &SPI1).
      @param   cs        Arduino pin # for chip-select (-1 if unused, tie CS low).
      @param   dc        Arduino pin # for data/command select (required).
      @param   rst       Arduino pin # for display reset (optional, display reset
                         can be tied to MCU reset, default of -1 means unused).
      @note    Output pins are not initialized in constructor; application
               typically will need to call subclass' begin() function, which
               in turn calls this library's initSPI() function to initialize
               pins. EXCEPT...if you have built your own SERCOM SPI peripheral
               (calling the SPIClass constructor) rather than one of the
               built-in SPI devices (e.g. &SPI, &SPI1 and so forth), you will
               need to call the begin() function for your object as well as
               pinPeripheral() for the MOSI, MISO and SCK pins to configure
               GPIO manually. Do this BEFORE calling the display-specific
               begin or init function. Unfortunate but unavoidable.
  */
  Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, SPIClass *spiClass,
                                   int8_t cs, int8_t dc, int8_t rst)
      : Adafruit_GFX(w, h), connection(TFT_HARD_SPI), _rst(rst), _cs(cs),
        _dc(dc) {
    hwspi._spi = spiClass;
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(CORE_TEENSY)
  #if !defined(KINETISK)
    dcPinMask = digitalPinToBitMask(dc);
  #endif
    dcPortSet = portSetRegister(dc);
    dcPortClr = portClearRegister(dc);
    if (cs >= 0) {
  #if !defined(KINETISK)
      csPinMask = digitalPinToBitMask(cs);
  #endif
      csPortSet = portSetRegister(cs);
      csPortClr = portClearRegister(cs);
    } else { // see comments below
  #if !defined(KINETISK)
      csPinMask = 0;
  #endif
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
    }
  #else  // !CORE_TEENSY
    dcPinMask = digitalPinToBitMask(dc);
    dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
    dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
    if (cs >= 0) {
      csPinMask = digitalPinToBitMask(cs);
      csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
      csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
      csPinMask = 0;
    }
  #endif // end !CORE_TEENSY
  #else  // !HAS_PORT_SET_CLR
    dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
    dcPinMaskSet = digitalPinToBitMask(dc);
    if (cs >= 0) {
      csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
      csPinMaskSet = digitalPinToBitMask(cs);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPort = dcPort;
      csPinMaskSet = 0;
    }
    csPinMaskClr = ~csPinMaskSet;
    dcPinMaskClr = ~dcPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #endif // end USE_FAST_PINIO
  }
  #endif // end !ESP8266

  /*!
      @brief   Adafruit_SPITFT constructor for parallel display connection.
      @param   w         Display width in pixels at default rotation (0).
      @param   h         Display height in pixels at default rotation (0).
      @param   busWidth  If tft16 (enumeration in header file), is a 16-bit
                         parallel connection, else 8-bit.
                         16-bit isn't fully implemented or tested yet so
                         applications should pass "tft8bitbus" for now...needed to
                         stick a required enum argument in there to
                         disambiguate this constructor from the soft-SPI case.
                         Argument is ignored on 8-bit architectures (no 'wide'
                         support there since PORTs are 8 bits anyway).
      @param   d0        Arduino pin # for data bit 0 (1+ are extrapolated).
                         The 8 (or 16) data bits MUST be contiguous and byte-
                         aligned (or word-aligned for wide interface) within
                         the same PORT register (might not correspond to
                         Arduino pin sequence).
      @param   wr        Arduino pin # for write strobe (required).
      @param   dc        Arduino pin # for data/command select (required).
      @param   cs        Arduino pin # for chip-select (optional, -1 if unused,
                         tie CS low).
      @param   rst       Arduino pin # for display reset (optional, display reset
                         can be tied to MCU reset, default of -1 means unused).
      @param   rd        Arduino pin # for read strobe (optional, -1 if unused).
      @note    Output pins are not initialized; application typically will need
               to call subclass' begin() function, which in turn calls this
               library's initSPI() function to initialize pins.
               Yes, the name is a misnomer...this library originally handled
               only SPI displays, parallel being a recent addition (but not
               wanting to break existing code).
  */
  Adafruit_SPITFT::Adafruit_SPITFT(uint16_t w, uint16_t h, tftBusWidth busWidth,
                                   int8_t d0, int8_t wr, int8_t dc, int8_t cs,
                                   int8_t rst, int8_t rd)
      : Adafruit_GFX(w, h), connection(TFT_PARALLEL), _rst(rst), _cs(cs),
        _dc(dc) {
    tft8._d0 = d0;
    tft8._wr = wr;
    tft8._rd = rd;
    tft8.wide = (busWidth == tft16bitbus);
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(CORE_TEENSY)
    tft8.wrPortSet = portSetRegister(wr);
    tft8.wrPortClr = portClearRegister(wr);
  #if !defined(KINETISK)
    dcPinMask = digitalPinToBitMask(dc);
  #endif
    dcPortSet = portSetRegister(dc);
    dcPortClr = portClearRegister(dc);
    if (cs >= 0) {
  #if !defined(KINETISK)
      csPinMask = digitalPinToBitMask(cs);
  #endif
      csPortSet = portSetRegister(cs);
      csPortClr = portClearRegister(cs);
    } else { // see comments below
  #if !defined(KINETISK)
      csPinMask = 0;
  #endif
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
    }
    if (rd >= 0) { // if read-strobe pin specified...
  #if defined(KINETISK)
      tft8.rdPinMask = 1;
  #else // !KINETISK
      tft8.rdPinMask = digitalPinToBitMask(rd);
  #endif
      tft8.rdPortSet = portSetRegister(rd);
      tft8.rdPortClr = portClearRegister(rd);
    } else {
      tft8.rdPinMask = 0;
      tft8.rdPortSet = dcPortSet;
      tft8.rdPortClr = dcPortClr;
    }
    // These are all uint8_t* pointers -- elsewhere they're recast
    // as necessary if a 'wide' 16-bit interface is in use.
    tft8.writePort = portOutputRegister(d0);
    tft8.readPort = portInputRegister(d0);
    tft8.dirSet = portModeRegister(d0);
    tft8.dirClr = portModeRegister(d0);
  #else  // !CORE_TEENSY
    tft8.wrPinMask = digitalPinToBitMask(wr);
    tft8.wrPortSet = &(PORT->Group[g_APinDescription[wr].ulPort].OUTSET.reg);
    tft8.wrPortClr = &(PORT->Group[g_APinDescription[wr].ulPort].OUTCLR.reg);
    dcPinMask = digitalPinToBitMask(dc);
    dcPortSet = &(PORT->Group[g_APinDescription[dc].ulPort].OUTSET.reg);
    dcPortClr = &(PORT->Group[g_APinDescription[dc].ulPort].OUTCLR.reg);
    if (cs >= 0) {
      csPinMask = digitalPinToBitMask(cs);
      csPortSet = &(PORT->Group[g_APinDescription[cs].ulPort].OUTSET.reg);
      csPortClr = &(PORT->Group[g_APinDescription[cs].ulPort].OUTCLR.reg);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPortSet = dcPortSet;
      csPortClr = dcPortClr;
      csPinMask = 0;
    }
    if (rd >= 0) { // if read-strobe pin specified...
      tft8.rdPinMask = digitalPinToBitMask(rd);
      tft8.rdPortSet = &(PORT->Group[g_APinDescription[rd].ulPort].OUTSET.reg);
      tft8.rdPortClr = &(PORT->Group[g_APinDescription[rd].ulPort].OUTCLR.reg);
    } else {
      tft8.rdPinMask = 0;
      tft8.rdPortSet = dcPortSet;
      tft8.rdPortClr = dcPortClr;
    }
    // Get pointers to PORT write/read/dir bytes within 32-bit PORT
    uint8_t dBit = g_APinDescription[d0].ulPin; // d0 bit # in PORT
    PortGroup *p = (&(PORT->Group[g_APinDescription[d0].ulPort]));
    uint8_t offset = dBit / 8; // d[7:0] byte # within PORT
    if (tft8.wide)
      offset &= ~1; // d[15:8] byte # within PORT
    // These are all uint8_t* pointers -- elsewhere they're recast
    // as necessary if a 'wide' 16-bit interface is in use.
    tft8.writePort = (volatile uint8_t *)&(p->OUT.reg) + offset;
    tft8.readPort = (volatile uint8_t *)&(p->IN.reg) + offset;
    tft8.dirSet = (volatile uint8_t *)&(p->DIRSET.reg) + offset;
    tft8.dirClr = (volatile uint8_t *)&(p->DIRCLR.reg) + offset;
  #endif // end !CORE_TEENSY
  #else  // !HAS_PORT_SET_CLR
    tft8.wrPort = (PORTreg_t)portOutputRegister(digitalPinToPort(wr));
    tft8.wrPinMaskSet = digitalPinToBitMask(wr);
    dcPort = (PORTreg_t)portOutputRegister(digitalPinToPort(dc));
    dcPinMaskSet = digitalPinToBitMask(dc);
    if (cs >= 0) {
      csPort = (PORTreg_t)portOutputRegister(digitalPinToPort(cs));
      csPinMaskSet = digitalPinToBitMask(cs);
    } else {
      // No chip-select line defined; might be permanently tied to GND.
      // Assign a valid GPIO register (though not used for CS), and an
      // empty pin bitmask...the nonsense bit-twiddling might be faster
      // than checking _cs and possibly branching.
      csPort = dcPort;
      csPinMaskSet = 0;
    }
    if (rd >= 0) { // if read-strobe pin specified...
      tft8.rdPort = (PORTreg_t)portOutputRegister(digitalPinToPort(rd));
      tft8.rdPinMaskSet = digitalPinToBitMask(rd);
    } else {
      tft8.rdPort = dcPort;
      tft8.rdPinMaskSet = 0;
    }
    csPinMaskClr = ~csPinMaskSet;
    dcPinMaskClr = ~dcPinMaskSet;
    tft8.wrPinMaskClr = ~tft8.wrPinMaskSet;
    tft8.rdPinMaskClr = ~tft8.rdPinMaskSet;
    tft8.writePort = (PORTreg_t)portOutputRegister(digitalPinToPort(d0));
    tft8.readPort = (PORTreg_t)portInputRegister(digitalPinToPort(d0));
    tft8.portDir = (PORTreg_t)portModeRegister(digitalPinToPort(d0));
  #endif // end !HAS_PORT_SET_CLR
  #endif // end USE_FAST_PINIO
  }

  // end constructors -------

  // CLASS MEMBER FUNCTIONS --------------------------------------------------

  // begin() and setAddrWindow() MUST be declared by any subclass.

  /*!
      @brief  Configure microcontroller pins for TFT interfacing. Typically
              called by a subclass' begin() function.
      @param  freq     SPI frequency when using hardware SPI. If default (0)
                       is passed, will fall back on a device-specific value.
                       Value is ignored when using software SPI or parallel
                       connection.
      @param  spiMode  SPI mode when using hardware SPI. MUST be one of the
                       values SPI_MODE0, SPI_MODE1, SPI_MODE2 or SPI_MODE3
                       defined in SPI.h. Do NOT attempt to pass '0' for
                       SPI_MODE0 and so forth...the values are NOT the same!
                       Use ONLY the defines! (Pity it's not an enum.)
      @note   Another anachronistically-named function; this is called even
              when the display connection is parallel (not SPI). Also, this
              could probably be made private...quite a few class functions
              were generously put in the public section.
  */
  void Adafruit_SPITFT::initSPI(uint32_t freq, uint8_t spiMode) {

    if (!freq)
      freq = DEFAULT_SPI_FREQ; // If no freq specified, use default

    // Init basic control pins common to all connection types
    if (_cs >= 0) {
      pinMode(_cs, OUTPUT);
      digitalWrite(_cs, HIGH); // Deselect
    }
    pinMode(_dc, OUTPUT);
    digitalWrite(_dc, HIGH); // Data mode

    if (connection == TFT_HARD_SPI) {

  #if defined(SPI_HAS_TRANSACTION)
      hwspi.settings = SPISettings(freq, MSBFIRST, spiMode);
  #else
      hwspi._freq = freq; // Save freq value for later
  #endif
      hwspi._mode = spiMode; // Save spiMode value for later
      // Call hwspi._spi->begin() ONLY if this is among the 'established'
      // SPI interfaces in variant.h. For DIY roll-your-own SERCOM SPIs,
      // begin() and pinPeripheral() calls MUST be made in one's calling
      // code, BEFORE the screen-specific begin/init function is called.
      // Reason for this is that SPI::begin() makes its own calls to
      // pinPeripheral() based on g_APinDescription[n].ulPinType, which
      // on non-established SPI interface pins will always be PIO_DIGITAL
      // or similar, while we need PIO_SERCOM or PIO_SERCOM_ALT...it's
      // highly unique between devices and variants for each pin or
      // SERCOM so we can't make those calls ourselves here. And the SPI
      // device needs to be set up before calling this because it's
      // immediately followed with initialization commands. Blargh.
      if (
  #if !defined(SPI_INTERFACES_COUNT)
          1
  #else
  #if SPI_INTERFACES_COUNT > 0
          (hwspi._spi == &SPI)
  #endif
  #if SPI_INTERFACES_COUNT > 1
          || (hwspi._spi == &SPI1)
  #endif
  #if SPI_INTERFACES_COUNT > 2
          || (hwspi._spi == &SPI2)
  #endif
  #if SPI_INTERFACES_COUNT > 3
          || (hwspi._spi == &SPI3)
  #endif
  #if SPI_INTERFACES_COUNT > 4
          || (hwspi._spi == &SPI4)
  #endif
  #if SPI_INTERFACES_COUNT > 5
          || (hwspi._spi == &SPI5)
  #endif
  #endif // end SPI_INTERFACES_COUNT
      ) {
        hwspi._spi->begin();
      }
    } else if (connection == TFT_SOFT_SPI) {

      pinMode(swspi._mosi, OUTPUT);
      digitalWrite(swspi._mosi, LOW);
      pinMode(swspi._sck, OUTPUT);
      digitalWrite(swspi._sck, LOW);
      if (swspi._miso >= 0) {
        pinMode(swspi._miso, INPUT);
      }

    } else { // TFT_PARALLEL
             // Initialize data pins.  We were only passed d0, so scan
             // the pin description list looking for the other pins.
             // They'll be on the same PORT, and within the next 7 (or 15) bits
             // (because we need to write to a contiguous PORT byte or word).
  #if defined(__AVR__)
      // PORT registers are 8 bits wide, so just need a register match...
      for (uint8_t i = 0; i < NUM_DIGITAL_PINS; i++) {
        if ((PORTreg_t)portOutputRegister(digitalPinToPort(i)) ==
            tft8.writePort) {
          pinMode(i, OUTPUT);
          digitalWrite(i, LOW);
        }
      }
  #elif defined(USE_FAST_PINIO)
  #if defined(CORE_TEENSY)
      if (!tft8.wide) {
        *tft8.dirSet = 0xFF;    // Set port to output
        *tft8.writePort = 0x00; // Write all 0s
      } else {
        *(volatile uint16_t *)tft8.dirSet = 0xFFFF;
        *(volatile uint16_t *)tft8.writePort = 0x0000;
      }
  #else  // !CORE_TEENSY
      uint8_t portNum = g_APinDescription[tft8._d0].ulPort, // d0 PORT #
          dBit = g_APinDescription[tft8._d0].ulPin,         // d0 bit in PORT
          lastBit = dBit + (tft8.wide ? 15 : 7);
      for (uint8_t i = 0; i < PINS_COUNT; i++) {
        if ((g_APinDescription[i].ulPort == portNum) &&
            (g_APinDescription[i].ulPin >= dBit) &&
            (g_APinDescription[i].ulPin <= (uint32_t)lastBit)) {
          pinMode(i, OUTPUT);
          digitalWrite(i, LOW);
        }
      }
  #endif // end !CORE_TEENSY
  #endif
      pinMode(tft8._wr, OUTPUT);
      digitalWrite(tft8._wr, HIGH);
      if (tft8._rd >= 0) {
        pinMode(tft8._rd, OUTPUT);
        digitalWrite(tft8._rd, HIGH);
      }
    }

    if (_rst >= 0) {
      // Toggle _rst low to reset
      pinMode(_rst, OUTPUT);
      digitalWrite(_rst, HIGH);
      delay(100);
      digitalWrite(_rst, LOW);
      delay(100);
      digitalWrite(_rst, HIGH);
      delay(200);
    }

  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
    if (((connection == TFT_HARD_SPI) || (connection == TFT_PARALLEL)) &&
        (dma.allocate() == DMA_STATUS_OK)) { // Allocate channel
      // The DMA library needs to alloc at least one valid descriptor,
      // so we do that here. It's not used in the usual sense though,
      // just before a transfer we copy descriptor[0] to this address.
      if (dptr = dma.addDescriptor(NULL, NULL, 42, DMA_BEAT_SIZE_BYTE, false,
                                   false)) {
        // Alloc 2 scanlines worth of pixels on display's major axis,
        // whichever that is, rounding each up to 2-pixel boundary.
        int major = (WIDTH > HEIGHT) ? WIDTH : HEIGHT;
        major += (major & 1);   // -> next 2-pixel bound, if needed.
        maxFillLen = major * 2; // 2 scanlines
        // Note to future self: if you decide to make the pixel buffer
        // much larger, remember that DMA transfer descriptors can't
        // exceed 65,535 bytes (not 65,536), meaning 32,767 pixels max.
        // Not that we have that kind of RAM to throw around right now.
        if ((pixelBuf[0] = (uint16_t *)malloc(maxFillLen * sizeof(uint16_t)))) {
          // Alloc OK. Get pointer to start of second scanline.
          pixelBuf[1] = &pixelBuf[0][major];
          // Determine number of DMA descriptors needed to cover
          // entire screen when entire 2-line pixelBuf is used
          // (round up for fractional last descriptor).
          int numDescriptors = (WIDTH * HEIGHT + (maxFillLen - 1)) / maxFillLen;
          // DMA descriptors MUST be 128-bit (16 byte) aligned.
          // memalign() is considered obsolete but it's replacements
          // (aligned_alloc() or posix_memalign()) are not currently
          // available in the version of ARM GCC in use, but this
          // is, so here we are.
          if ((descriptor = (DmacDescriptor *)memalign(
                   16, numDescriptors * sizeof(DmacDescriptor)))) {
            int dmac_id;
            volatile uint32_t *data_reg;

            if (connection == TFT_HARD_SPI) {
              // THIS IS AN AFFRONT TO NATURE, but I don't know
              // any "clean" way to get the sercom number from the
              // the SPIClass pointer (e.g. &SPI or &SPI1), which
              // is all we have to work with. SPIClass does contain
              // a SERCOM pointer but it is a PRIVATE member!
              // Doing an UNSPEAKABLY HORRIBLE THING here, directly
              // accessing the first 32-bit value in the SPIClass
              // structure, knowing that's (currently) where the
              // SERCOM pointer lives, but this ENTIRELY DEPENDS on
              // that structure not changing nor the compiler
              // rearranging things. Oh the humanity!

              if (*(SERCOM **)hwspi._spi == &sercom0) {
                dmac_id = SERCOM0_DMAC_ID_TX;
                data_reg = &SERCOM0->SPI.DATA.reg;
  #if defined SERCOM1
              } else if (*(SERCOM **)hwspi._spi == &sercom1) {
                dmac_id = SERCOM1_DMAC_ID_TX;
                data_reg = &SERCOM1->SPI.DATA.reg;
  #endif
  #if defined SERCOM2
              } else if (*(SERCOM **)hwspi._spi == &sercom2) {
                dmac_id = SERCOM2_DMAC_ID_TX;
                data_reg = &SERCOM2->SPI.DATA.reg;
  #endif
  #if defined SERCOM3
              } else if (*(SERCOM **)hwspi._spi == &sercom3) {
                dmac_id = SERCOM3_DMAC_ID_TX;
                data_reg = &SERCOM3->SPI.DATA.reg;
  #endif
  #if defined SERCOM4
              } else if (*(SERCOM **)hwspi._spi == &sercom4) {
                dmac_id = SERCOM4_DMAC_ID_TX;
                data_reg = &SERCOM4->SPI.DATA.reg;
  #endif
  #if defined SERCOM5
              } else if (*(SERCOM **)hwspi._spi == &sercom5) {
                dmac_id = SERCOM5_DMAC_ID_TX;
                data_reg = &SERCOM5->SPI.DATA.reg;
  #endif
  #if defined SERCOM6
              } else if (*(SERCOM **)hwspi._spi == &sercom6) {
                dmac_id = SERCOM6_DMAC_ID_TX;
                data_reg = &SERCOM6->SPI.DATA.reg;
  #endif
  #if defined SERCOM7
              } else if (*(SERCOM **)hwspi._spi == &sercom7) {
                dmac_id = SERCOM7_DMAC_ID_TX;
                data_reg = &SERCOM7->SPI.DATA.reg;
  #endif
              }
              dma.setPriority(DMA_PRIORITY_3);
              dma.setTrigger(dmac_id);
              dma.setAction(DMA_TRIGGER_ACTON_BEAT);

              // Initialize descriptor list.
              for (int d = 0; d < numDescriptors; d++) {
                // No need to set SRCADDR, DESCADDR or BTCNT --
                // those are done in the pixel-writing functions.
                descriptor[d].BTCTRL.bit.VALID = true;
                descriptor[d].BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE;
                descriptor[d].BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
                descriptor[d].BTCTRL.bit.BEATSIZE = DMA_BEAT_SIZE_BYTE;
                descriptor[d].BTCTRL.bit.DSTINC = 0;
                descriptor[d].BTCTRL.bit.STEPSEL = DMA_STEPSEL_SRC;
                descriptor[d].BTCTRL.bit.STEPSIZE =
                    DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
                descriptor[d].DSTADDR.reg = (uint32_t)data_reg;
              }

            } else { // Parallel connection

  #if defined(__SAMD51__)
              int dmaChannel = dma.getChannel();
              // Enable event output, use EVOSEL output
              DMAC->Channel[dmaChannel].CHEVCTRL.bit.EVOE = 1;
              DMAC->Channel[dmaChannel].CHEVCTRL.bit.EVOMODE = 0;

              // CONFIGURE TIMER/COUNTER (for write strobe)

              Tc *timer = tcList[tcNum].tc; // -> Timer struct
              int id = tcList[tcNum].gclk;  // Timer GCLK ID
              GCLK_PCHCTRL_Type pchctrl;

              // Set up timer clock source from GCLK
              GCLK->PCHCTRL[id].bit.CHEN = 0; // Stop timer
              while (GCLK->PCHCTRL[id].bit.CHEN)
                ; // Wait for it
              pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK0_Val;
              pchctrl.bit.CHEN = 1; // Enable
              GCLK->PCHCTRL[id].reg = pchctrl.reg;
              while (!GCLK->PCHCTRL[id].bit.CHEN)
                ; // Wait for it

              // Disable timer/counter before configuring it
              timer->COUNT8.CTRLA.bit.ENABLE = 0;
              while (timer->COUNT8.SYNCBUSY.bit.STATUS)
                ;

              timer->COUNT8.WAVE.bit.WAVEGEN = 2;    // NPWM
              timer->COUNT8.CTRLA.bit.MODE = 1;      // 8-bit
              timer->COUNT8.CTRLA.bit.PRESCALER = 0; // 1:1
              while (timer->COUNT8.SYNCBUSY.bit.STATUS)
                ;

              timer->COUNT8.CTRLBCLR.bit.DIR = 1; // Count UP
              while (timer->COUNT8.SYNCBUSY.bit.CTRLB)
                ;
              timer->COUNT8.CTRLBSET.bit.ONESHOT = 1; // One-shot
              while (timer->COUNT8.SYNCBUSY.bit.CTRLB)
                ;
              timer->COUNT8.PER.reg = 6; // PWM top
              while (timer->COUNT8.SYNCBUSY.bit.PER)
                ;
              timer->COUNT8.CC[0].reg = 2; // Compare
              while (timer->COUNT8.SYNCBUSY.bit.CC0)
                ;
              // Enable async input events,
              // event action = restart.
              timer->COUNT8.EVCTRL.bit.TCEI = 1;
              timer->COUNT8.EVCTRL.bit.EVACT = 1;

              // Enable timer
              timer->COUNT8.CTRLA.reg |= TC_CTRLA_ENABLE;
              while (timer->COUNT8.SYNCBUSY.bit.STATUS)
                ;

  #if (wrPeripheral == PIO_CCL)
              // CONFIGURE CCL (inverts timer/counter output)

              MCLK->APBCMASK.bit.CCL_ = 1;         // Enable CCL clock
              CCL->CTRL.bit.ENABLE = 0;            // Disable to config
              CCL->CTRL.bit.SWRST = 1;             // Reset CCL registers
              CCL->LUTCTRL[tcNum].bit.ENABLE = 0;  // Disable LUT
              CCL->LUTCTRL[tcNum].bit.FILTSEL = 0; // No filter
              CCL->LUTCTRL[tcNum].bit.INSEL0 = 6;  // TC input
              CCL->LUTCTRL[tcNum].bit.INSEL1 = 0;  // MASK
              CCL->LUTCTRL[tcNum].bit.INSEL2 = 0;  // MASK
              CCL->LUTCTRL[tcNum].bit.TRUTH = 1;   // Invert in 0
              CCL->LUTCTRL[tcNum].bit.ENABLE = 1;  // Enable LUT
              CCL->CTRL.bit.ENABLE = 1;            // Enable CCL
  #endif

              // CONFIGURE EVENT SYSTEM

              // Set up event system clock source from GCLK...
              // Disable EVSYS, wait for disable
              GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN = 0;
              while (GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN)
                ;
              pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK0_Val;
              pchctrl.bit.CHEN = 1; // Re-enable
              GCLK->PCHCTRL[EVSYS_GCLK_ID_0].reg = pchctrl.reg;
              // Wait for it, then enable EVSYS clock
              while (!GCLK->PCHCTRL[EVSYS_GCLK_ID_0].bit.CHEN)
                ;
              MCLK->APBBMASK.bit.EVSYS_ = 1;

              // Connect Timer EVU to ch 0
              EVSYS->USER[tcList[tcNum].evu].reg = 1;
              // Datasheet recommends single write operation;
              // reg instead of bit. Also datasheet: PATH bits
              // must be zero when using async!
              EVSYS_CHANNEL_Type ev;
              ev.reg = 0;
              ev.bit.PATH = 2;                  // Asynchronous
              ev.bit.EVGEN = 0x22 + dmaChannel; // DMA channel 0+
              EVSYS->Channel[0].CHANNEL.reg = ev.reg;

              // Initialize descriptor list.
              for (int d = 0; d < numDescriptors; d++) {
                // No need to set SRCADDR, DESCADDR or BTCNT --
                // those are done in the pixel-writing functions.
                descriptor[d].BTCTRL.bit.VALID = true;
                // Event strobe on beat xfer:
                descriptor[d].BTCTRL.bit.EVOSEL = 0x3;
                descriptor[d].BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
                descriptor[d].BTCTRL.bit.BEATSIZE =
                    tft8.wide ? DMA_BEAT_SIZE_HWORD : DMA_BEAT_SIZE_BYTE;
                descriptor[d].BTCTRL.bit.SRCINC = 1;
                descriptor[d].BTCTRL.bit.DSTINC = 0;
                descriptor[d].BTCTRL.bit.STEPSEL = DMA_STEPSEL_SRC;
                descriptor[d].BTCTRL.bit.STEPSIZE =
                    DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
                descriptor[d].DSTADDR.reg = (uint32_t)tft8.writePort;
              }
  #endif // __SAMD51
            } // end parallel-specific DMA setup

            lastFillColor = 0x0000;
            lastFillLen = 0;
            dma.setCallback(dma_callback);
            return; // Success!
                    // else clean up any partial allocation...
          } // end descriptor memalign()
          free(pixelBuf[0]);
          pixelBuf[0] = pixelBuf[1] = NULL;
        } // end pixelBuf malloc()
          // Don't currently have a descriptor delete function in
          // ZeroDMA lib, but if we did, it would be called here.
      } // end addDescriptor()
      dma.free(); // Deallocate DMA channel
    }
  #endif // end USE_SPI_DMA
  }

  /*!
      @brief  Allow changing the SPI clock speed after initialization
      @param  freq Desired frequency of SPI clock, may not be the
      end frequency you get based on what the chip can do!
  */
  void Adafruit_SPITFT::setSPISpeed(uint32_t freq) {
  #if defined(SPI_HAS_TRANSACTION)
    hwspi.settings = SPISettings(freq, MSBFIRST, hwspi._mode);
  #else
    hwspi._freq = freq; // Save freq value for later
  #endif
  }

  /*!
      @brief  Call before issuing command(s) or data to display. Performs
              chip-select (if required) and starts an SPI transaction (if
              using hardware SPI and transactions are supported). Required
              for all display types; not an SPI-specific function.
  */
  void Adafruit_SPITFT::startWrite(void) {
    SPI_BEGIN_TRANSACTION();
    if (_cs >= 0)
      SPI_CS_LOW();
  }

  /*!
      @brief  Call after issuing command(s) or data to display. Performs
              chip-deselect (if required) and ends an SPI transaction (if
              using hardware SPI and transactions are supported). Required
              for all display types; not an SPI-specific function.
  */
  void Adafruit_SPITFT::endWrite(void) {
    if (_cs >= 0)
      SPI_CS_HIGH();
    SPI_END_TRANSACTION();
  }

  // -------------------------------------------------------------------------
  // Lower-level graphics operations. These functions require a chip-select
  // and/or SPI transaction around them (via startWrite(), endWrite() above).
  // Higher-level graphics primitives might start a single transaction and
  // then make multiple calls to these functions (e.g. circle or text
  // rendering might make repeated lines or rects) before ending the
  // transaction. It's more efficient than starting a transaction every time.

  /*!
      @brief  Draw a single pixel to the display at requested coordinates.
              Not self-contained; should follow a startWrite() call.
      @param  x      Horizontal position (0 = left).
      @param  y      Vertical position   (0 = top).
      @param  color  16-bit pixel color in '565' RGB format.
  */
  void Adafruit_SPITFT::writePixel(int16_t x, int16_t y, uint16_t color) {
    if ((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) {
      setAddrWindow(x, y, 1, 1);
      SPI_WRITE16(color);
    }
  }

  /*!
      @brief  Swap bytes in an array of pixels; converts little-to-big or
              big-to-little endian. Used by writePixels() below in some
              situations, but may also be helpful for user code occasionally.
      @param  src   Source address of 16-bit pixels buffer.
      @param  len   Number of pixels to byte-swap.
      @param  dest  Optional destination address if different than src --
                    otherwise, if NULL (default) or same address is passed,
                    pixel buffer is overwritten in-place.
  */
  void Adafruit_SPITFT::swapBytes(uint16_t *src, uint32_t len, uint16_t *dest) {
    if (!dest)
      dest = src; // NULL -> overwrite src buffer
    for (uint32_t i = 0; i < len; i++) {
      dest[i] = __builtin_bswap16(src[i]);
    }
  }

  /*!
      @brief  Issue a series of pixels from memory to the display. Not self-
              contained; should follow startWrite() and setAddrWindow() calls.
      @param  colors     Pointer to array of 16-bit pixel values in '565' RGB
                         format.
      @param  len        Number of elements in 'colors' array.
      @param  block      If true (default case if unspecified), function blocks
                         until DMA transfer is complete. This is simply IGNORED
                         if DMA is not enabled. If false, the function returns
                         immediately after the last DMA transfer is started,
                         and one should use the dmaWait() function before
                         doing ANY other display-related activities (or even
                         any SPI-related activities, if using an SPI display
                         that shares the bus with other devices).
      @param  bigEndian  If true, bitmap in memory is in big-endian order (most
                         significant byte first). By default this is false, as
                         most microcontrollers seem to be little-endian and
                         16-bit pixel values must be byte-swapped before
                         issuing to the display (which tend toward big-endian
                         when using SPI or 8-bit parallel). If an application
                         can optimize around this -- for example, a bitmap in a
                         uint16_t array having the byte values already ordered
                         big-endian, this can save time here, ESPECIALLY if
                         using this function's non-blocking DMA mode.
  */
  void Adafruit_SPITFT::writePixels(uint16_t *colors, uint32_t len, bool block,
                                    bool bigEndian) {

    if (!len)
      return; // Avoid 0-byte transfers

    // avoid paramater-not-used complaints
    (void)block;
    (void)bigEndian;

  #if defined(ESP32)
    if (connection == TFT_HARD_SPI) {
      if (!bigEndian) {
        hwspi._spi->writePixels(colors, len * 2); // Inbuilt endian-swap
      } else {
        hwspi._spi->writeBytes((uint8_t *)colors, len * 2); // Issue bytes direct
      }
      return;
    }
  #elif defined(ARDUINO_NRF52_ADAFRUIT) &&                                       \
      defined(NRF52840_XXAA) // Adafruit nRF52 use SPIM3 DMA at 32Mhz
    if (!bigEndian) {
      swapBytes(colors, len); // convert little-to-big endian for display
    }
    hwspi._spi->transfer(colors, NULL, 2 * len); // NULL RX to avoid overwrite
    if (!bigEndian) {
      swapBytes(colors, len); // big-to-little endian to restore pixel buffer
    }

    return;
  #elif defined(ARDUINO_ARCH_RP2040)
    spi_inst_t *pi_spi = hwspi._spi == &SPI ? __SPI0_DEVICE : __SPI1_DEVICE;

    if (!bigEndian) {
      // switch to 16-bit writes
      hw_write_masked(&spi_get_hw(pi_spi)->cr0, 15 << SPI_SSPCR0_DSS_LSB,
                      SPI_SSPCR0_DSS_BITS);
      spi_write16_blocking(pi_spi, colors, len);
      // switch back to 8-bit
      hw_write_masked(&spi_get_hw(pi_spi)->cr0, 7 << SPI_SSPCR0_DSS_LSB,
                      SPI_SSPCR0_DSS_BITS);
    } else {
      spi_write_blocking(pi_spi, (uint8_t *)colors, len * 2);
    }
    return;
  #elif defined(ARDUINO_ARCH_RTTHREAD)
    if (!bigEndian) {
      swapBytes(colors, len); // convert little-to-big endian for display
    }
    hwspi._spi->transfer(colors, 2 * len);
    if (!bigEndian) {
      swapBytes(colors, len); // big-to-little endian to restore pixel buffer
    }
    return;
  #elif defined(USE_SPI_DMA) &&                                                  \
      (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
    if ((connection == TFT_HARD_SPI) || (connection == TFT_PARALLEL)) {
      int maxSpan = maxFillLen / 2; // One scanline max
      uint8_t pixelBufIdx = 0;      // Active pixel buffer number
  #if defined(__SAMD51__)
      if (connection == TFT_PARALLEL) {
        // Switch WR pin to PWM or CCL
        pinPeripheral(tft8._wr, wrPeripheral);
      }
  #endif // end __SAMD51__
      if (!bigEndian) { // Normal little-endian situation...
        while (len) {
          int count = (len < maxSpan) ? len : maxSpan;

          // Because TFT and SAMD endianisms are different, must swap
          // bytes from the 'colors' array passed into a DMA working
          // buffer. This can take place while the prior DMA transfer
          // is in progress, hence the need for two pixelBufs.
          swapBytes(colors, count, pixelBuf[pixelBufIdx]);
          colors += count;

          // The transfers themselves are relatively small, so we don't
          // need a long descriptor list. We just alternate between the
          // first two, sharing pixelBufIdx for that purpose.
          descriptor[pixelBufIdx].SRCADDR.reg =
              (uint32_t)pixelBuf[pixelBufIdx] + count * 2;
          descriptor[pixelBufIdx].BTCTRL.bit.SRCINC = 1;
          descriptor[pixelBufIdx].BTCNT.reg = count * 2;
          descriptor[pixelBufIdx].DESCADDR.reg = 0;

          while (dma_busy)
            ; // Wait for prior line to finish

          // Move new descriptor into place...
          memcpy(dptr, &descriptor[pixelBufIdx], sizeof(DmacDescriptor));
          dma_busy = true;
          dma.startJob(); // Trigger SPI DMA transfer
          if (connection == TFT_PARALLEL)
            dma.trigger();
          pixelBufIdx = 1 - pixelBufIdx; // Swap DMA pixel buffers

          len -= count;
        }
      } else { // bigEndian == true
        // With big-endian pixel data, this can be handled as a single
        // DMA transfer using chained descriptors. Even full screen, this
        // needs only a relatively short descriptor list, each
        // transferring a max of 32,767 (not 32,768) pixels. The list
        // was allocated large enough to accommodate a full screen's
        // worth of data, so this won't run past the end of the list.
        int d, numDescriptors = (len + 32766) / 32767;
        for (d = 0; d < numDescriptors; d++) {
          int count = (len < 32767) ? len : 32767;
          descriptor[d].SRCADDR.reg = (uint32_t)colors + count * 2;
          descriptor[d].BTCTRL.bit.SRCINC = 1;
          descriptor[d].BTCNT.reg = count * 2;
          descriptor[d].DESCADDR.reg = (uint32_t)&descriptor[d + 1];
          len -= count;
          colors += count;
        }
        descriptor[d - 1].DESCADDR.reg = 0;

        while (dma_busy)
          ; // Wait for prior transfer (if any) to finish

        // Move first descriptor into place and start transfer...
        memcpy(dptr, &descriptor[0], sizeof(DmacDescriptor));
        dma_busy = true;
        dma.startJob(); // Trigger SPI DMA transfer
        if (connection == TFT_PARALLEL)
          dma.trigger();
      } // end bigEndian

      lastFillColor = 0x0000; // pixelBuf has been sullied
      lastFillLen = 0;
      if (block) {
        while (dma_busy)
          ; // Wait for last line to complete
  #if defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO)
        if (connection == TFT_HARD_SPI) {
          // See SAMD51/21 note in writeColor()
          hwspi._spi->setDataMode(hwspi._mode);
        } else {
          pinPeripheral(tft8._wr, PIO_OUTPUT); // Switch WR back to GPIO
        }
  #endif // end __SAMD51__ || ARDUINO_SAMD_ZERO
      }
      return;
    }
  #endif // end USE_SPI_DMA

    // All other cases (bitbang SPI or non-DMA hard SPI or parallel),
    // use a loop with the normal 16-bit data write function:

    if (!bigEndian) {
      while (len--) {
        SPI_WRITE16(*colors++);
      }
    } else {
      // Well this is awkward. SPI_WRITE16() was designed for little-endian
      // hosts and big-endian displays as that's nearly always the typical
      // case. If the bigEndian flag was set, data is already in display's
      // order...so each pixel needs byte-swapping before being issued.
      // Rather than having a separate big-endian SPI_WRITE16 (adding more
      // bloat), it's preferred if calling function is smart and only uses
      // bigEndian where DMA is supported. But we gotta handle this...
      while (len--) {
        SPI_WRITE16(__builtin_bswap16(*colors++));
      }
    }
  }

  /*!
      @brief  Wait for the last DMA transfer in a prior non-blocking
              writePixels() call to complete. This does nothing if DMA
              is not enabled, and is not needed if blocking writePixels()
              was used (as is the default case).
  */
  void Adafruit_SPITFT::dmaWait(void) {
  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
    while (dma_busy)
      ;
  #if defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO)
    if (connection == TFT_HARD_SPI) {
      // See SAMD51/21 note in writeColor()
      hwspi._spi->setDataMode(hwspi._mode);
    } else {
      pinPeripheral(tft8._wr, PIO_OUTPUT); // Switch WR back to GPIO
    }
  #endif // end __SAMD51__ || ARDUINO_SAMD_ZERO
  #endif
  }

  /*!
      @brief  Check if DMA transfer is active. Always returts false if DMA
              is not enabled.
      @return true if DMA is enabled and transmitting data, false otherwise.
  */
  bool Adafruit_SPITFT::dmaBusy(void) const {
  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
    return dma_busy;
  #else
    return false;
  #endif
  }

  /*!
      @brief  Issue a series of pixels, all the same color. Not self-
              contained; should follow startWrite() and setAddrWindow() calls.
      @param  color  16-bit pixel color in '565' RGB format.
      @param  len    Number of pixels to draw.
  */
  void Adafruit_SPITFT::writeColor(uint16_t color, uint32_t len) {

    if (!len)
      return; // Avoid 0-byte transfers

    uint8_t hi = color >> 8, lo = color;

  #if defined(ESP32) // ESP32 has a special SPI pixel-writing function...
    if (connection == TFT_HARD_SPI) {
  #define SPI_MAX_PIXELS_AT_ONCE 32
  #define TMPBUF_LONGWORDS (SPI_MAX_PIXELS_AT_ONCE + 1) / 2
  #define TMPBUF_PIXELS (TMPBUF_LONGWORDS * 2)
      static uint32_t temp[TMPBUF_LONGWORDS];
      uint32_t c32 = color * 0x00010001;
      uint16_t bufLen = (len < TMPBUF_PIXELS) ? len : TMPBUF_PIXELS, xferLen,
               fillLen;
      // Fill temp buffer 32 bits at a time
      fillLen = (bufLen + 1) / 2; // Round up to next 32-bit boundary
      for (uint32_t t = 0; t < fillLen; t++) {
        temp[t] = c32;
      }
      // Issue pixels in blocks from temp buffer
      while (len) {                              // While pixels remain
        xferLen = (bufLen < len) ? bufLen : len; // How many this pass?
        writePixels((uint16_t *)temp, xferLen);
        len -= xferLen;
      }
      return;
    }
  #elif defined(ARDUINO_NRF52_ADAFRUIT) &&                                       \
      defined(NRF52840_XXAA) // Adafruit nRF52840 use SPIM3 DMA at 32Mhz
    // at most 2 scan lines
    uint32_t const pixbufcount = min(len, ((uint32_t)2 * width()));
    uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);

    // use SPI3 DMA if we could allocate buffer, else fall back to writing each
    // pixel loop below
    if (pixbuf) {
      uint16_t const swap_color = __builtin_bswap16(color);

      // fill buffer with color
      for (uint32_t i = 0; i < pixbufcount; i++) {
        pixbuf[i] = swap_color;
      }

      while (len) {
        uint32_t const count = min(len, pixbufcount);
        writePixels(pixbuf, count, true, true);
        len -= count;
      }

      rtos_free(pixbuf);
      return;
    }
  #elif defined(ARDUINO_ARCH_RTTHREAD)
    uint16_t pixbufcount;
    uint16_t *pixbuf;
    int16_t lines = height() / 4;
  #define QUICKPATH_MAX_LEN 16
    uint16_t quickpath_buffer[QUICKPATH_MAX_LEN];

    do {
      pixbufcount = min(len, (lines * width()));
      if (pixbufcount > QUICKPATH_MAX_LEN) {
        pixbuf = (uint16_t *)rt_malloc(2 * pixbufcount);
      } else {
        pixbuf = quickpath_buffer;
      }
      lines -= 2;
    } while (!pixbuf && lines > 0);

    if (pixbuf) {
      uint16_t const swap_color = __builtin_bswap16(color);

      while (len) {
        uint16_t count = min(len, pixbufcount);
        // fill buffer with color
        for (uint16_t i = 0; i < count; i++) {
          pixbuf[i] = swap_color;
        }
        // Don't need to swap color inside the function
        // It has been done outside this function
        writePixels(pixbuf, count, true, true);
        len -= count;
      }
      if (pixbufcount > QUICKPATH_MAX_LEN) {
        rt_free(pixbuf);
      }
  #undef QUICKPATH_MAX_LEN
      return;
    }
  #else // !ESP32
  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
    if (((connection == TFT_HARD_SPI) || (connection == TFT_PARALLEL)) &&
        (len >= 16)) { // Don't bother with DMA on short pixel runs
      int i, d, numDescriptors;
      if (hi == lo) { // If high & low bytes are same...
        onePixelBuf = color;
        // Can do this with a relatively short descriptor list,
        // each transferring a max of 32,767 (not 32,768) pixels.
        // This won't run off the end of the allocated descriptor list,
        // since we're using much larger chunks per descriptor here.
        numDescriptors = (len + 32766) / 32767;
        for (d = 0; d < numDescriptors; d++) {
          int count = (len < 32767) ? len : 32767;
          descriptor[d].SRCADDR.reg = (uint32_t)&onePixelBuf;
          descriptor[d].BTCTRL.bit.SRCINC = 0;
          descriptor[d].BTCNT.reg = count * 2;
          descriptor[d].DESCADDR.reg = (uint32_t)&descriptor[d + 1];
          len -= count;
        }
        descriptor[d - 1].DESCADDR.reg = 0;
      } else {
        // If high and low bytes are distinct, it's necessary to fill
        // a buffer with pixel data (swapping high and low bytes because
        // TFT and SAMD are different endianisms) and create a longer
        // descriptor list pointing repeatedly to this data. We can do
        // this slightly faster working 2 pixels (32 bits) at a time.
        uint32_t *pixelPtr = (uint32_t *)pixelBuf[0],
                 twoPixels = __builtin_bswap16(color) * 0x00010001;
        // We can avoid some or all of the buffer-filling if the color
        // is the same as last time...
        if (color == lastFillColor) {
          // If length is longer than prior instance, fill only the
          // additional pixels in the buffer and update lastFillLen.
          if (len > lastFillLen) {
            int fillStart = lastFillLen / 2,
                fillEnd = (((len < maxFillLen) ? len : maxFillLen) + 1) / 2;
            for (i = fillStart; i < fillEnd; i++)
              pixelPtr[i] = twoPixels;
            lastFillLen = fillEnd * 2;
          } // else do nothing, don't set pixels or change lastFillLen
        } else {
          int fillEnd = (((len < maxFillLen) ? len : maxFillLen) + 1) / 2;
          for (i = 0; i < fillEnd; i++)
            pixelPtr[i] = twoPixels;
          lastFillLen = fillEnd * 2;
          lastFillColor = color;
        }

        numDescriptors = (len + maxFillLen - 1) / maxFillLen;
        for (d = 0; d < numDescriptors; d++) {
          int pixels = (len < maxFillLen) ? len : maxFillLen, bytes = pixels * 2;
          descriptor[d].SRCADDR.reg = (uint32_t)pixelPtr + bytes;
          descriptor[d].BTCTRL.bit.SRCINC = 1;
          descriptor[d].BTCNT.reg = bytes;
          descriptor[d].DESCADDR.reg = (uint32_t)&descriptor[d + 1];
          len -= pixels;
        }
        descriptor[d - 1].DESCADDR.reg = 0;
      }
      memcpy(dptr, &descriptor[0], sizeof(DmacDescriptor));
  #if defined(__SAMD51__)
      if (connection == TFT_PARALLEL) {
        // Switch WR pin to PWM or CCL
        pinPeripheral(tft8._wr, wrPeripheral);
      }
  #endif // end __SAMD51__

      dma_busy = true;
      dma.startJob();
      if (connection == TFT_PARALLEL)
        dma.trigger();
      while (dma_busy)
        ; // Wait for completion
          // Unfortunately blocking is necessary. An earlier version returned
          // immediately and checked dma_busy on startWrite() instead, but it
          // turns out to be MUCH slower on many graphics operations (as when
          // drawing lines, pixel-by-pixel), perhaps because it's a volatile
          // type and doesn't cache. Working on this.
  #if defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO)
      if (connection == TFT_HARD_SPI) {
        // SAMD51: SPI DMA seems to leave the SPI peripheral in a freaky
        // state on completion. Workaround is to explicitly set it back...
        // (5/17/2019: apparently SAMD21 too, in certain cases, observed
        // with ST7789 display.)
        hwspi._spi->setDataMode(hwspi._mode);
      } else {
        pinPeripheral(tft8._wr, PIO_OUTPUT); // Switch WR back to GPIO
      }
  #endif // end __SAMD51__
      return;
    }
  #endif // end USE_SPI_DMA
  #endif // end !ESP32

    // All other cases (non-DMA hard SPI, bitbang SPI, parallel)...

    if (connection == TFT_HARD_SPI) {
  #if defined(ESP8266)
      do {
        uint32_t pixelsThisPass = len;
        if (pixelsThisPass > 50000)
          pixelsThisPass = 50000;
        len -= pixelsThisPass;
        delay(1); // Periodic delay on long fills
        while (pixelsThisPass--) {
          hwspi._spi->write(hi);
          hwspi._spi->write(lo);
        }
      } while (len);
  #elif defined(ARDUINO_ARCH_RP2040)
      spi_inst_t *pi_spi = hwspi._spi == &SPI ? __SPI0_DEVICE : __SPI1_DEVICE;
      color = __builtin_bswap16(color);

      while (len--)
        spi_write_blocking(pi_spi, (uint8_t *)&color, 2);
  #else // !ESP8266 && !ARDUINO_ARCH_RP2040
      while (len--) {
  #if defined(__AVR__)
        AVR_WRITESPI(hi);
        AVR_WRITESPI(lo);
  #elif defined(ESP32)
        hwspi._spi->write(hi);
        hwspi._spi->write(lo);
  #else
        hwspi._spi->transfer(hi);
        hwspi._spi->transfer(lo);
  #endif
      }
  #endif // end !ESP8266
    } else if (connection == TFT_SOFT_SPI) {
  #if defined(ESP8266)
      do {
        uint32_t pixelsThisPass = len;
        if (pixelsThisPass > 20000)
          pixelsThisPass = 20000;
        len -= pixelsThisPass;
        yield(); // Periodic yield() on long fills
        while (pixelsThisPass--) {
          for (uint16_t bit = 0, x = color; bit < 16; bit++) {
            if (x & 0x8000)
              SPI_MOSI_HIGH();
            else
              SPI_MOSI_LOW();
            SPI_SCK_HIGH();
            SPI_SCK_LOW();
            x <<= 1;
          }
        }
      } while (len);
  #else // !ESP8266
      while (len--) {
  #if defined(__AVR__)
        for (uint8_t bit = 0, x = hi; bit < 8; bit++) {
          if (x & 0x80)
            SPI_MOSI_HIGH();
          else
            SPI_MOSI_LOW();
          SPI_SCK_HIGH();
          SPI_SCK_LOW();
          x <<= 1;
        }
        for (uint8_t bit = 0, x = lo; bit < 8; bit++) {
          if (x & 0x80)
            SPI_MOSI_HIGH();
          else
            SPI_MOSI_LOW();
          SPI_SCK_HIGH();
          SPI_SCK_LOW();
          x <<= 1;
        }
  #else      // !__AVR__
        for (uint16_t bit = 0, x = color; bit < 16; bit++) {
          if (x & 0x8000)
            SPI_MOSI_HIGH();
          else
            SPI_MOSI_LOW();
          SPI_SCK_HIGH();
          x <<= 1;
          SPI_SCK_LOW();
        }
  #endif     // end !__AVR__
      }
  #endif     // end !ESP8266
    } else { // PARALLEL
      if (hi == lo) {
  #if defined(__AVR__)
        len *= 2;
        *tft8.writePort = hi;
        while (len--) {
          TFT_WR_STROBE();
        }
  #elif defined(USE_FAST_PINIO)
        if (!tft8.wide) {
          len *= 2;
          *tft8.writePort = hi;
        } else {
          *(volatile uint16_t *)tft8.writePort = color;
        }
        while (len--) {
          TFT_WR_STROBE();
        }
  #endif
      } else {
        while (len--) {
  #if defined(__AVR__)
          *tft8.writePort = hi;
          TFT_WR_STROBE();
          *tft8.writePort = lo;
  #elif defined(USE_FAST_PINIO)
          if (!tft8.wide) {
            *tft8.writePort = hi;
            TFT_WR_STROBE();
            *tft8.writePort = lo;
          } else {
            *(volatile uint16_t *)tft8.writePort = color;
          }
  #endif
          TFT_WR_STROBE();
        }
      }
    }
  }

  /*!
      @brief  Draw a filled rectangle to the display. Not self-contained;
              should follow startWrite(). Typically used by higher-level
              graphics primitives; user code shouldn't need to call this and
              is likely to use the self-contained fillRect() instead.
              writeFillRect() performs its own edge clipping and rejection;
              see writeFillRectPreclipped() for a more 'raw' implementation.
      @param  x      Horizontal position of first corner.
      @param  y      Vertical position of first corner.
      @param  w      Rectangle width in pixels (positive = right of first
                     corner, negative = left of first corner).
      @param  h      Rectangle height in pixels (positive = below first
                     corner, negative = above first corner).
      @param  color  16-bit fill color in '565' RGB format.
      @note   Written in this deep-nested way because C by definition will
              optimize for the 'if' case, not the 'else' -- avoids branches
              and rejects clipped rectangles at the least-work possibility.
  */
  void Adafruit_SPITFT::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                                      uint16_t color) {
    if (w && h) {   // Nonzero width and height?
      if (w < 0) {  // If negative width...
        x += w + 1; //   Move X to left edge
        w = -w;     //   Use positive width
      }
      if (x < _width) { // Not off right
        if (h < 0) {    // If negative height...
          y += h + 1;   //   Move Y to top edge
          h = -h;       //   Use positive height
        }
        if (y < _height) { // Not off bottom
          int16_t x2 = x + w - 1;
          if (x2 >= 0) { // Not off left
            int16_t y2 = y + h - 1;
            if (y2 >= 0) { // Not off top
              // Rectangle partly or fully overlaps screen
              if (x < 0) {
                x = 0;
                w = x2 + 1;
              } // Clip left
              if (y < 0) {
                y = 0;
                h = y2 + 1;
              } // Clip top
              if (x2 >= _width) {
                w = _width - x;
              } // Clip right
              if (y2 >= _height) {
                h = _height - y;
              } // Clip bottom
              writeFillRectPreclipped(x, y, w, h, color);
            }
          }
        }
      }
    }
  }

  /*!
      @brief  Draw a horizontal line on the display. Performs edge clipping
              and rejection. Not self-contained; should follow startWrite().
              Typically used by higher-level graphics primitives; user code
              shouldn't need to call this and is likely to use the self-
              contained drawFastHLine() instead.
      @param  x      Horizontal position of first point.
      @param  y      Vertical position of first point.
      @param  w      Line width in pixels (positive = right of first point,
                     negative = point of first corner).
      @param  color  16-bit line color in '565' RGB format.
  */
  void inline Adafruit_SPITFT::writeFastHLine(int16_t x, int16_t y, int16_t w,
                                              uint16_t color) {
    if ((y >= 0) && (y < _height) && w) { // Y on screen, nonzero width
      if (w < 0) {                        // If negative width...
        x += w + 1;                       //   Move X to left edge
        w = -w;                           //   Use positive width
      }
      if (x < _width) { // Not off right
        int16_t x2 = x + w - 1;
        if (x2 >= 0) { // Not off left
          // Line partly or fully overlaps screen
          if (x < 0) {
            x = 0;
            w = x2 + 1;
          } // Clip left
          if (x2 >= _width) {
            w = _width - x;
          } // Clip right
          writeFillRectPreclipped(x, y, w, 1, color);
        }
      }
    }
  }

  /*!
      @brief  Draw a vertical line on the display. Performs edge clipping and
              rejection. Not self-contained; should follow startWrite().
              Typically used by higher-level graphics primitives; user code
              shouldn't need to call this and is likely to use the self-
              contained drawFastVLine() instead.
      @param  x      Horizontal position of first point.
      @param  y      Vertical position of first point.
      @param  h      Line height in pixels (positive = below first point,
                     negative = above first point).
      @param  color  16-bit line color in '565' RGB format.
  */
  void inline Adafruit_SPITFT::writeFastVLine(int16_t x, int16_t y, int16_t h,
                                              uint16_t color) {
    if ((x >= 0) && (x < _width) && h) { // X on screen, nonzero height
      if (h < 0) {                       // If negative height...
        y += h + 1;                      //   Move Y to top edge
        h = -h;                          //   Use positive height
      }
      if (y < _height) { // Not off bottom
        int16_t y2 = y + h - 1;
        if (y2 >= 0) { // Not off top
          // Line partly or fully overlaps screen
          if (y < 0) {
            y = 0;
            h = y2 + 1;
          } // Clip top
          if (y2 >= _height) {
            h = _height - y;
          } // Clip bottom
          writeFillRectPreclipped(x, y, 1, h, color);
        }
      }
    }
  }

  /*!
      @brief  A lower-level version of writeFillRect(). This version requires
              all inputs are in-bounds, that width and height are positive,
              and no part extends offscreen. NO EDGE CLIPPING OR REJECTION IS
              PERFORMED. If higher-level graphics primitives are written to
              handle their own clipping earlier in the drawing process, this
              can avoid unnecessary function calls and repeated clipping
              operations in the lower-level functions.
      @param  x      Horizontal position of first corner. MUST BE WITHIN
                     SCREEN BOUNDS.
      @param  y      Vertical position of first corner. MUST BE WITHIN SCREEN
                     BOUNDS.
      @param  w      Rectangle width in pixels. MUST BE POSITIVE AND NOT
                     EXTEND OFF SCREEN.
      @param  h      Rectangle height in pixels. MUST BE POSITIVE AND NOT
                     EXTEND OFF SCREEN.
      @param  color  16-bit fill color in '565' RGB format.
      @note   This is a new function, no graphics primitives besides rects
              and horizontal/vertical lines are written to best use this yet.
  */
  inline void Adafruit_SPITFT::writeFillRectPreclipped(int16_t x, int16_t y,
                                                       int16_t w, int16_t h,
                                                       uint16_t color) {
    setAddrWindow(x, y, w, h);
    writeColor(color, (uint32_t)w * h);
  }

  // -------------------------------------------------------------------------
  // Ever-so-slightly higher-level graphics operations. Similar to the 'write'
  // functions above, but these contain their own chip-select and SPI
  // transactions as needed (via startWrite(), endWrite()). They're typically
  // used solo -- as graphics primitives in themselves, not invoked by higher-
  // level primitives (which should use the functions above for better
  // performance).

  /*!
      @brief  Draw a single pixel to the display at requested coordinates.
              Self-contained and provides its own transaction as needed
              (see writePixel(x,y,color) for a lower-level variant).
              Edge clipping is performed here.
      @param  x      Horizontal position (0 = left).
      @param  y      Vertical position   (0 = top).
      @param  color  16-bit pixel color in '565' RGB format.
  */
  void Adafruit_SPITFT::drawPixel(int16_t x, int16_t y, uint16_t color) {
    // Clip first...
    if ((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) {
      // THEN set up transaction (if needed) and draw...
      startWrite();
      setAddrWindow(x, y, 1, 1);
      SPI_WRITE16(color);
      endWrite();
    }
  }

  /*!
      @brief  Draw a filled rectangle to the display. Self-contained and
              provides its own transaction as needed (see writeFillRect() or
              writeFillRectPreclipped() for lower-level variants). Edge
              clipping and rejection is performed here.
      @param  x      Horizontal position of first corner.
      @param  y      Vertical position of first corner.
      @param  w      Rectangle width in pixels (positive = right of first
                     corner, negative = left of first corner).
      @param  h      Rectangle height in pixels (positive = below first
                     corner, negative = above first corner).
      @param  color  16-bit fill color in '565' RGB format.
      @note   This repeats the writeFillRect() function almost in its entirety,
              with the addition of a transaction start/end. It's done this way
              (rather than starting the transaction and calling writeFillRect()
              to handle clipping and so forth) so that the transaction isn't
              performed at all if the rectangle is rejected. It's really not
              that much code.
  */
  void Adafruit_SPITFT::fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                                 uint16_t color) {
    if (w && h) {   // Nonzero width and height?
      if (w < 0) {  // If negative width...
        x += w + 1; //   Move X to left edge
        w = -w;     //   Use positive width
      }
      if (x < _width) { // Not off right
        if (h < 0) {    // If negative height...
          y += h + 1;   //   Move Y to top edge
          h = -h;       //   Use positive height
        }
        if (y < _height) { // Not off bottom
          int16_t x2 = x + w - 1;
          if (x2 >= 0) { // Not off left
            int16_t y2 = y + h - 1;
            if (y2 >= 0) { // Not off top
              // Rectangle partly or fully overlaps screen
              if (x < 0) {
                x = 0;
                w = x2 + 1;
              } // Clip left
              if (y < 0) {
                y = 0;
                h = y2 + 1;
              } // Clip top
              if (x2 >= _width) {
                w = _width - x;
              } // Clip right
              if (y2 >= _height) {
                h = _height - y;
              } // Clip bottom
              startWrite();
              writeFillRectPreclipped(x, y, w, h, color);
              endWrite();
            }
          }
        }
      }
    }
  }

  /*!
      @brief  Draw a horizontal line on the display. Self-contained and
              provides its own transaction as needed (see writeFastHLine() for
              a lower-level variant). Edge clipping and rejection is performed
              here.
      @param  x      Horizontal position of first point.
      @param  y      Vertical position of first point.
      @param  w      Line width in pixels (positive = right of first point,
                     negative = point of first corner).
      @param  color  16-bit line color in '565' RGB format.
      @note   This repeats the writeFastHLine() function almost in its
              entirety, with the addition of a transaction start/end. It's
              done this way (rather than starting the transaction and calling
              writeFastHLine() to handle clipping and so forth) so that the
              transaction isn't performed at all if the line is rejected.
  */
  void Adafruit_SPITFT::drawFastHLine(int16_t x, int16_t y, int16_t w,
                                      uint16_t color) {
    if ((y >= 0) && (y < _height) && w) { // Y on screen, nonzero width
      if (w < 0) {                        // If negative width...
        x += w + 1;                       //   Move X to left edge
        w = -w;                           //   Use positive width
      }
      if (x < _width) { // Not off right
        int16_t x2 = x + w - 1;
        if (x2 >= 0) { // Not off left
          // Line partly or fully overlaps screen
          if (x < 0) {
            x = 0;
            w = x2 + 1;
          } // Clip left
          if (x2 >= _width) {
            w = _width - x;
          } // Clip right
          startWrite();
          writeFillRectPreclipped(x, y, w, 1, color);
          endWrite();
        }
      }
    }
  }

  /*!
      @brief  Draw a vertical line on the display. Self-contained and provides
              its own transaction as needed (see writeFastHLine() for a lower-
              level variant). Edge clipping and rejection is performed here.
      @param  x      Horizontal position of first point.
      @param  y      Vertical position of first point.
      @param  h      Line height in pixels (positive = below first point,
                     negative = above first point).
      @param  color  16-bit line color in '565' RGB format.
      @note   This repeats the writeFastVLine() function almost in its
              entirety, with the addition of a transaction start/end. It's
              done this way (rather than starting the transaction and calling
              writeFastVLine() to handle clipping and so forth) so that the
              transaction isn't performed at all if the line is rejected.
  */
  void Adafruit_SPITFT::drawFastVLine(int16_t x, int16_t y, int16_t h,
                                      uint16_t color) {
    if ((x >= 0) && (x < _width) && h) { // X on screen, nonzero height
      if (h < 0) {                       // If negative height...
        y += h + 1;                      //   Move Y to top edge
        h = -h;                          //   Use positive height
      }
      if (y < _height) { // Not off bottom
        int16_t y2 = y + h - 1;
        if (y2 >= 0) { // Not off top
          // Line partly or fully overlaps screen
          if (y < 0) {
            y = 0;
            h = y2 + 1;
          } // Clip top
          if (y2 >= _height) {
            h = _height - y;
          } // Clip bottom
          startWrite();
          writeFillRectPreclipped(x, y, 1, h, color);
          endWrite();
        }
      }
    }
  }

  /*!
      @brief  Essentially writePixel() with a transaction around it. I don't
              think this is in use by any of our code anymore (believe it was
              for some older BMP-reading examples), but is kept here in case
              any user code relies on it. Consider it DEPRECATED.
      @param  color  16-bit pixel color in '565' RGB format.
  */
  void Adafruit_SPITFT::pushColor(uint16_t color) {
    startWrite();
    SPI_WRITE16(color);
    endWrite();
  }

  /*!
      @brief  Draw a 16-bit image (565 RGB) at the specified (x,y) position.
              For 16-bit display devices; no color reduction performed.
              Adapted from https://github.com/PaulStoffregen/ILI9341_t3
              by Marc MERLIN. See examples/pictureEmbed to use this.
              5/6/2017: function name and arguments have changed for
              compatibility with current GFX library and to avoid naming
              problems in prior implementation.  Formerly drawBitmap() with
              arguments in different order. Handles its own transaction and
              edge clipping/rejection.
      @param  x        Top left corner horizontal coordinate.
      @param  y        Top left corner vertical coordinate.
      @param  pcolors  Pointer to 16-bit array of pixel values.
      @param  w        Width of bitmap in pixels.
      @param  h        Height of bitmap in pixels.
  */
  void Adafruit_SPITFT::drawRGBBitmap(int16_t x, int16_t y, uint16_t *pcolors,
                                      int16_t w, int16_t h) {

    int16_t x2, y2;                 // Lower-right coord
    if ((x >= _width) ||            // Off-edge right
        (y >= _height) ||           // " top
        ((x2 = (x + w - 1)) < 0) || // " left
        ((y2 = (y + h - 1)) < 0))
      return; // " bottom

    int16_t bx1 = 0, by1 = 0, // Clipped top-left within bitmap
        saveW = w;            // Save original bitmap width value
    if (x < 0) {              // Clip left
      w += x;
      bx1 = -x;
      x = 0;
    }
    if (y < 0) { // Clip top
      h += y;
      by1 = -y;
      y = 0;
    }
    if (x2 >= _width)
      w = _width - x; // Clip right
    if (y2 >= _height)
      h = _height - y; // Clip bottom

    pcolors += by1 * saveW + bx1; // Offset bitmap ptr to clipped top-left
    startWrite();
    setAddrWindow(x, y, w, h); // Clipped area
    while (h--) {              // For each (clipped) scanline...
      writePixels(pcolors, w); // Push one (clipped) row
      pcolors += saveW;        // Advance pointer by one full (unclipped) line
    }
    endWrite();
  }

  // -------------------------------------------------------------------------
  // Miscellaneous class member functions that don't draw anything.

  /*!
      @brief  Invert the colors of the display (if supported by hardware).
              Self-contained, no transaction setup required.
      @param  i  true = inverted display, false = normal display.
  */
  void Adafruit_SPITFT::invertDisplay(bool i) {
    startWrite();
    writeCommand(i ? invertOnCommand : invertOffCommand);
    endWrite();
  }

  /*!
      @brief   Given 8-bit red, green and blue values, return a 'packed'
               16-bit color value in '565' RGB format (5 bits red, 6 bits
               green, 5 bits blue). This is just a mathematical operation,
               no hardware is touched.
      @param   red    8-bit red brightnesss (0 = off, 255 = max).
      @param   green  8-bit green brightnesss (0 = off, 255 = max).
      @param   blue   8-bit blue brightnesss (0 = off, 255 = max).
      @return  'Packed' 16-bit color value (565 format).
  */
  uint16_t Adafruit_SPITFT::color565(uint8_t red, uint8_t green, uint8_t blue) {
    return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
  }

  /*!
  @brief   Adafruit_SPITFT Send Command handles complete sending of commands and
  data
  @param   commandByte       The Command Byte
  @param   dataBytes         A pointer to the Data bytes to send
  @param   numDataBytes      The number of bytes we should send
  */
  void Adafruit_SPITFT::sendCommand(uint8_t commandByte, uint8_t *dataBytes,
                                    uint8_t numDataBytes) {
    SPI_BEGIN_TRANSACTION();
    if (_cs >= 0)
      SPI_CS_LOW();

    SPI_DC_LOW();          // Command mode
    spiWrite(commandByte); // Send the command byte

    SPI_DC_HIGH();
    for (int i = 0; i < numDataBytes; i++) {
      if ((connection == TFT_PARALLEL) && tft8.wide) {
        SPI_WRITE16(*(uint16_t *)dataBytes);
        dataBytes += 2;
      } else {
        spiWrite(*dataBytes); // Send the data bytes
        dataBytes++;
      }
    }

    if (_cs >= 0)
      SPI_CS_HIGH();
    SPI_END_TRANSACTION();
  }

  /*!
   @brief   Adafruit_SPITFT Send Command handles complete sending of commands and
   data
   @param   commandByte       The Command Byte
   @param   dataBytes         A pointer to the Data bytes to send
   @param   numDataBytes      The number of bytes we should send
   */
  void Adafruit_SPITFT::sendCommand(uint8_t commandByte, const uint8_t *dataBytes,
                                    uint8_t numDataBytes) {
    SPI_BEGIN_TRANSACTION();
    if (_cs >= 0)
      SPI_CS_LOW();

    SPI_DC_LOW();          // Command mode
    spiWrite(commandByte); // Send the command byte

    SPI_DC_HIGH();
    for (int i = 0; i < numDataBytes; i++) {
      if ((connection == TFT_PARALLEL) && tft8.wide) {
        SPI_WRITE16(*(uint16_t *)dataBytes);
        dataBytes += 2;
      } else {
        spiWrite(pgm_read_byte(dataBytes++));
      }
    }

    if (_cs >= 0)
      SPI_CS_HIGH();
    SPI_END_TRANSACTION();
  }

  /*!
   @brief  Adafruit_SPITFT sendCommand16 handles complete sending of
           commands and data for 16-bit parallel displays. Currently somewhat
           rigged for the NT35510, which has the odd behavior of wanting
           commands 16-bit, but subsequent data as 8-bit values, despite
           the 16-bit bus (high byte is always 0). Also seems to require
           issuing and incrementing address with each transfer.
   @param  commandWord   The command word (16 bits)
   @param  dataBytes     A pointer to the data bytes to send
   @param  numDataBytes  The number of bytes we should send
   */
  void Adafruit_SPITFT::sendCommand16(uint16_t commandWord,
                                      const uint8_t *dataBytes,
                                      uint8_t numDataBytes) {
    SPI_BEGIN_TRANSACTION();
    if (_cs >= 0)
      SPI_CS_LOW();

    if (numDataBytes == 0) {
      SPI_DC_LOW();             // Command mode
      SPI_WRITE16(commandWord); // Send the command word
      SPI_DC_HIGH();            // Data mode
    }
    for (int i = 0; i < numDataBytes; i++) {
      SPI_DC_LOW();             // Command mode
      SPI_WRITE16(commandWord); // Send the command word
      SPI_DC_HIGH();            // Data mode
      commandWord++;
      SPI_WRITE16((uint16_t)pgm_read_byte(dataBytes++));
    }

    if (_cs >= 0)
      SPI_CS_HIGH();
    SPI_END_TRANSACTION();
  }

  /*!
   @brief   Read 8 bits of data from display configuration memory (not RAM).
   This is highly undocumented/supported and should be avoided,
   function is only included because some of the examples use it.
   @param   commandByte
   The command register to read data from.
   @param   index
   The byte index into the command to read from.
   @return  Unsigned 8-bit data read from display register.
   */
  /**************************************************************************/
  uint8_t Adafruit_SPITFT::readcommand8(uint8_t commandByte, uint8_t index) {
    uint8_t result;
    startWrite();
    SPI_DC_LOW(); // Command mode
    spiWrite(commandByte);
    SPI_DC_HIGH(); // Data mode
    do {
      result = spiRead();
    } while (index--); // Discard bytes up to index'th
    endWrite();
    return result;
  }

  /*!
   @brief   Read 16 bits of data from display register.
            For 16-bit parallel displays only.
   @param   addr  Command/register to access.
   @return  Unsigned 16-bit data.
   */
  uint16_t Adafruit_SPITFT::readcommand16(uint16_t addr) {
  #if defined(USE_FAST_PINIO) // NOT SUPPORTED without USE_FAST_PINIO
    uint16_t result = 0;
    if ((connection == TFT_PARALLEL) && tft8.wide) {
      startWrite();
      SPI_DC_LOW(); // Command mode
      SPI_WRITE16(addr);
      SPI_DC_HIGH(); // Data mode
      TFT_RD_LOW();  // Read line LOW
  #if defined(HAS_PORT_SET_CLR)
      *(volatile uint16_t *)tft8.dirClr = 0xFFFF;   // Input state
      result = *(volatile uint16_t *)tft8.readPort; // 16-bit read
      *(volatile uint16_t *)tft8.dirSet = 0xFFFF;   // Output state
  #else                                             // !HAS_PORT_SET_CLR
      *(volatile uint16_t *)tft8.portDir = 0x0000;  // Input state
      result = *(volatile uint16_t *)tft8.readPort; // 16-bit read
      *(volatile uint16_t *)tft8.portDir = 0xFFFF;  // Output state
  #endif                                            // end !HAS_PORT_SET_CLR
      TFT_RD_HIGH();                                // Read line HIGH
      endWrite();
    }
    return result;
  #else
    (void)addr; // disable -Wunused-parameter warning
    return 0;
  #endif // end !USE_FAST_PINIO
  }

  // -------------------------------------------------------------------------
  // Lowest-level hardware-interfacing functions. Many of these are inline and
  // compile to different things based on #defines -- typically just a few
  // instructions. Others, not so much, those are not inlined.

  /*!
      @brief  Start an SPI transaction if using the hardware SPI interface to
              the display. If using an earlier version of the Arduino platform
              (before the addition of SPI transactions), this instead attempts
              to set up the SPI clock and mode. No action is taken if the
              connection is not hardware SPI-based. This does NOT include a
              chip-select operation -- see startWrite() for a function that
              encapsulated both actions.
  */
  inline void Adafruit_SPITFT::SPI_BEGIN_TRANSACTION(void) {
    if (connection == TFT_HARD_SPI) {
  #if defined(SPI_HAS_TRANSACTION)
      hwspi._spi->beginTransaction(hwspi.settings);
  #else // No transactions, configure SPI manually...
  #if defined(__AVR__) || defined(TEENSYDUINO) || defined(ARDUINO_ARCH_STM32F1)
      hwspi._spi->setClockDivider(SPI_CLOCK_DIV2);
  #elif defined(__arm__)
      hwspi._spi->setClockDivider(11);
  #elif defined(ESP8266) || defined(ESP32)
      hwspi._spi->setFrequency(hwspi._freq);
  #elif defined(RASPI) || defined(ARDUINO_ARCH_STM32F1)
      hwspi._spi->setClock(hwspi._freq);
  #endif
      hwspi._spi->setBitOrder(MSBFIRST);
      hwspi._spi->setDataMode(hwspi._mode);
  #endif // end !SPI_HAS_TRANSACTION
    }
  }

  /*!
      @brief  End an SPI transaction if using the hardware SPI interface to
              the display. No action is taken if the connection is not
              hardware SPI-based or if using an earlier version of the Arduino
              platform (before the addition of SPI transactions). This does
              NOT include a chip-deselect operation -- see endWrite() for a
              function that encapsulated both actions.
  */
  inline void Adafruit_SPITFT::SPI_END_TRANSACTION(void) {
  #if defined(SPI_HAS_TRANSACTION)
    if (connection == TFT_HARD_SPI) {
      hwspi._spi->endTransaction();
    }
  #endif
  }

  /*!
      @brief  Issue a single 8-bit value to the display. Chip-select,
              transaction and data/command selection must have been
              previously set -- this ONLY issues the byte. This is another of
              those functions in the library with a now-not-accurate name
              that's being maintained for compatibility with outside code.
              This function is used even if display connection is parallel.
      @param  b  8-bit value to write.
  */
  void Adafruit_SPITFT::spiWrite(uint8_t b) {
    if (connection == TFT_HARD_SPI) {
  #if defined(__AVR__)
      AVR_WRITESPI(b);
  #elif defined(ESP8266) || defined(ESP32)
      hwspi._spi->write(b);
  #elif defined(ARDUINO_ARCH_RP2040)
      spi_inst_t *pi_spi = hwspi._spi == &SPI ? __SPI0_DEVICE : __SPI1_DEVICE;
      spi_write_blocking(pi_spi, &b, 1);
  #else
      hwspi._spi->transfer(b);
  #endif
    } else if (connection == TFT_SOFT_SPI) {
      for (uint8_t bit = 0; bit < 8; bit++) {
        if (b & 0x80)
          SPI_MOSI_HIGH();
        else
          SPI_MOSI_LOW();
        SPI_SCK_HIGH();
        b <<= 1;
        SPI_SCK_LOW();
      }
    } else { // TFT_PARALLEL
  #if defined(__AVR__)
      *tft8.writePort = b;
  #elif defined(USE_FAST_PINIO)
      if (!tft8.wide)
        *tft8.writePort = b;
      else
        *(volatile uint16_t *)tft8.writePort = b;
  #endif
      TFT_WR_STROBE();
    }
  }

  /*!
      @brief  Write a single command byte to the display. Chip-select and
              transaction must have been previously set -- this ONLY sets
              the device to COMMAND mode, issues the byte and then restores
              DATA mode. There is no corresponding explicit writeData()
              function -- just use spiWrite().
      @param  cmd  8-bit command to write.
  */
  void Adafruit_SPITFT::writeCommand(uint8_t cmd) {
    SPI_DC_LOW();
    spiWrite(cmd);
    SPI_DC_HIGH();
  }

  /*!
      @brief   Read a single 8-bit value from the display. Chip-select and
               transaction must have been previously set -- this ONLY reads
               the byte. This is another of those functions in the library
               with a now-not-accurate name that's being maintained for
               compatibility with outside code. This function is used even if
               display connection is parallel.
      @return  Unsigned 8-bit value read (always zero if USE_FAST_PINIO is
               not supported by the MCU architecture).
  */
  uint8_t Adafruit_SPITFT::spiRead(void) {
    uint8_t b = 0;
    uint16_t w = 0;
    if (connection == TFT_HARD_SPI) {
      return hwspi._spi->transfer((uint8_t)0);
    } else if (connection == TFT_SOFT_SPI) {
      if (swspi._miso >= 0) {
        for (uint8_t i = 0; i < 8; i++) {
          SPI_SCK_HIGH();
          b <<= 1;
          if (SPI_MISO_READ())
            b++;
          SPI_SCK_LOW();
        }
      }
      return b;
    } else { // TFT_PARALLEL
      if (tft8._rd >= 0) {
  #if defined(USE_FAST_PINIO)
        TFT_RD_LOW(); // Read line LOW
  #if defined(__AVR__)
        *tft8.portDir = 0x00; // Set port to input state
        w = *tft8.readPort;   // Read value from port
        *tft8.portDir = 0xFF; // Restore port to output
  #else                       // !__AVR__
        if (!tft8.wide) { // 8-bit TFT connection
  #if defined(HAS_PORT_SET_CLR)
          *tft8.dirClr = 0xFF; // Set port to input state
          w = *tft8.readPort;  // Read value from port
          *tft8.dirSet = 0xFF; // Restore port to output
  #else  // !HAS_PORT_SET_CLR
          *tft8.portDir = 0x00; // Set port to input state
          w = *tft8.readPort;   // Read value from port
          *tft8.portDir = 0xFF; // Restore port to output
  #endif // end HAS_PORT_SET_CLR
        } else { // 16-bit TFT connection
  #if defined(HAS_PORT_SET_CLR)
          *(volatile uint16_t *)tft8.dirClr = 0xFFFF; // Input state
          w = *(volatile uint16_t *)tft8.readPort;    // 16-bit read
          *(volatile uint16_t *)tft8.dirSet = 0xFFFF; // Output state
  #else  // !HAS_PORT_SET_CLR
          *(volatile uint16_t *)tft8.portDir = 0x0000; // Input state
          w = *(volatile uint16_t *)tft8.readPort;     // 16-bit read
          *(volatile uint16_t *)tft8.portDir = 0xFFFF; // Output state
  #endif // end !HAS_PORT_SET_CLR
        }
        TFT_RD_HIGH(); // Read line HIGH
  #endif // end !__AVR__
  #else  // !USE_FAST_PINIO
        w = 0; // Parallel TFT is NOT SUPPORTED without USE_FAST_PINIO
  #endif // end !USE_FAST_PINIO
      }
      return w;
    }
  }

  /*!
      @brief  Issue a single 16-bit value to the display. Chip-select,
              transaction and data/command selection must have been
              previously set -- this ONLY issues the word.
              Thus operates ONLY on 'wide' (16-bit) parallel displays!
      @param  w  16-bit value to write.
  */
  void Adafruit_SPITFT::write16(uint16_t w) {
    if (connection == TFT_PARALLEL) {
  #if defined(USE_FAST_PINIO)
      if (tft8.wide)
        *(volatile uint16_t *)tft8.writePort = w;
  #else
      (void)w; // disable -Wunused-parameter warning
  #endif
      TFT_WR_STROBE();
    }
  }

  /*!
      @brief  Write a single command word to the display. Chip-select and
              transaction must have been previously set -- this ONLY sets
              the device to COMMAND mode, issues the byte and then restores
              DATA mode. This operates ONLY on 'wide' (16-bit) parallel
              displays!
      @param  cmd  16-bit command to write.
  */
  void Adafruit_SPITFT::writeCommand16(uint16_t cmd) {
    SPI_DC_LOW();
    write16(cmd);
    SPI_DC_HIGH();
  }

  /*!
      @brief   Read a single 16-bit value from the display. Chip-select and
               transaction must have been previously set -- this ONLY reads
               the byte. This operates ONLY on 'wide' (16-bit) parallel
               displays!
      @return  Unsigned 16-bit value read (always zero if USE_FAST_PINIO is
               not supported by the MCU architecture).
  */
  uint16_t Adafruit_SPITFT::read16(void) {
    uint16_t w = 0;
    if (connection == TFT_PARALLEL) {
      if (tft8._rd >= 0) {
  #if defined(USE_FAST_PINIO)
        TFT_RD_LOW();    // Read line LOW
        if (tft8.wide) { // 16-bit TFT connection
  #if defined(HAS_PORT_SET_CLR)
          *(volatile uint16_t *)tft8.dirClr = 0xFFFF; // Input state
          w = *(volatile uint16_t *)tft8.readPort;    // 16-bit read
          *(volatile uint16_t *)tft8.dirSet = 0xFFFF; // Output state
  #else                                               // !HAS_PORT_SET_CLR
          *(volatile uint16_t *)tft8.portDir = 0x0000; // Input state
          w = *(volatile uint16_t *)tft8.readPort;     // 16-bit read
          *(volatile uint16_t *)tft8.portDir = 0xFFFF; // Output state
  #endif                                              // end !HAS_PORT_SET_CLR
        }
        TFT_RD_HIGH(); // Read line HIGH
  #else                // !USE_FAST_PINIO
        w = 0; // Parallel TFT is NOT SUPPORTED without USE_FAST_PINIO
  #endif               // end !USE_FAST_PINIO
      }
    }
    return w;
  }

  /*!
      @brief  Set the software (bitbang) SPI MOSI line HIGH.
  */
  inline void Adafruit_SPITFT::SPI_MOSI_HIGH(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
    *swspi.mosiPortSet = 1;
  #else // !KINETISK
    *swspi.mosiPortSet = swspi.mosiPinMask;
  #endif
  #else  // !HAS_PORT_SET_CLR
    *swspi.mosiPort |= swspi.mosiPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(swspi._mosi, HIGH);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Set the software (bitbang) SPI MOSI line LOW.
  */
  inline void Adafruit_SPITFT::SPI_MOSI_LOW(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
    *swspi.mosiPortClr = 1;
  #else // !KINETISK
    *swspi.mosiPortClr = swspi.mosiPinMask;
  #endif
  #else  // !HAS_PORT_SET_CLR
    *swspi.mosiPort &= swspi.mosiPinMaskClr;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(swspi._mosi, LOW);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Set the software (bitbang) SPI SCK line HIGH.
  */
  inline void Adafruit_SPITFT::SPI_SCK_HIGH(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
    *swspi.sckPortSet = 1;
  #else // !KINETISK
    *swspi.sckPortSet = swspi.sckPinMask;
  #endif
  #else  // !HAS_PORT_SET_CLR
    *swspi.sckPort |= swspi.sckPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(swspi._sck, HIGH);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Set the software (bitbang) SPI SCK line LOW.
  */
  inline void Adafruit_SPITFT::SPI_SCK_LOW(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
    *swspi.sckPortClr = 1;
  #else // !KINETISK
    *swspi.sckPortClr = swspi.sckPinMask;
  #endif
  #else  // !HAS_PORT_SET_CLR
    *swspi.sckPort &= swspi.sckPinMaskClr;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(swspi._sck, LOW);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief   Read the state of the software (bitbang) SPI MISO line.
      @return  true if HIGH, false if LOW.
  */
  inline bool Adafruit_SPITFT::SPI_MISO_READ(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(KINETISK)
    return *swspi.misoPort;
  #else  // !KINETISK
    return *swspi.misoPort & swspi.misoPinMask;
  #endif // end !KINETISK
  #else  // !USE_FAST_PINIO
    return digitalRead(swspi._miso);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Issue a single 16-bit value to the display. Chip-select,
              transaction and data/command selection must have been
              previously set -- this ONLY issues the word. Despite the name,
              this function is used even if display connection is parallel;
              name was maintaned for backward compatibility. Naming is also
              not consistent with the 8-bit version, spiWrite(). Sorry about
              that. Again, staying compatible with outside code.
      @param  w  16-bit value to write.
  */
  void Adafruit_SPITFT::SPI_WRITE16(uint16_t w) {
    if (connection == TFT_HARD_SPI) {
  #if defined(__AVR__)
      AVR_WRITESPI(w >> 8);
      AVR_WRITESPI(w);
  #elif defined(ESP8266) || defined(ESP32)
      hwspi._spi->write16(w);
  #elif defined(ARDUINO_ARCH_RP2040)
      spi_inst_t *pi_spi = hwspi._spi == &SPI ? __SPI0_DEVICE : __SPI1_DEVICE;
      w = __builtin_bswap16(w);
      spi_write_blocking(pi_spi, (uint8_t *)&w, 2);
  #elif defined(ARDUINO_ARCH_RTTHREAD)
      hwspi._spi->transfer16(w);
  #else
      // MSB, LSB because TFTs are generally big-endian
      hwspi._spi->transfer(w >> 8);
      hwspi._spi->transfer(w);
  #endif
    } else if (connection == TFT_SOFT_SPI) {
      for (uint8_t bit = 0; bit < 16; bit++) {
        if (w & 0x8000)
          SPI_MOSI_HIGH();
        else
          SPI_MOSI_LOW();
        SPI_SCK_HIGH();
        SPI_SCK_LOW();
        w <<= 1;
      }
    } else { // TFT_PARALLEL
  #if defined(__AVR__)
      *tft8.writePort = w >> 8;
      TFT_WR_STROBE();
      *tft8.writePort = w;
  #elif defined(USE_FAST_PINIO)
      if (!tft8.wide) {
        *tft8.writePort = w >> 8;
        TFT_WR_STROBE();
        *tft8.writePort = w;
      } else {
        *(volatile uint16_t *)tft8.writePort = w;
      }
  #endif
      TFT_WR_STROBE();
    }
  }

  /*!
      @brief  Issue a single 32-bit value to the display. Chip-select,
              transaction and data/command selection must have been
              previously set -- this ONLY issues the longword. Despite the
              name, this function is used even if display connection is
              parallel; name was maintaned for backward compatibility. Naming
              is also not consistent with the 8-bit version, spiWrite().
              Sorry about that. Again, staying compatible with outside code.
      @param  l  32-bit value to write.
  */
  void Adafruit_SPITFT::SPI_WRITE32(uint32_t l) {
    if (connection == TFT_HARD_SPI) {
  #if defined(__AVR__)
      AVR_WRITESPI(l >> 24);
      AVR_WRITESPI(l >> 16);
      AVR_WRITESPI(l >> 8);
      AVR_WRITESPI(l);
  #elif defined(ESP8266) || defined(ESP32)
      hwspi._spi->write32(l);
  #elif defined(ARDUINO_ARCH_RP2040)
      spi_inst_t *pi_spi = hwspi._spi == &SPI ? __SPI0_DEVICE : __SPI1_DEVICE;
      l = __builtin_bswap32(l);
      spi_write_blocking(pi_spi, (uint8_t *)&l, 4);
  #elif defined(ARDUINO_ARCH_RTTHREAD)
      hwspi._spi->transfer16(l >> 16);
      hwspi._spi->transfer16(l);
  #else
      hwspi._spi->transfer(l >> 24);
      hwspi._spi->transfer(l >> 16);
      hwspi._spi->transfer(l >> 8);
      hwspi._spi->transfer(l);
  #endif
    } else if (connection == TFT_SOFT_SPI) {
      for (uint8_t bit = 0; bit < 32; bit++) {
        if (l & 0x80000000)
          SPI_MOSI_HIGH();
        else
          SPI_MOSI_LOW();
        SPI_SCK_HIGH();
        SPI_SCK_LOW();
        l <<= 1;
      }
    } else { // TFT_PARALLEL
  #if defined(__AVR__)
      *tft8.writePort = l >> 24;
      TFT_WR_STROBE();
      *tft8.writePort = l >> 16;
      TFT_WR_STROBE();
      *tft8.writePort = l >> 8;
      TFT_WR_STROBE();
      *tft8.writePort = l;
  #elif defined(USE_FAST_PINIO)
      if (!tft8.wide) {
        *tft8.writePort = l >> 24;
        TFT_WR_STROBE();
        *tft8.writePort = l >> 16;
        TFT_WR_STROBE();
        *tft8.writePort = l >> 8;
        TFT_WR_STROBE();
        *tft8.writePort = l;
      } else {
        *(volatile uint16_t *)tft8.writePort = l >> 16;
        TFT_WR_STROBE();
        *(volatile uint16_t *)tft8.writePort = l;
      }
  #endif
      TFT_WR_STROBE();
    }
  }

  /*!
      @brief  Set the WR line LOW, then HIGH. Used for parallel-connected
              interfaces when writing data.
  */
  inline void Adafruit_SPITFT::TFT_WR_STROBE(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
    *tft8.wrPortClr = 1;
    *tft8.wrPortSet = 1;
  #else  // !KINETISK
    *tft8.wrPortClr = tft8.wrPinMask;
    *tft8.wrPortSet = tft8.wrPinMask;
  #endif // end !KINETISK
  #else  // !HAS_PORT_SET_CLR
    *tft8.wrPort &= tft8.wrPinMaskClr;
    *tft8.wrPort |= tft8.wrPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(tft8._wr, LOW);
    digitalWrite(tft8._wr, HIGH);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Set the RD line HIGH. Used for parallel-connected interfaces
              when reading data.
  */
  inline void Adafruit_SPITFT::TFT_RD_HIGH(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
    *tft8.rdPortSet = tft8.rdPinMask;
  #else  // !HAS_PORT_SET_CLR
    *tft8.rdPort |= tft8.rdPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(tft8._rd, HIGH);
  #endif // end !USE_FAST_PINIO
  }

  /*!
      @brief  Set the RD line LOW. Used for parallel-connected interfaces
              when reading data.
  */
  inline void Adafruit_SPITFT::TFT_RD_LOW(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
    *tft8.rdPortClr = tft8.rdPinMask;
  #else  // !HAS_PORT_SET_CLR
    *tft8.rdPort &= tft8.rdPinMaskClr;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
    digitalWrite(tft8._rd, LOW);
  #endif // end !USE_FAST_PINIO
  }

  #endif // end __AVR_ATtiny85__ __AVR_ATtiny84__


repo_name: wheelie
function_name: begin
last_modified: 2025-11-11 15:52:59
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\Adafruit_SPITFT.h

content:
  /*!
   * @file Adafruit_SPITFT.h
   *
   * Part of Adafruit's GFX graphics library. Originally this class was
   * written to handle a range of color TFT displays connected via SPI,
   * but over time this library and some display-specific subclasses have
   * mutated to include some color OLEDs as well as parallel-interfaced
   * displays. The name's been kept for the sake of older code.
   *
   * Adafruit invests time and resources providing this open source code,
   * please support Adafruit and open-source hardware by purchasing
   * products from Adafruit!
   *
   * Written by Limor "ladyada" Fried for Adafruit Industries,
   * with contributions from the open source community.
   *
   * BSD license, all text here must be included in any redistribution.
   */

  #ifndef _ADAFRUIT_SPITFT_H_
  #define _ADAFRUIT_SPITFT_H_

  // Not for ATtiny, at all
  #if !defined(__AVR_ATtiny85__) && !defined(__AVR_ATtiny84__)

  #include "Adafruit_GFX.h"
  #include <SPI.h>

  // HARDWARE CONFIG ---------------------------------------------------------

  #if defined(__AVR__)
  typedef uint8_t ADAGFX_PORT_t;       ///< PORT values are 8-bit
  #define USE_FAST_PINIO               ///< Use direct PORT register access
  #elif defined(ARDUINO_STM32_FEATHER) // WICED
  typedef class HardwareSPI SPIClass; ///< SPI is a bit odd on WICED
  typedef uint32_t ADAGFX_PORT_t;     ///< PORT values are 32-bit
  #elif defined(__arm__)
  #if defined(ARDUINO_ARCH_SAMD)
  // Adafruit M0, M4
  typedef uint32_t ADAGFX_PORT_t; ///< PORT values are 32-bit
  #define USE_FAST_PINIO   ///< Use direct PORT register access
  #define HAS_PORT_SET_CLR ///< PORTs have set & clear registers
  #elif defined(CORE_TEENSY)
  // PJRC Teensy 4.x
  #if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  typedef uint32_t ADAGFX_PORT_t; ///< PORT values are 32-bit
                                  // PJRC Teensy 3.x
  #else
  typedef uint8_t ADAGFX_PORT_t; ///< PORT values are 8-bit
  #endif
  #define USE_FAST_PINIO   ///< Use direct PORT register access
  #define HAS_PORT_SET_CLR ///< PORTs have set & clear registers
  #else
  // Arduino Due?
  typedef uint32_t ADAGFX_PORT_t; ///< PORT values are 32-bit
  // USE_FAST_PINIO not available here (yet)...Due has a totally different
  // GPIO register set and will require some changes elsewhere (e.g. in
  // constructors especially).
  #endif
  #else                                      // !ARM
  // Probably ESP8266 or ESP32. USE_FAST_PINIO is not available here (yet)
  // but don't worry about it too much...the digitalWrite() implementation
  // on these platforms is reasonably efficient and already RAM-resident,
  // only gotcha then is no parallel connection support for now.
  typedef uint32_t ADAGFX_PORT_t; ///< PORT values are 32-bit
  #endif                                     // end !ARM
  typedef volatile ADAGFX_PORT_t *PORTreg_t; ///< PORT register type

  #if defined(__AVR__) && !defined(__LGT8F__)
  #define DEFAULT_SPI_FREQ 8000000L ///< Hardware SPI default speed
  #else
  #define DEFAULT_SPI_FREQ 16000000L ///< Hardware SPI default speed
  #endif

  #if defined(ADAFRUIT_PYPORTAL) || defined(ADAFRUIT_PYPORTAL_M4_TITANO) ||      \
      defined(ADAFRUIT_PYBADGE_M4_EXPRESS) ||                                    \
      defined(ADAFRUIT_PYGAMER_M4_EXPRESS) ||                                    \
      defined(ADAFRUIT_MONSTER_M4SK_EXPRESS) || defined(NRF52_SERIES) ||         \
      defined(ADAFRUIT_CIRCUITPLAYGROUND_M0)
  #define USE_SPI_DMA ///< Auto DMA
  #else
                                             // #define USE_SPI_DMA ///< If set,
                                             //  use DMA if available
  #endif
  // Another "oops" name -- this now also handles parallel DMA.
  // If DMA is enabled, Arduino sketch MUST #include <Adafruit_ZeroDMA.h>
  // Estimated RAM usage:
  // 4 bytes/pixel on display major axis + 8 bytes/pixel on minor axis,
  // e.g. 320x240 pixels = 320 * 4 + 240 * 8 = 3,200 bytes.

  #if defined(USE_SPI_DMA) && (defined(__SAMD51__) || defined(ARDUINO_SAMD_ZERO))
  #include <Adafruit_ZeroDMA.h>
  #endif

  // This is kind of a kludge. Needed a way to disambiguate the software SPI
  // and parallel constructors via their argument lists. Originally tried a
  // bool as the first argument to the parallel constructor (specifying 8-bit
  // vs 16-bit interface) but the compiler regards this as equivalent to an
  // integer and thus still ambiguous. SO...the parallel constructor requires
  // an enumerated type as the first argument: tft8 (for 8-bit parallel) or
  // tft16 (for 16-bit)...even though 16-bit isn't fully implemented or tested
  // and might never be, still needed that disambiguation from soft SPI.
  /*! For first arg to parallel constructor */
  enum tftBusWidth { tft8bitbus, tft16bitbus };

  // SPI defaults for RP2040
  #if defined(ARDUINO_ARCH_RP2040)
  #ifndef __SPI0_DEVICE
  #define __SPI0_DEVICE spi0
  #endif
  #ifndef __SPI1_DEVICE
  #define __SPI1_DEVICE spi1
  #endif
  #endif

  // CLASS DEFINITION --------------------------------------------------------

  /*!
    @brief  Adafruit_SPITFT is an intermediary class between Adafruit_GFX
            and various hardware-specific subclasses for different displays.
            It handles certain operations that are common to a range of
            displays (address window, area fills, etc.). Originally these were
            all color TFT displays interfaced via SPI, but it's since expanded
            to include color OLEDs and parallel-interfaced TFTs. THE NAME HAS
            BEEN KEPT TO AVOID BREAKING A LOT OF SUBCLASSES AND EXAMPLE CODE.
            Many of the class member functions similarly live on with names
            that don't necessarily accurately describe what they're doing,
            again to avoid breaking a lot of other code. If in doubt, read
            the comments.
  */
  class Adafruit_SPITFT : public Adafruit_GFX {

  public:
    // CONSTRUCTORS --------------------------------------------------------

    // Software SPI constructor: expects width & height (at default rotation
    // setting 0), 4 signal pins (cs, dc, mosi, sclk), 2 optional pins
    // (reset, miso). cs argument is required but can be -1 if unused --
    // rather than moving it to the optional arguments, it was done this way
    // to avoid breaking existing code (-1 option was a later addition).
    Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc, int8_t mosi,
                    int8_t sck, int8_t rst = -1, int8_t miso = -1);

    // Hardware SPI constructor using the default SPI port: expects width &
    // height (at default rotation setting 0), 2 signal pins (cs, dc),
    // optional reset pin. cs is required but can be -1 if unused -- rather
    // than moving it to the optional arguments, it was done this way to
    // avoid breaking existing code (-1 option was a later addition).
    Adafruit_SPITFT(uint16_t w, uint16_t h, int8_t cs, int8_t dc,
                    int8_t rst = -1);

  #if !defined(ESP8266) // See notes in .cpp
    // Hardware SPI constructor using an arbitrary SPI peripheral: expects
    // width & height (rotation 0), SPIClass pointer, 2 signal pins (cs, dc)
    // and optional reset pin. cs is required but can be -1 if unused.
    Adafruit_SPITFT(uint16_t w, uint16_t h, SPIClass *spiClass, int8_t cs,
                    int8_t dc, int8_t rst = -1);
  #endif // end !ESP8266

    // Parallel constructor: expects width & height (rotation 0), flag
    // indicating whether 16-bit (true) or 8-bit (false) interface, 3 signal
    // pins (d0, wr, dc), 3 optional pins (cs, rst, rd). 16-bit parallel
    // isn't even fully implemented but the 'wide' flag was added as a
    // required argument to avoid ambiguity with other constructors.
    Adafruit_SPITFT(uint16_t w, uint16_t h, tftBusWidth busWidth, int8_t d0,
                    int8_t wr, int8_t dc, int8_t cs = -1, int8_t rst = -1,
                    int8_t rd = -1);

    // DESTRUCTOR ----------------------------------------------------------

    ~Adafruit_SPITFT(){};

    // CLASS MEMBER FUNCTIONS ----------------------------------------------

    // These first two functions MUST be declared by subclasses:

    /*!
        @brief  Display-specific initialization function.
        @param  freq  SPI frequency, in hz (or 0 for default or unused).
    */
    virtual void begin(uint32_t freq) = 0;

    /*!
        @brief  Set up the specific display hardware's "address window"
                for subsequent pixel-pushing operations.
        @param  x  Leftmost pixel of area to be drawn (MUST be within
                   display bounds at current rotation setting).
        @param  y  Topmost pixel of area to be drawn (MUST be within
                   display bounds at current rotation setting).
        @param  w  Width of area to be drawn, in pixels (MUST be >0 and,
                   added to x, within display bounds at current rotation).
        @param  h  Height of area to be drawn, in pixels (MUST be >0 and,
                   added to x, within display bounds at current rotation).
    */
    virtual void setAddrWindow(uint16_t x, uint16_t y, uint16_t w,
                               uint16_t h) = 0;

    // Remaining functions do not need to be declared in subclasses
    // unless they wish to provide hardware-specific optimizations.
    // Brief comments here...documented more thoroughly in .cpp file.

    // Subclass' begin() function invokes this to initialize hardware.
    // freq=0 to use default SPI speed. spiMode must be one of the SPI_MODEn
    // values defined in SPI.h, which are NOT the same as 0 for SPI_MODE0,
    // 1 for SPI_MODE1, etc...use ONLY the SPI_MODEn defines! Only!
    // Name is outdated (interface may be parallel) but for compatibility:
    void initSPI(uint32_t freq = 0, uint8_t spiMode = SPI_MODE0);
    void setSPISpeed(uint32_t freq);
    // Chip select and/or hardware SPI transaction start as needed:
    void startWrite(void);
    // Chip deselect and/or hardware SPI transaction end as needed:
    void endWrite(void);
    void sendCommand(uint8_t commandByte, uint8_t *dataBytes,
                     uint8_t numDataBytes);
    void sendCommand(uint8_t commandByte, const uint8_t *dataBytes = NULL,
                     uint8_t numDataBytes = 0);
    void sendCommand16(uint16_t commandWord, const uint8_t *dataBytes = NULL,
                       uint8_t numDataBytes = 0);
    uint8_t readcommand8(uint8_t commandByte, uint8_t index = 0);
    uint16_t readcommand16(uint16_t addr);

    // These functions require a chip-select and/or SPI transaction
    // around them. Higher-level graphics primitives might start a
    // single transaction and then make multiple calls to these functions
    // (e.g. circle or text rendering might make repeated lines or rects)
    // before ending the transaction. It's more efficient than starting a
    // transaction every time.
    void writePixel(int16_t x, int16_t y, uint16_t color);
    void writePixels(uint16_t *colors, uint32_t len, bool block = true,
                     bool bigEndian = false);
    void writeColor(uint16_t color, uint32_t len);
    void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                       uint16_t color);
    void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    // This is a new function, similar to writeFillRect() except that
    // all arguments MUST be onscreen, sorted and clipped. If higher-level
    // primitives can handle their own sorting/clipping, it avoids repeating
    // such operations in the low-level code, making it potentially faster.
    // CALLING THIS WITH UNCLIPPED OR NEGATIVE VALUES COULD BE DISASTROUS.
    inline void writeFillRectPreclipped(int16_t x, int16_t y, int16_t w,
                                        int16_t h, uint16_t color);
    // Another new function, companion to the new non-blocking
    // writePixels() variant.
    void dmaWait(void);
    // Used by writePixels() in some situations, but might have rare need in
    // user code, so it's public...
    bool dmaBusy(void) const; // true if DMA is used and busy, false otherwise
    void swapBytes(uint16_t *src, uint32_t len, uint16_t *dest = NULL);

    // These functions are similar to the 'write' functions above, but with
    // a chip-select and/or SPI transaction built-in. They're typically used
    // solo -- that is, as graphics primitives in themselves, not invoked by
    // higher-level primitives (which should use the functions above).
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
    void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    // A single-pixel push encapsulated in a transaction. I don't think
    // this is used anymore (BMP demos might've used it?) but is provided
    // for backward compatibility, consider it deprecated:
    void pushColor(uint16_t color);

    using Adafruit_GFX::drawRGBBitmap; // Check base class first
    void drawRGBBitmap(int16_t x, int16_t y, uint16_t *pcolors, int16_t w,
                       int16_t h);

    void invertDisplay(bool i);
    uint16_t color565(uint8_t r, uint8_t g, uint8_t b);

    // Despite parallel additions, function names kept for compatibility:
    void spiWrite(uint8_t b);          // Write single byte as DATA
    void writeCommand(uint8_t cmd);    // Write single byte as COMMAND
    uint8_t spiRead(void);             // Read single byte of data
    void write16(uint16_t w);          // Write 16-bit value as DATA
    void writeCommand16(uint16_t cmd); // Write 16-bit value as COMMAND
    uint16_t read16(void);             // Read single 16-bit value

    // Most of these low-level functions were formerly macros in
    // Adafruit_SPITFT_Macros.h. Some have been made into inline functions
    // to avoid macro mishaps. Despite the addition of code for a parallel
    // display interface, the names have been kept for backward
    // compatibility (some subclasses may be invoking these):
    void SPI_WRITE16(uint16_t w); // Not inline
    void SPI_WRITE32(uint32_t l); // Not inline
    // Old code had both a spiWrite16() function and SPI_WRITE16 macro
    // in addition to the SPI_WRITE32 macro. The latter two have been
    // made into functions here, and spiWrite16() removed (use SPI_WRITE16()
    // instead). It looks like most subclasses had gotten comfortable with
    // SPI_WRITE16 and SPI_WRITE32 anyway so those names were kept rather
    // than the less-obnoxious camelcase variants, oh well.

    // Placing these functions entirely in the class definition inlines
    // them implicitly them while allowing their use in other code:

    /*!
        @brief  Set the chip-select line HIGH. Does NOT check whether CS pin
                is set (>=0), that should be handled in calling function.
                Despite function name, this is used even if the display
                connection is parallel.
    */
    void SPI_CS_HIGH(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
      *csPortSet = 1;
  #else  // !KINETISK
      *csPortSet = csPinMask;
  #endif // end !KINETISK
  #else  // !HAS_PORT_SET_CLR
      *csPort |= csPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
      digitalWrite(_cs, HIGH);
  #endif // end !USE_FAST_PINIO
    }

    /*!
        @brief  Set the chip-select line LOW. Does NOT check whether CS pin
                is set (>=0), that should be handled in calling function.
                Despite function name, this is used even if the display
                connection is parallel.
    */
    void SPI_CS_LOW(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
      *csPortClr = 1;
  #else  // !KINETISK
      *csPortClr = csPinMask;
  #endif // end !KINETISK
  #else  // !HAS_PORT_SET_CLR
      *csPort &= csPinMaskClr;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
      digitalWrite(_cs, LOW);
  #endif // end !USE_FAST_PINIO
    }

    /*!
        @brief  Set the data/command line HIGH (data mode).
    */
    void SPI_DC_HIGH(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
      *dcPortSet = 1;
  #else  // !KINETISK
      *dcPortSet = dcPinMask;
  #endif // end !KINETISK
  #else  // !HAS_PORT_SET_CLR
      *dcPort |= dcPinMaskSet;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
      digitalWrite(_dc, HIGH);
  #endif // end !USE_FAST_PINIO
    }

    /*!
        @brief  Set the data/command line LOW (command mode).
    */
    void SPI_DC_LOW(void) {
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if defined(KINETISK)
      *dcPortClr = 1;
  #else  // !KINETISK
      *dcPortClr = dcPinMask;
  #endif // end !KINETISK
  #else  // !HAS_PORT_SET_CLR
      *dcPort &= dcPinMaskClr;
  #endif // end !HAS_PORT_SET_CLR
  #else  // !USE_FAST_PINIO
      digitalWrite(_dc, LOW);
  #endif // end !USE_FAST_PINIO
    }

  protected:
    // A few more low-level member functions -- some may have previously
    // been macros. Shouldn't have a need to access these externally, so
    // they've been moved to the protected section. Additionally, they're
    // declared inline here and the code is in the .cpp file, since outside
    // code doesn't need to see these.
    inline void SPI_MOSI_HIGH(void);
    inline void SPI_MOSI_LOW(void);
    inline void SPI_SCK_HIGH(void);
    inline void SPI_SCK_LOW(void);
    inline bool SPI_MISO_READ(void);
    inline void SPI_BEGIN_TRANSACTION(void);
    inline void SPI_END_TRANSACTION(void);
    inline void TFT_WR_STROBE(void); // Parallel interface write strobe
    inline void TFT_RD_HIGH(void);   // Parallel interface read high
    inline void TFT_RD_LOW(void);    // Parallel interface read low

    // CLASS INSTANCE VARIABLES --------------------------------------------

    // Here be dragons! There's a big union of three structures here --
    // one each for hardware SPI, software (bitbang) SPI, and parallel
    // interfaces. This is to save some memory, since a display's connection
    // will be only one of these. The order of some things is a little weird
    // in an attempt to get values to align and pack better in RAM.

  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
    PORTreg_t csPortSet; ///< PORT register for chip select SET
    PORTreg_t csPortClr; ///< PORT register for chip select CLEAR
    PORTreg_t dcPortSet; ///< PORT register for data/command SET
    PORTreg_t dcPortClr; ///< PORT register for data/command CLEAR
  #else                  // !HAS_PORT_SET_CLR
    PORTreg_t csPort; ///< PORT register for chip select
    PORTreg_t dcPort; ///< PORT register for data/command
  #endif                 // end HAS_PORT_SET_CLR
  #endif                 // end USE_FAST_PINIO
  #if defined(__cplusplus) && (__cplusplus >= 201100)
    union {
  #endif
      struct {          //   Values specific to HARDWARE SPI:
        SPIClass *_spi; ///< SPI class pointer
  #if defined(SPI_HAS_TRANSACTION)
        SPISettings settings; ///< SPI transaction settings
  #else
      uint32_t _freq; ///< SPI bitrate (if no SPI transactions)
  #endif
        uint32_t _mode; ///< SPI data mode (transactions or no)
      } hwspi;          ///< Hardware SPI values
      struct {          //   Values specific to SOFTWARE SPI:
  #if defined(USE_FAST_PINIO)
        PORTreg_t misoPort; ///< PORT (PIN) register for MISO
  #if defined(HAS_PORT_SET_CLR)
        PORTreg_t mosiPortSet; ///< PORT register for MOSI SET
        PORTreg_t mosiPortClr; ///< PORT register for MOSI CLEAR
        PORTreg_t sckPortSet;  ///< PORT register for SCK SET
        PORTreg_t sckPortClr;  ///< PORT register for SCK CLEAR
  #if !defined(KINETISK)
        ADAGFX_PORT_t mosiPinMask; ///< Bitmask for MOSI
        ADAGFX_PORT_t sckPinMask;  ///< Bitmask for SCK
  #endif                           // end !KINETISK
  #else                            // !HAS_PORT_SET_CLR
        PORTreg_t mosiPort;           ///< PORT register for MOSI
        PORTreg_t sckPort;            ///< PORT register for SCK
        ADAGFX_PORT_t mosiPinMaskSet; ///< Bitmask for MOSI SET (OR)
        ADAGFX_PORT_t mosiPinMaskClr; ///< Bitmask for MOSI CLEAR (AND)
        ADAGFX_PORT_t sckPinMaskSet;  ///< Bitmask for SCK SET (OR bitmask)
        ADAGFX_PORT_t sckPinMaskClr;  ///< Bitmask for SCK CLEAR (AND)
  #endif                           // end HAS_PORT_SET_CLR
  #if !defined(KINETISK)
        ADAGFX_PORT_t misoPinMask; ///< Bitmask for MISO
  #endif                           // end !KINETISK
  #endif                           // end USE_FAST_PINIO
        int8_t _mosi;              ///< MOSI pin #
        int8_t _miso;              ///< MISO pin #
        int8_t _sck;               ///< SCK pin #
      } swspi;                     ///< Software SPI values
      struct {                     //   Values specific to 8-bit parallel:
  #if defined(USE_FAST_PINIO)

  #if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
        volatile uint32_t *writePort; ///< PORT register for DATA WRITE
        volatile uint32_t *readPort;  ///< PORT (PIN) register for DATA READ
  #else
        volatile uint8_t *writePort; ///< PORT register for DATA WRITE
        volatile uint8_t *readPort;  ///< PORT (PIN) register for DATA READ
  #endif
  #if defined(HAS_PORT_SET_CLR)
        // Port direction register pointers are always 8-bit regardless of
        // PORTreg_t -- even if 32-bit port, we modify a byte-aligned 8 bits.
  #if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
        volatile uint32_t *dirSet; ///< PORT byte data direction SET
        volatile uint32_t *dirClr; ///< PORT byte data direction CLEAR
  #else
        volatile uint8_t *dirSet; ///< PORT byte data direction SET
        volatile uint8_t *dirClr; ///< PORT byte data direction CLEAR
  #endif
        PORTreg_t wrPortSet; ///< PORT register for write strobe SET
        PORTreg_t wrPortClr; ///< PORT register for write strobe CLEAR
        PORTreg_t rdPortSet; ///< PORT register for read strobe SET
        PORTreg_t rdPortClr; ///< PORT register for read strobe CLEAR
  #if !defined(KINETISK)
        ADAGFX_PORT_t wrPinMask; ///< Bitmask for write strobe
  #endif                         // end !KINETISK
        ADAGFX_PORT_t rdPinMask; ///< Bitmask for read strobe
  #else                          // !HAS_PORT_SET_CLR
        // Port direction register pointer is always 8-bit regardless of
        // PORTreg_t -- even if 32-bit port, we modify a byte-aligned 8 bits.
        volatile uint8_t *portDir;  ///< PORT direction register
        PORTreg_t wrPort;           ///< PORT register for write strobe
        PORTreg_t rdPort;           ///< PORT register for read strobe
        ADAGFX_PORT_t wrPinMaskSet; ///< Bitmask for write strobe SET (OR)
        ADAGFX_PORT_t wrPinMaskClr; ///< Bitmask for write strobe CLEAR (AND)
        ADAGFX_PORT_t rdPinMaskSet; ///< Bitmask for read strobe SET (OR)
        ADAGFX_PORT_t rdPinMaskClr; ///< Bitmask for read strobe CLEAR (AND)
  #endif                         // end HAS_PORT_SET_CLR
  #endif                         // end USE_FAST_PINIO
        int8_t _d0;              ///< Data pin 0 #
        int8_t _wr;              ///< Write strobe pin #
        int8_t _rd;              ///< Read strobe pin # (or -1)
        bool wide = 0;           ///< If true, is 16-bit interface
      } tft8;                    ///< Parallel interface settings
  #if defined(__cplusplus) && (__cplusplus >= 201100)
    }; ///< Only one interface is active
  #endif
  #if defined(USE_SPI_DMA) &&                                                    \
      (defined(__SAMD51__) ||                                                    \
       defined(ARDUINO_SAMD_ZERO))     // Used by hardware SPI and tft8
    Adafruit_ZeroDMA dma;              ///< DMA instance
    DmacDescriptor *dptr = NULL;       ///< 1st descriptor
    DmacDescriptor *descriptor = NULL; ///< Allocated descriptor list
    uint16_t *pixelBuf[2];             ///< Working buffers
    uint16_t maxFillLen;               ///< Max pixels per DMA xfer
    uint16_t lastFillColor = 0;        ///< Last color used w/fill
    uint32_t lastFillLen = 0;          ///< # of pixels w/last fill
    uint8_t onePixelBuf;               ///< For hi==lo fill
  #endif
  #if defined(USE_FAST_PINIO)
  #if defined(HAS_PORT_SET_CLR)
  #if !defined(KINETISK)
    ADAGFX_PORT_t csPinMask; ///< Bitmask for chip select
    ADAGFX_PORT_t dcPinMask; ///< Bitmask for data/command
  #endif                     // end !KINETISK
  #else                      // !HAS_PORT_SET_CLR
    ADAGFX_PORT_t csPinMaskSet; ///< Bitmask for chip select SET (OR)
    ADAGFX_PORT_t csPinMaskClr; ///< Bitmask for chip select CLEAR (AND)
    ADAGFX_PORT_t dcPinMaskSet; ///< Bitmask for data/command SET (OR)
    ADAGFX_PORT_t dcPinMaskClr; ///< Bitmask for data/command CLEAR (AND)
  #endif                     // end HAS_PORT_SET_CLR
  #endif                     // end USE_FAST_PINIO
    uint8_t connection;      ///< TFT_HARD_SPI, TFT_SOFT_SPI, etc.
    int8_t _rst;             ///< Reset pin # (or -1)
    int8_t _cs;              ///< Chip select pin # (or -1)
    int8_t _dc;              ///< Data/command pin #

    int16_t _xstart = 0;          ///< Internal framebuffer X offset
    int16_t _ystart = 0;          ///< Internal framebuffer Y offset
    uint8_t invertOnCommand = 0;  ///< Command to enable invert mode
    uint8_t invertOffCommand = 0; ///< Command to disable invert mode

    uint32_t _freq = 0; ///< Dummy var to keep subclasses happy
  };

  #endif // end __AVR_ATtiny85__ __AVR_ATtiny84__
  #endif // end _ADAFRUIT_SPITFT_H_


repo_name: wheelie
function_name: getEvent
last_modified: 2023-11-15 12:59:21
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\Adafruit_MPU6050.h

content:
  /*!
   *  @file Adafruit_MPU6050.h
   *
   * 	I2C Driver for MPU6050 6-DoF Accelerometer and Gyro
   *
   * 	This is a library for the Adafruit MPU6050 breakout:
   * 	https://www.adafruit.com/products/3886
   *
   * 	Adafruit invests time and resources providing this open source code,
   *  please support Adafruit and open-source hardware by purchasing products from
   * 	Adafruit!
   *
   *
   *	BSD license (see license.txt)
   */

  #ifndef _ADAFRUIT_MPU6050_H
  #define _ADAFRUIT_MPU6050_H

  #include "Arduino.h"
  #include <Adafruit_BusIO_Register.h>
  #include <Adafruit_I2CDevice.h>
  #include <Adafruit_Sensor.h>
  #include <Wire.h>

  #define MPU6050_I2CADDR_DEFAULT                                                \
    0x68                         ///< MPU6050 default i2c address w/ AD0 high
  #define MPU6050_DEVICE_ID 0x68 ///< The correct MPU6050_WHO_AM_I value

  #define MPU6050_SELF_TEST_X                                                    \
    0x0D ///< Self test factory calibrated values register
  #define MPU6050_SELF_TEST_Y                                                    \
    0x0E ///< Self test factory calibrated values register
  #define MPU6050_SELF_TEST_Z                                                    \
    0x0F ///< Self test factory calibrated values register
  #define MPU6050_SELF_TEST_A                                                    \
    0x10 ///< Self test factory calibrated values register
  #define MPU6050_SMPLRT_DIV 0x19  ///< sample rate divisor register
  #define MPU6050_CONFIG 0x1A      ///< General configuration register
  #define MPU6050_GYRO_CONFIG 0x1B ///< Gyro specfic configuration register
  #define MPU6050_ACCEL_CONFIG                                                   \
    0x1C ///< Accelerometer specific configration register
  #define MPU6050_INT_PIN_CONFIG 0x37 ///< Interrupt pin configuration register
  #define MPU6050_INT_ENABLE 0x38     ///< Interrupt enable configuration register
  #define MPU6050_INT_STATUS 0x3A     ///< Interrupt status register
  #define MPU6050_WHO_AM_I 0x75       ///< Divice ID register
  #define MPU6050_SIGNAL_PATH_RESET 0x68 ///< Signal path reset register
  #define MPU6050_USER_CTRL 0x6A         ///< FIFO and I2C Master control register
  #define MPU6050_PWR_MGMT_1 0x6B        ///< Primary power/sleep control register
  #define MPU6050_PWR_MGMT_2 0x6C ///< Secondary power/sleep control register
  #define MPU6050_TEMP_H 0x41     ///< Temperature data high byte register
  #define MPU6050_TEMP_L 0x42     ///< Temperature data low byte register
  #define MPU6050_ACCEL_OUT 0x3B  ///< base address for sensor data reads
  #define MPU6050_MOT_THR 0x1F    ///< Motion detection threshold bits [7:0]
  #define MPU6050_MOT_DUR                                                        \
    0x20 ///< Duration counter threshold for motion int. 1 kHz rate, LSB = 1 ms

  /**
   * @brief FSYNC output values
   *
   * Allowed values for `setFsyncSampleOutput`.
   */
  typedef enum fsync_out {
    MPU6050_FSYNC_OUT_DISABLED,
    MPU6050_FSYNC_OUT_TEMP,
    MPU6050_FSYNC_OUT_GYROX,
    MPU6050_FSYNC_OUT_GYROY,
    MPU6050_FSYNC_OUT_GYROZ,
    MPU6050_FSYNC_OUT_ACCELX,
    MPU6050_FSYNC_OUT_ACCELY,
    MPU6050_FSYNC_OUT_ACCEL_Z,
  } mpu6050_fsync_out_t;

  /**
   * @brief Clock source options
   *
   * Allowed values for `setClock`.
   */
  typedef enum clock_select {
    MPU6050_INTR_8MHz,
    MPU6050_PLL_GYROX,
    MPU6050_PLL_GYROY,
    MPU6050_PLL_GYROZ,
    MPU6050_PLL_EXT_32K,
    MPU6050_PLL_EXT_19MHz,
    MPU6050_STOP = 7,
  } mpu6050_clock_select_t;

  /**
   * @brief Accelerometer range options
   *
   * Allowed values for `setAccelerometerRange`.
   */
  typedef enum {
    MPU6050_RANGE_2_G = 0b00,  ///< +/- 2g (default value)
    MPU6050_RANGE_4_G = 0b01,  ///< +/- 4g
    MPU6050_RANGE_8_G = 0b10,  ///< +/- 8g
    MPU6050_RANGE_16_G = 0b11, ///< +/- 16g
  } mpu6050_accel_range_t;

  /**
   * @brief Gyroscope range options
   *
   * Allowed values for `setGyroRange`.
   */
  typedef enum {
    MPU6050_RANGE_250_DEG,  ///< +/- 250 deg/s (default value)
    MPU6050_RANGE_500_DEG,  ///< +/- 500 deg/s
    MPU6050_RANGE_1000_DEG, ///< +/- 1000 deg/s
    MPU6050_RANGE_2000_DEG, ///< +/- 2000 deg/s
  } mpu6050_gyro_range_t;

  /**
   * @brief Digital low pass filter bandthwidth options
   *
   * Allowed values for `setFilterBandwidth`.
   */
  typedef enum {
    MPU6050_BAND_260_HZ, ///< Docs imply this disables the filter
    MPU6050_BAND_184_HZ, ///< 184 Hz
    MPU6050_BAND_94_HZ,  ///< 94 Hz
    MPU6050_BAND_44_HZ,  ///< 44 Hz
    MPU6050_BAND_21_HZ,  ///< 21 Hz
    MPU6050_BAND_10_HZ,  ///< 10 Hz
    MPU6050_BAND_5_HZ,   ///< 5 Hz
  } mpu6050_bandwidth_t;

  /**
   * @brief Accelerometer high pass filter options
   *
   * Allowed values for `setHighPassFilter`.
   */
  typedef enum {
    MPU6050_HIGHPASS_DISABLE,
    MPU6050_HIGHPASS_5_HZ,
    MPU6050_HIGHPASS_2_5_HZ,
    MPU6050_HIGHPASS_1_25_HZ,
    MPU6050_HIGHPASS_0_63_HZ,
    MPU6050_HIGHPASS_UNUSED,
    MPU6050_HIGHPASS_HOLD,
  } mpu6050_highpass_t;

  /**
   * @brief Periodic measurement options
   *
   * Allowed values for `setCycleRate`.
   */
  typedef enum {
    MPU6050_CYCLE_1_25_HZ, ///< 1.25 Hz
    MPU6050_CYCLE_5_HZ,    ///< 5 Hz
    MPU6050_CYCLE_20_HZ,   ///< 20 Hz
    MPU6050_CYCLE_40_HZ,   ///< 40 Hz
  } mpu6050_cycle_rate_t;

  class Adafruit_MPU6050;

  /** Adafruit Unified Sensor interface for temperature component of MPU6050 */
  class Adafruit_MPU6050_Temp : public Adafruit_Sensor {
  public:
    /** @brief Create an Adafruit_Sensor compatible object for the temp sensor
        @param parent A pointer to the MPU6050 class */
    Adafruit_MPU6050_Temp(Adafruit_MPU6050 *parent) { _theMPU6050 = parent; }
    bool getEvent(sensors_event_t *);
    void getSensor(sensor_t *);

  private:
    int _sensorID = 0x650;
    Adafruit_MPU6050 *_theMPU6050 = NULL;
  };

  /** Adafruit Unified Sensor interface for accelerometer component of MPU6050 */
  class Adafruit_MPU6050_Accelerometer : public Adafruit_Sensor {
  public:
    /** @brief Create an Adafruit_Sensor compatible object for the accelerometer
       sensor
        @param parent A pointer to the MPU6050 class */
    Adafruit_MPU6050_Accelerometer(Adafruit_MPU6050 *parent) {
      _theMPU6050 = parent;
    }
    bool getEvent(sensors_event_t *);
    void getSensor(sensor_t *);

  private:
    int _sensorID = 0x651;
    Adafruit_MPU6050 *_theMPU6050 = NULL;
  };

  /** Adafruit Unified Sensor interface for gyro component of MPU6050 */
  class Adafruit_MPU6050_Gyro : public Adafruit_Sensor {
  public:
    /** @brief Create an Adafruit_Sensor compatible object for the gyro sensor
        @param parent A pointer to the MPU6050 class */
    Adafruit_MPU6050_Gyro(Adafruit_MPU6050 *parent) { _theMPU6050 = parent; }
    bool getEvent(sensors_event_t *);
    void getSensor(sensor_t *);

  private:
    int _sensorID = 0x652;
    Adafruit_MPU6050 *_theMPU6050 = NULL;
  };

  /*!
   *    @brief  Class that stores state and functions for interacting with
   *            the MPU6050 I2C Digital Potentiometer
   */
  class Adafruit_MPU6050 final {
  public:
    Adafruit_MPU6050();
    ~Adafruit_MPU6050();

    bool begin(uint8_t i2c_addr = MPU6050_I2CADDR_DEFAULT, TwoWire *wire = &Wire,
               int32_t sensorID = 0);

    // Adafruit_Sensor API/Interface
    bool getEvent(sensors_event_t *accel, sensors_event_t *gyro,
                  sensors_event_t *temp);

    mpu6050_accel_range_t getAccelerometerRange(void);
    void setAccelerometerRange(mpu6050_accel_range_t);

    mpu6050_gyro_range_t getGyroRange(void);
    void setGyroRange(mpu6050_gyro_range_t);

    void setInterruptPinPolarity(bool active_low);
    void setInterruptPinLatch(bool held);
    void setFsyncSampleOutput(mpu6050_fsync_out_t fsync_output);

    mpu6050_highpass_t getHighPassFilter(void);
    void setHighPassFilter(mpu6050_highpass_t bandwidth);

    void setMotionInterrupt(bool active);
    void setMotionDetectionThreshold(uint8_t thr);
    void setMotionDetectionDuration(uint8_t dur);
    bool getMotionInterruptStatus(void);

    mpu6050_fsync_out_t getFsyncSampleOutput(void);
    void setI2CBypass(bool bypass);

    void setClock(mpu6050_clock_select_t);
    mpu6050_clock_select_t getClock(void);

    void setFilterBandwidth(mpu6050_bandwidth_t bandwidth);
    mpu6050_bandwidth_t getFilterBandwidth(void);

    void setSampleRateDivisor(uint8_t);
    uint8_t getSampleRateDivisor(void);

    bool enableSleep(bool enable);
    bool enableCycle(bool enable);

    void setCycleRate(mpu6050_cycle_rate_t rate);
    mpu6050_cycle_rate_t getCycleRate(void);

    bool setGyroStandby(bool xAxisStandby, bool yAxisStandby, bool zAxisStandby);
    bool setAccelerometerStandby(bool xAxisStandby, bool yAxisStandby,
                                 bool zAxisStandby);
    bool setTemperatureStandby(bool enable);

    void reset(void);

    Adafruit_Sensor *getTemperatureSensor(void);
    Adafruit_Sensor *getAccelerometerSensor(void);
    Adafruit_Sensor *getGyroSensor(void);

  private:
    void _getRawSensorData(void);
    void _scaleSensorData(void);

  protected:
    float temperature, ///< Last reading's temperature (C)
        accX,          ///< Last reading's accelerometer X axis m/s^2
        accY,          ///< Last reading's accelerometer Y axis m/s^2
        accZ,          ///< Last reading's accelerometer Z axis m/s^2
        gyroX,         ///< Last reading's gyro X axis in rad/s
        gyroY,         ///< Last reading's gyro Y axis in rad/s
        gyroZ;         ///< Last reading's gyro Z axis in rad/s

    Adafruit_I2CDevice *i2c_dev = NULL; ///< Pointer to I2C bus interface

    Adafruit_MPU6050_Temp *temp_sensor = NULL; ///< Temp sensor data object
    Adafruit_MPU6050_Accelerometer *accel_sensor =
        NULL;                                  ///< Accelerometer data object
    Adafruit_MPU6050_Gyro *gyro_sensor = NULL; ///< Gyro data object

    uint16_t _sensorid_accel, ///< ID number for accelerometer
        _sensorid_gyro,       ///< ID number for gyro
        _sensorid_temp;       ///< ID number for temperature

    void _read(void);
    virtual bool _init(int32_t sensor_id);

  private:
    friend class Adafruit_MPU6050_Temp; ///< Gives access to private members to
                                        ///< Temp data object
    friend class Adafruit_MPU6050_Accelerometer; ///< Gives access to private
                                                 ///< members to Accelerometer
                                                 ///< data object
    friend class Adafruit_MPU6050_Gyro; ///< Gives access to private members to
                                        ///< Gyro data object

    int16_t rawAccX, rawAccY, rawAccZ, rawTemp, rawGyroX, rawGyroY, rawGyroZ;

    void fillTempEvent(sensors_event_t *temp, uint32_t timestamp);
    void fillAccelEvent(sensors_event_t *accel, uint32_t timestamp);
    void fillGyroEvent(sensors_event_t *gyro, uint32_t timestamp);
  };

  #endif


repo_name: wheelie
function_name: begin
last_modified: 2025-12-05 14:38:14
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\Adafruit_SSD1306.h

content:
  /*!
   * @file Adafruit_SSD1306.h
   *
   * This is part of for Adafruit's SSD1306 library for monochrome
   * OLED displays: http://www.adafruit.com/category/63_98
   *
   * These displays use I2C or SPI to communicate. I2C requires 2 pins
   * (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
   * select, data/command) and optionally a reset pin. Hardware SPI or
   * 'bitbang' software SPI are both supported.
   *
   * Adafruit invests time and resources providing this open source code,
   * please support Adafruit and open-source hardware by purchasing
   * products from Adafruit!
   *
   * Written by Limor Fried/Ladyada for Adafruit Industries, with
   * contributions from the open source community.
   *
   * BSD license, all text above, and the splash screen header file,
   * must be included in any redistribution.
   *
   */

  #ifndef _Adafruit_SSD1306_H_
  #define _Adafruit_SSD1306_H_

  // ONE of the following three lines must be #defined:
  // #define SSD1306_128_64 ///< DEPRECTAED: old way to specify 128x64 screen
  #define SSD1306_128_32 ///< DEPRECATED: old way to specify 128x32 screen
  // #define SSD1306_96_16  ///< DEPRECATED: old way to specify 96x16 screen
  // This establishes the screen dimensions in old Adafruit_SSD1306 sketches
  // (NEW CODE SHOULD IGNORE THIS, USE THE CONSTRUCTORS THAT ACCEPT WIDTH
  // AND HEIGHT ARGUMENTS).

  // Uncomment to disable Adafruit splash logo
  // #define SSD1306_NO_SPLASH

  #if defined(ARDUINO_STM32_FEATHER)
  typedef class HardwareSPI SPIClass;
  #endif

  #include <Adafruit_GFX.h>
  #include <SPI.h>
  #include <Wire.h>

  #if defined(__AVR__)
  typedef volatile uint8_t PortReg;
  typedef uint8_t PortMask;
  #define HAVE_PORTREG
  #elif defined(__SAM3X8E__)
  typedef volatile RwReg PortReg;
  typedef uint32_t PortMask;
  #define HAVE_PORTREG
  #elif defined(ARDUINO_ARCH_RTTHREAD)
  #undef HAVE_PORTREG
  #elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) &&                      \
      !defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_RP2040)
  typedef volatile uint32_t PortReg;
  typedef uint32_t PortMask;
  #define HAVE_PORTREG
  #endif

  /// The following "raw" color names are kept for backwards client compatability
  /// They can be disabled by predefining this macro before including the Adafruit
  /// header client code will then need to be modified to use the scoped enum
  /// values directly
  #ifndef NO_ADAFRUIT_SSD1306_COLOR_COMPATIBILITY
  #define BLACK SSD1306_BLACK     ///< Draw 'off' pixels
  #define WHITE SSD1306_WHITE     ///< Draw 'on' pixels
  #define INVERSE SSD1306_INVERSE ///< Invert pixels
  #endif
  /// fit into the SSD1306_ naming scheme
  #define SSD1306_BLACK 0   ///< Draw 'off' pixels
  #define SSD1306_WHITE 1   ///< Draw 'on' pixels
  #define SSD1306_INVERSE 2 ///< Invert pixels

  #define SSD1306_MEMORYMODE 0x20          ///< See datasheet
  #define SSD1306_COLUMNADDR 0x21          ///< See datasheet
  #define SSD1306_PAGEADDR 0x22            ///< See datasheet
  #define SSD1306_SETCONTRAST 0x81         ///< See datasheet
  #define SSD1306_CHARGEPUMP 0x8D          ///< See datasheet
  #define SSD1306_SEGREMAP 0xA0            ///< See datasheet
  #define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
  #define SSD1306_DISPLAYALLON 0xA5        ///< Not currently used
  #define SSD1306_NORMALDISPLAY 0xA6       ///< See datasheet
  #define SSD1306_INVERTDISPLAY 0xA7       ///< See datasheet
  #define SSD1306_SETMULTIPLEX 0xA8        ///< See datasheet
  #define SSD1306_DISPLAYOFF 0xAE          ///< See datasheet
  #define SSD1306_DISPLAYON 0xAF           ///< See datasheet
  #define SSD1306_COMSCANINC 0xC0          ///< Not currently used
  #define SSD1306_COMSCANDEC 0xC8          ///< See datasheet
  #define SSD1306_SETDISPLAYOFFSET 0xD3    ///< See datasheet
  #define SSD1306_SETDISPLAYCLOCKDIV 0xD5  ///< See datasheet
  #define SSD1306_SETPRECHARGE 0xD9        ///< See datasheet
  #define SSD1306_SETCOMPINS 0xDA          ///< See datasheet
  #define SSD1306_SETVCOMDETECT 0xDB       ///< See datasheet

  #define SSD1306_SETLOWCOLUMN 0x00  ///< Not currently used
  #define SSD1306_SETHIGHCOLUMN 0x10 ///< Not currently used
  #define SSD1306_SETSTARTLINE 0x40  ///< See datasheet

  #define SSD1306_EXTERNALVCC 0x01  ///< External display voltage source
  #define SSD1306_SWITCHCAPVCC 0x02 ///< Gen. display voltage from 3.3V

  #define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26              ///< Init rt scroll
  #define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27               ///< Init left scroll
  #define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll
  #define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A  ///< Init diag scroll
  #define SSD1306_DEACTIVATE_SCROLL 0x2E                    ///< Stop scroll
  #define SSD1306_ACTIVATE_SCROLL 0x2F                      ///< Start scroll
  #define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3             ///< Set scroll range

  // Deprecated size stuff for backwards compatibility with old sketches
  #if defined SSD1306_128_64
  #define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_64 defined
  #define SSD1306_LCDHEIGHT 64 ///< DEPRECATED: height w/SSD1306_128_64 defined
  #endif
  #if defined SSD1306_128_32
  #define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_32 defined
  #define SSD1306_LCDHEIGHT 32 ///< DEPRECATED: height w/SSD1306_128_32 defined
  #endif
  #if defined SSD1306_96_16
  #define SSD1306_LCDWIDTH 96  ///< DEPRECATED: width w/SSD1306_96_16 defined
  #define SSD1306_LCDHEIGHT 16 ///< DEPRECATED: height w/SSD1306_96_16 defined
  #endif

  /*!
      @brief  Class that stores state and functions for interacting with
              SSD1306 OLED displays.
  */
  class Adafruit_SSD1306 : public Adafruit_GFX {
  public:
    // NEW CONSTRUCTORS -- recommended for new projects
    Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi = &Wire,
                     int8_t rst_pin = -1, uint32_t clkDuring = 400000UL,
                     uint32_t clkAfter = 100000UL);
    Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin,
                     int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
    Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi, int8_t dc_pin,
                     int8_t rst_pin, int8_t cs_pin, uint32_t bitrate = 8000000UL);

    // DEPRECATED CONSTRUCTORS - for back compatibility, avoid in new projects
    Adafruit_SSD1306(int8_t mosi_pin, int8_t sclk_pin, int8_t dc_pin,
                     int8_t rst_pin, int8_t cs_pin);
    Adafruit_SSD1306(int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
    Adafruit_SSD1306(int8_t rst_pin = -1);

    ~Adafruit_SSD1306(void);

    bool begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = 0,
               bool reset = true, bool periphBegin = true);
    void display(void);
    void clearDisplay(void);
    void invertDisplay(bool i);
    void dim(bool dim);
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
    virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
    void startscrollright(uint8_t start, uint8_t stop);
    void startscrollleft(uint8_t start, uint8_t stop);
    void startscrolldiagright(uint8_t start, uint8_t stop);
    void startscrolldiagleft(uint8_t start, uint8_t stop);
    void stopscroll(void);
    void ssd1306_command(uint8_t c);
    bool getPixel(int16_t x, int16_t y);
    uint8_t *getBuffer(void);

  protected:
    inline void SPIwrite(uint8_t d) __attribute__((always_inline));
    void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color);
    void drawFastVLineInternal(int16_t x, int16_t y, int16_t h, uint16_t color);
    void ssd1306_command1(uint8_t c);
    void ssd1306_commandList(const uint8_t *c, uint8_t n);

    SPIClass *spi;   ///< Initialized during construction when using SPI. See
                     ///< SPI.cpp, SPI.h
    TwoWire *wire;   ///< Initialized during construction when using I2C. See
                     ///< Wire.cpp, Wire.h
    uint8_t *buffer; ///< Buffer data used for display buffer. Allocated when
                     ///< begin method is called.
    int8_t i2caddr;  ///< I2C address initialized when begin method is called.
    int8_t vccstate; ///< VCC selection, set by begin method.
    int8_t page_end; ///< not used
    int8_t mosiPin;  ///< (Master Out Slave In) set when using SPI set during
                     ///< construction.
    int8_t clkPin;   ///< (Clock Pin) set when using SPI set during construction.
    int8_t dcPin;    ///< (Data Pin) set when using SPI set during construction.
    int8_t
        csPin; ///< (Chip Select Pin) set when using SPI set during construction.
    int8_t rstPin; ///< Display reset pin assignment. Set during construction.

  #ifdef HAVE_PORTREG
    PortReg *mosiPort, *clkPort, *dcPort, *csPort;
    PortMask mosiPinMask, clkPinMask, dcPinMask, csPinMask;
  #endif
  #if ARDUINO >= 157
    uint32_t wireClk;    ///< Wire speed for SSD1306 transfers
    uint32_t restoreClk; ///< Wire speed following SSD1306 transfers
  #endif
    uint8_t contrast; ///< normal contrast setting for this device
  #if defined(SPI_HAS_TRANSACTION)
  protected:
    // Allow sub-class to change
    SPISettings spiSettings;
  #endif
  };

  #endif // _Adafruit_SSD1306_H_


repo_name: wheelie
function_name: enableAutoRange
last_modified: 2025-01-06 14:52:43
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit Unified Sensor\Adafruit_Sensor.h

content:
  /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software< /span>
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */

  /* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
   * extended sensor support to include color, voltage and current */

  #ifndef _ADAFRUIT_SENSOR_H
  #define _ADAFRUIT_SENSOR_H

  #ifndef ARDUINO
  #include <stdint.h>
  #elif ARDUINO >= 100
  #include "Arduino.h"
  #include "Print.h"
  #else
  #include "WProgram.h"
  #endif

  /* Constants */
  #define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
  #define SENSORS_GRAVITY_MOON (1.6F)      /**< The moon's gravity in m/s^2 */
  #define SENSORS_GRAVITY_SUN (275.0F)     /**< The sun's gravity in m/s^2 */
  #define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH)
  #define SENSORS_MAGFIELD_EARTH_MAX                                             \
    (60.0F) /**< Maximum magnetic field on Earth's surface */
  #define SENSORS_MAGFIELD_EARTH_MIN                                             \
    (30.0F) /**< Minimum magnetic field on Earth's surface */
  #define SENSORS_PRESSURE_SEALEVELHPA                                           \
    (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
  #define SENSORS_DPS_TO_RADS                                                    \
    (0.017453293F) /**< Degrees/s to rad/s multiplier                            \
                    */
  #define SENSORS_RADS_TO_DPS                                                    \
    (57.29577793F) /**< Rad/s to degrees/s  multiplier */
  #define SENSORS_GAUSS_TO_MICROTESLA                                            \
    (100) /**< Gauss to micro-Tesla multiplier */

  /** Sensor types */
  typedef enum {
    SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */
    SENSOR_TYPE_MAGNETIC_FIELD = (2),
    SENSOR_TYPE_ORIENTATION = (3),
    SENSOR_TYPE_GYROSCOPE = (4),
    SENSOR_TYPE_LIGHT = (5),
    SENSOR_TYPE_PRESSURE = (6),
    SENSOR_TYPE_PROXIMITY = (8),
    SENSOR_TYPE_GRAVITY = (9),
    SENSOR_TYPE_LINEAR_ACCELERATION =
        (10), /**< Acceleration not including gravity */
    SENSOR_TYPE_ROTATION_VECTOR = (11),
    SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
    SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
    SENSOR_TYPE_OBJECT_TEMPERATURE = (14),
    SENSOR_TYPE_VOLTAGE = (15),
    SENSOR_TYPE_CURRENT = (16),
    SENSOR_TYPE_COLOR = (17),
    SENSOR_TYPE_TVOC = (18),
    SENSOR_TYPE_VOC_INDEX = (19),
    SENSOR_TYPE_NOX_INDEX = (20),
    SENSOR_TYPE_CO2 = (21),
    SENSOR_TYPE_ECO2 = (22),
    SENSOR_TYPE_PM10_STD = (23),
    SENSOR_TYPE_PM25_STD = (24),
    SENSOR_TYPE_PM100_STD = (25),
    SENSOR_TYPE_PM10_ENV = (26),
    SENSOR_TYPE_PM25_ENV = (27),
    SENSOR_TYPE_PM100_ENV = (28),
    SENSOR_TYPE_GAS_RESISTANCE = (29),
    SENSOR_TYPE_UNITLESS_PERCENT = (30),
    SENSOR_TYPE_ALTITUDE = (31)
  } sensors_type_t;

  /** struct sensors_vec_s is used to return a vector in a common format. */
  typedef struct {
    union {
      float v[3]; ///< 3D vector elements
      struct {
        float x; ///< X component of vector
        float y; ///< Y component of vector
        float z; ///< Z component of vector
      };         ///< Struct for holding XYZ component
      /* Orientation sensors */
      struct {
        float roll; /**< Rotation around the longitudinal axis (the plane body, 'X
                       axis'). Roll is positive and increasing when moving
                       downward. -90 degrees <= roll <= 90 degrees */
        float pitch;   /**< Rotation around the lateral axis (the wing span, 'Y
                          axis'). Pitch is positive and increasing when moving
                          upwards. -180 degrees <= pitch <= 180 degrees) */
        float heading; /**< Angle between the longitudinal axis (the plane body)
                          and magnetic north, measured clockwise when viewing from
                          the top of the device. 0-359 degrees */
      };               ///< Struct for holding roll/pitch/heading
    };                 ///< Union that can hold 3D vector array, XYZ components or
                       ///< roll/pitch/heading
    int8_t status;     ///< Status byte
    uint8_t reserved[3]; ///< Reserved
  } sensors_vec_t;

  /** struct sensors_color_s is used to return color data in a common format. */
  typedef struct {
    union {
      float c[3]; ///< Raw 3-element data
      /* RGB color space */
      struct {
        float r;   /**< Red component */
        float g;   /**< Green component */
        float b;   /**< Blue component */
      };           ///< RGB data in floating point notation
    };             ///< Union of various ways to describe RGB colorspace
    uint32_t rgba; /**< 24-bit RGBA value */
  } sensors_color_t;

  /* Sensor event (36 bytes) */
  /** struct sensor_event_s is used to provide a single sensor event in a common
   * format. */
  typedef struct {
    int32_t version;   /**< must be sizeof(struct sensors_event_t) */
    int32_t sensor_id; /**< unique sensor identifier */
    int32_t type;      /**< sensor type */
    int32_t reserved0; /**< reserved */
    int32_t timestamp; /**< time is in milliseconds */
    union {
      float data[4];              ///< Raw data */
      sensors_vec_t acceleration; /**< acceleration values are in meter per second
                                     per second (m/s^2) */
      sensors_vec_t
          magnetic; /**< magnetic vector values are in micro-Tesla (uT) */
      sensors_vec_t orientation; /**< orientation values are in degrees */
      sensors_vec_t gyro;        /**< gyroscope values are in rad/s */
      float temperature; /**< temperature is in degrees centigrade (Celsius) */
      float distance;    /**< distance in centimeters */
      float light;       /**< light in SI lux units */
      float pressure;    /**< pressure in hectopascal (hPa) */
      float relative_humidity; /**< relative humidity in percent */
      float current;           /**< current in milliamps (mA) */
      float voltage;           /**< voltage in volts (V) */
      float tvoc;              /**< Total Volatile Organic Compounds, in ppb */
      float voc_index; /**< VOC (Volatile Organic Compound) index where 100 is
                            normal (unitless) */
      float nox_index; /**< NOx (Nitrogen Oxides) index where 1 is normal
                            (unitless) */
      float CO2;       /**< Measured CO2 in parts per million (ppm) */
      float eCO2;      /**< equivalent/estimated CO2 in parts per million (ppm
                          estimated from some other measurement) */
      float pm10_std;  /**< Standard Particulate Matter <=1.0 in parts per million
                          (ppm) */
      float pm25_std;  /**< Standard Particulate Matter <=2.5 in parts per million
                          (ppm) */
      float pm100_std; /**< Standard Particulate Matter <=10.0 in parts per
                          million (ppm) */
      float pm10_env;  /**< Environmental Particulate Matter <=1.0 in parts per
                          million (ppm) */
      float pm25_env;  /**< Environmental Particulate Matter <=2.5 in parts per
                          million (ppm) */
      float pm100_env; /**< Environmental Particulate Matter <=10.0 in parts per
                          million (ppm) */
      float gas_resistance;   /**< Proportional to the amount of VOC particles in
                                 the air (Ohms) */
      float unitless_percent; /**<Percentage, unit-less (%) */
      sensors_color_t color;  /**< color in RGB component values */
      float altitude; /**< Distance between a reference datum and a point or
                         object, in meters. */
    };                ///< Union for the wide ranges of data we can carry
  } sensors_event_t;

  /* Sensor details (40 bytes) */
  /** struct sensor_s is used to describe basic information about a specific
   * sensor. */
  typedef struct {
    char name[12];     /**< sensor name */
    int32_t version;   /**< version of the hardware + driver */
    int32_t sensor_id; /**< unique sensor identifier */
    int32_t type;      /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
    float max_value;   /**< maximum value of this sensor's value in SI units */
    float min_value;   /**< minimum value of this sensor's value in SI units */
    float resolution; /**< smallest difference between two values reported by this
                         sensor */
    int32_t min_delay; /**< min delay in microseconds between events. zero = not a
                          constant rate */
  } sensor_t;

  /** @brief Common sensor interface to unify various sensors.
   * Intentionally modeled after sensors.h in the Android API:
   * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h
   */
  class Adafruit_Sensor {
  public:
    // Constructor(s)
    Adafruit_Sensor() {}
    virtual ~Adafruit_Sensor() {}

    // These must be defined by the subclass

    /*! @brief Whether we should automatically change the range (if possible) for
       higher precision
        @param enabled True if we will try to autorange */
    virtual void enableAutoRange(bool enabled) {
      (void)enabled; /* suppress unused warning */
    };

    /*! @brief Get the latest sensor event
        @returns True if able to fetch an event */
    virtual bool getEvent(sensors_event_t *) = 0;
    /*! @brief Get info about the sensor itself */
    virtual void getSensor(sensor_t *) = 0;

    void printSensorDetails(void);
  };

  #endif


file_path: \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\VL53L0X\VL53L0X.h
repo_name: wheelie
function_name: setBus
last_modified: 2022-04-06 03:04:44
content:
  #ifndef VL53L0X_h
  #define VL53L0X_h

  #include <Arduino.h>
  #include <Wire.h>

  class VL53L0X
  {
    public:
      // register addresses from API vl53l0x_device.h (ordered as listed there)
      enum regAddr
      {
        SYSRANGE_START                              = 0x00,

        SYSTEM_THRESH_HIGH                          = 0x0C,
        SYSTEM_THRESH_LOW                           = 0x0E,

        SYSTEM_SEQUENCE_CONFIG                      = 0x01,
        SYSTEM_RANGE_CONFIG                         = 0x09,
        SYSTEM_INTERMEASUREMENT_PERIOD              = 0x04,

        SYSTEM_INTERRUPT_CONFIG_GPIO                = 0x0A,

        GPIO_HV_MUX_ACTIVE_HIGH                     = 0x84,

        SYSTEM_INTERRUPT_CLEAR                      = 0x0B,

        RESULT_INTERRUPT_STATUS                     = 0x13,
        RESULT_RANGE_STATUS                         = 0x14,

        RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN       = 0xBC,
        RESULT_CORE_RANGING_TOTAL_EVENTS_RTN        = 0xC0,
        RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF       = 0xD0,
        RESULT_CORE_RANGING_TOTAL_EVENTS_REF        = 0xD4,
        RESULT_PEAK_SIGNAL_RATE_REF                 = 0xB6,

        ALGO_PART_TO_PART_RANGE_OFFSET_MM           = 0x28,

        I2C_SLAVE_DEVICE_ADDRESS                    = 0x8A,

        MSRC_CONFIG_CONTROL                         = 0x60,

        PRE_RANGE_CONFIG_MIN_SNR                    = 0x27,
        PRE_RANGE_CONFIG_VALID_PHASE_LOW            = 0x56,
        PRE_RANGE_CONFIG_VALID_PHASE_HIGH           = 0x57,
        PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT          = 0x64,

        FINAL_RANGE_CONFIG_MIN_SNR                  = 0x67,
        FINAL_RANGE_CONFIG_VALID_PHASE_LOW          = 0x47,
        FINAL_RANGE_CONFIG_VALID_PHASE_HIGH         = 0x48,
        FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44,

        PRE_RANGE_CONFIG_SIGMA_THRESH_HI            = 0x61,
        PRE_RANGE_CONFIG_SIGMA_THRESH_LO            = 0x62,

        PRE_RANGE_CONFIG_VCSEL_PERIOD               = 0x50,
        PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI          = 0x51,
        PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO          = 0x52,

        SYSTEM_HISTOGRAM_BIN                        = 0x81,
        HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT       = 0x33,
        HISTOGRAM_CONFIG_READOUT_CTRL               = 0x55,

        FINAL_RANGE_CONFIG_VCSEL_PERIOD             = 0x70,
        FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI        = 0x71,
        FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO        = 0x72,
        CROSSTALK_COMPENSATION_PEAK_RATE_MCPS       = 0x20,

        MSRC_CONFIG_TIMEOUT_MACROP                  = 0x46,

        SOFT_RESET_GO2_SOFT_RESET_N                 = 0xBF,
        IDENTIFICATION_MODEL_ID                     = 0xC0,
        IDENTIFICATION_REVISION_ID                  = 0xC2,

        OSC_CALIBRATE_VAL                           = 0xF8,

        GLOBAL_CONFIG_VCSEL_WIDTH                   = 0x32,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_0            = 0xB0,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_1            = 0xB1,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_2            = 0xB2,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_3            = 0xB3,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_4            = 0xB4,
        GLOBAL_CONFIG_SPAD_ENABLES_REF_5            = 0xB5,

        GLOBAL_CONFIG_REF_EN_START_SELECT           = 0xB6,
        DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD         = 0x4E,
        DYNAMIC_SPAD_REF_EN_START_OFFSET            = 0x4F,
        POWER_MANAGEMENT_GO1_POWER_FORCE            = 0x80,

        VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV           = 0x89,

        ALGO_PHASECAL_LIM                           = 0x30,
        ALGO_PHASECAL_CONFIG_TIMEOUT                = 0x30,
      };

      enum vcselPeriodType { VcselPeriodPreRange, VcselPeriodFinalRange };

      uint8_t last_status; // status of last I2C transmission

      VL53L0X();

      void setBus(TwoWire * bus) { this->bus = bus; }
      TwoWire * getBus() { return bus; }

      void setAddress(uint8_t new_addr);
      inline uint8_t getAddress() { return address; }

      bool init(bool io_2v8 = true);

      void writeReg(uint8_t reg, uint8_t value);
      void writeReg16Bit(uint8_t reg, uint16_t value);
      void writeReg32Bit(uint8_t reg, uint32_t value);
      uint8_t readReg(uint8_t reg);
      uint16_t readReg16Bit(uint8_t reg);
      uint32_t readReg32Bit(uint8_t reg);

      void writeMulti(uint8_t reg, uint8_t const * src, uint8_t count);
      void readMulti(uint8_t reg, uint8_t * dst, uint8_t count);

      bool setSignalRateLimit(float limit_Mcps);
      float getSignalRateLimit();

      bool setMeasurementTimingBudget(uint32_t budget_us);
      uint32_t getMeasurementTimingBudget();

      bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks);
      uint8_t getVcselPulsePeriod(vcselPeriodType type);

      void startContinuous(uint32_t period_ms = 0);
      void stopContinuous();
      uint16_t readRangeContinuousMillimeters();
      uint16_t readRangeSingleMillimeters();

      inline void setTimeout(uint16_t timeout) { io_timeout = timeout; }
      inline uint16_t getTimeout() { return io_timeout; }
      bool timeoutOccurred();

    private:
      // TCC: Target CentreCheck
      // MSRC: Minimum Signal Rate Check
      // DSS: Dynamic Spad Selection

      struct SequenceStepEnables
      {
        boolean tcc, msrc, dss, pre_range, final_range;
      };

      struct SequenceStepTimeouts
      {
        uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks;

        uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks;
        uint32_t msrc_dss_tcc_us,    pre_range_us,    final_range_us;
      };

      TwoWire * bus;
      uint8_t address;
      uint16_t io_timeout;
      bool did_timeout;
      uint16_t timeout_start_ms;

      uint8_t stop_variable; // read by init and used when starting measurement; is StopVariable field of VL53L0X_DevData_t structure in API
      uint32_t measurement_timing_budget_us;

      bool getSpadInfo(uint8_t * count, bool * type_is_aperture);

      void getSequenceStepEnables(SequenceStepEnables * enables);
      void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts);

      bool performSingleRefCalibration(uint8_t vhv_init_byte);

      static uint16_t decodeTimeout(uint16_t value);
      static uint16_t encodeTimeout(uint32_t timeout_mclks);
      static uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks);
      static uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks);
  };

  #endif


repo_name: wheelie
function_name: setup
last_modified: 2022-04-06 03:04:44
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\VL53L0X\examples\Continuous\Continuous.ino

content:
  /* This example shows how to use continuous mode to take
  range measurements with the VL53L0X. It is based on
  vl53l0x_ContinuousRanging_Example.c from the VL53L0X API.

  The range readings are in units of mm. */

  #include <Wire.h>
  #include <VL53L0X.h>

  VL53L0X sensor;

  void setup()
  {
    Serial.begin(9600);
    Wire.begin();

    sensor.setTimeout(500);
    if (!sensor.init())
    {
      Serial.println("Failed to detect and initialize sensor!");
      while (1) {}
    }

    // Start continuous back-to-back mode (take readings as
    // fast as possible).  To use continuous timed mode
    // instead, provide a desired inter-measurement period in
    // ms (e.g. sensor.startContinuous(100)).
    sensor.startContinuous();
  }

  void loop()
  {
    Serial.print(sensor.readRangeContinuousMillimeters());
    if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }

    Serial.println();
  }


repo_name: wheelie
function_name: setup
last_modified: 2022-04-06 03:04:44
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\VL53L0X\examples\Single\Single.ino

content:
  /* This example shows how to get single-shot range
   measurements from the VL53L0X. The sensor can optionally be
   configured with different ranging profiles, as described in
   the VL53L0X API user manual, to get better performance for
   a certain application. This code is based on the four
   "SingleRanging" examples in the VL53L0X API.

   The range readings are in units of mm. */

  #include <Wire.h>
  #include <VL53L0X.h>

  VL53L0X sensor;


  // Uncomment this line to use long range mode. This
  // increases the sensitivity of the sensor and extends its
  // potential range, but increases the likelihood of getting
  // an inaccurate reading because of reflections from objects
  // other than the intended target. It works best in dark
  // conditions.

  //#define LONG_RANGE


  // Uncomment ONE of these two lines to get
  // - higher speed at the cost of lower accuracy OR
  // - higher accuracy at the cost of lower speed

  //#define HIGH_SPEED
  //#define HIGH_ACCURACY


  void setup()
  {
    Serial.begin(9600);
    Wire.begin();

    sensor.setTimeout(500);
    if (!sensor.init())
    {
      Serial.println("Failed to detect and initialize sensor!");
      while (1) {}
    }

  #if defined LONG_RANGE
    // lower the return signal rate limit (default is 0.25 MCPS)
    sensor.setSignalRateLimit(0.1);
    // increase laser pulse periods (defaults are 14 and 10 PCLKs)
    sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
    sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);
  #endif

  #if defined HIGH_SPEED
    // reduce timing budget to 20 ms (default is about 33 ms)
    sensor.setMeasurementTimingBudget(20000);
  #elif defined HIGH_ACCURACY
    // increase timing budget to 200 ms
    sensor.setMeasurementTimingBudget(200000);
  #endif
  }

  void loop()
  {
    Serial.print(sensor.readRangeSingleMillimeters());
    if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }

    Serial.println();
  }


repo_name: wheelie
function_name: generateEventMessage
last_modified: 2025-06-09 04:44:46.782799
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncEventSource.cpp

content:
  /*
    Asynchronous WebServer library for Espressif MCUs

    Copyright (c) 2016 Hristo Gochkov. All rights reserved.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
  #include "Arduino.h"
  #include "AsyncEventSource.h"

  static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
    String ev = "";

    if(reconnect){
      ev += "retry: ";
      ev += String(reconnect);
      ev += "\r\n";
    }

    if(id){
      ev += "id: ";
      ev += String(id);
      ev += "\r\n";
    }

    if(event != NULL){
      ev += "event: ";
      ev += String(event);
      ev += "\r\n";
    }

    if(message != NULL){
      size_t messageLen = strlen(message);
      char * lineStart = (char *)message;
      char * lineEnd;
      do {
        char * nextN = strchr(lineStart, '\n');
        char * nextR = strchr(lineStart, '\r');
        if(nextN == NULL && nextR == NULL){
          size_t llen = ((char *)message + messageLen) - lineStart;
          char * ldata = (char *)malloc(llen+1);
          if(ldata != NULL){
            memcpy(ldata, lineStart, llen);
            ldata[llen] = 0;
            ev += "data: ";
            ev += ldata;
            ev += "\r\n\r\n";
            free(ldata);
          }
          lineStart = (char *)message + messageLen;
        } else {
          char * nextLine = NULL;
          if(nextN != NULL && nextR != NULL){
            if(nextR < nextN){
              lineEnd = nextR;
              if(nextN == (nextR + 1))
                nextLine = nextN + 1;
              else
                nextLine = nextR + 1;
            } else {
              lineEnd = nextN;
              if(nextR == (nextN + 1))
                nextLine = nextR + 1;
              else
                nextLine = nextN + 1;
            }
          } else if(nextN != NULL){
            lineEnd = nextN;
            nextLine = nextN + 1;
          } else {
            lineEnd = nextR;
            nextLine = nextR + 1;
          }

          size_t llen = lineEnd - lineStart;
          char * ldata = (char *)malloc(llen+1);
          if(ldata != NULL){
            memcpy(ldata, lineStart, llen);
            ldata[llen] = 0;
            ev += "data: ";
            ev += ldata;
            ev += "\r\n";
            free(ldata);
          }
          lineStart = nextLine;
          if(lineStart == ((char *)message + messageLen))
            ev += "\r\n";
        }
      } while(lineStart < ((char *)message + messageLen));
    }

    return ev;
  }

  // Message

  AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
  : _data(nullptr), _len(len), _sent(0), _acked(0)
  {
    _data = (uint8_t*)malloc(_len+1);
    if(_data == nullptr){
      _len = 0;
    } else {
      memcpy(_data, data, len);
      _data[_len] = 0;
    }
  }

  AsyncEventSourceMessage::~AsyncEventSourceMessage() {
       if(_data != NULL)
          free(_data);
  }

  size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
      (void)time;
      return ack(len);
  }

  size_t AsyncEventSourceMessage::ack(size_t len) {
    // If the whole message is now acked...
    if(_acked + len > _len){
       // Return the number of extra bytes acked (they will be carried on to the next message)
       const size_t extra = _acked + len - _len;
       _acked = _len;
       return extra;
    }
    // Return that no extra bytes left.
    _acked += len;
    return 0;
  }

  size_t AsyncEventSourceMessage::write_buffer(AsyncClient *client) {
    if (!client->canSend() || client->space() <= 0)
      return 0;
    size_t len = _len - _sent;
    if(client->space() < len){
      len = client->space();
    }
    size_t sent = client->add((const char *)_data + _sent, len);
    _sent += sent;
    return sent;
  }

  size_t AsyncEventSourceMessage::send(AsyncClient *client) {
    size_t sent = write_buffer(client);
    client->send();
    return sent;
  }

  // Client

  AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
  : _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete  m; }))
  {
    _client = request->client();
    _server = server;
    _lastId = 0;
    if(request->hasHeader("Last-Event-ID"))
      _lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());

    _client->setRxTimeout(0);
    _client->onError(NULL, NULL);
    _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
    _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
    _client->onData(NULL, NULL);
    _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
    _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);

    _server->_addClient(this);
    delete request;

    _client->setNoDelay(true);
  }

  AsyncEventSourceClient::~AsyncEventSourceClient(){
     _messageQueue.free();
    close();
  }

  void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
    if(!_tryQueueMessage(dataMessage)){
        ets_printf("AsyncEventSourceClient: ERROR: Queue is full, communications too slow, dropping event");
    }
  }

  bool AsyncEventSourceClient::_tryQueueMessage(AsyncEventSourceMessage *dataMessage){
    if(dataMessage == NULL)
      return true;
    if(!connected()){
      delete dataMessage;
      return true;
    }
    bool ret = true;
    if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
        delete dataMessage;
        ret = false;
    } else {
        _messageQueue.add(dataMessage);
    }
    if(_client->canSend())
      _runQueue();
    return ret;
  }

  void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
    _runQueue();
  }

  void AsyncEventSourceClient::_onPoll(){
    if(!_messageQueue.isEmpty()){
      _runQueue();
    }
  }


  void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
    _client->close(true);
  }

  void AsyncEventSourceClient::_onDisconnect(){
    _client = NULL;
    _server->_handleDisconnect(this);
  }

  void AsyncEventSourceClient::close(){
    if(_client != NULL)
      _client->close();
  }

  void AsyncEventSourceClient::write(const char * message, size_t len){
    try_write(message, len);
  }

  bool AsyncEventSourceClient::try_write(const char * message, size_t len){
    return _tryQueueMessage(new AsyncEventSourceMessage(message, len));
  }

  void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
    String ev = generateEventMessage(message, event, id, reconnect);
    _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
  }

  void AsyncEventSourceClient::_runQueue(){
  #if defined(ESP32)
    if(!this->_messageQueue_mutex.try_lock()) {
      return;
    }
  #else
    if(this->_messageQueue_processing){
      return;
    }
    this->_messageQueue_processing = true;
  #endif // ESP32

    size_t total_bytes_written = 0;
    for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
    {
      if(!(*i)->sent()) {
        size_t bytes_written = (*i)->write_buffer(_client);
        total_bytes_written += bytes_written;
        if(bytes_written == 0)
          break;
      }
    }
    if(total_bytes_written > 0)
      _client->send();

    size_t len = total_bytes_written;
    while(len && !_messageQueue.isEmpty()){
      len = _messageQueue.front()->ack(len);
      if(_messageQueue.front()->finished()){
        _messageQueue.remove(_messageQueue.front());
      }
    }

  #if defined(ESP32)
    this->_messageQueue_mutex.unlock();
  #else
    this->_messageQueue_processing = false;
  #endif // ESP32
  }


  // Handler

  AsyncEventSource::AsyncEventSource(const String& url)
    : _url(url)
    , _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
    , _connectcb(NULL)
    , _connectcb2(NULL)
    , _disconnectcb(NULL)
  {}

  AsyncEventSource::~AsyncEventSource(){
    close();
  }

  void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
    _connectcb = cb;
  }

  void AsyncEventSource::onConnect(ArEventHandlerFunction2 cb){
    _connectcb2 = cb;
  }

  void AsyncEventSource::onDisconnect(ArEventHandlerFunction2 cb){
    _disconnectcb = cb;
  }

  void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
    /*char * temp = (char *)malloc(2054);
    if(temp != NULL){
      memset(temp+1,' ',2048);
      temp[0] = ':';
      temp[2049] = '\r';
      temp[2050] = '\n';
      temp[2051] = '\r';
      temp[2052] = '\n';
      temp[2053] = 0;
      client->write((const char *)temp, 2053);
      free(temp);
    }*/

    _clients.add(client);
    if(_connectcb)
      _connectcb(client);
    if(_connectcb2)
      _connectcb2(this, client);
  }

  void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
    _clients.remove(client);
    if(_disconnectcb)
      _disconnectcb(this, client);
  }

  void AsyncEventSource::close(){
    for(const auto &c: _clients){
      if(c->connected())
        c->close();
    }
  }

  // pmb fix
  size_t AsyncEventSource::avgPacketsWaiting() const {
    if(_clients.isEmpty())
      return 0;

    size_t    aql=0;
    uint32_t  nConnectedClients=0;

    for(const auto &c: _clients){
      if(c->connected()) {
        aql+=c->packetsWaiting();
        ++nConnectedClients;
      }
    }
  //  return aql / nConnectedClients;
    return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
  }

  void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
    try_send(message, event, id, reconnect);
  }

  bool AsyncEventSource::try_send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
    String ev = generateEventMessage(message, event, id, reconnect);
    bool succeeded = false;
    for(const auto &c: _clients){
      if(c->connected()) {
        if(c->try_write(ev.c_str(), ev.length()))
          succeeded = true;
      }
    }
    return succeeded;
  }

  size_t AsyncEventSource::count() const {
    return _clients.count_if([](AsyncEventSourceClient *c){
      return c->connected();
    });
  }

  bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
    if(request->method() != HTTP_GET || !request->url().equals(_url)) {
      return false;
    }
    request->addInterestingHeader("Last-Event-ID");
    return true;
  }

  void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
    if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
      return request->requestAuthentication();
    request->send(new AsyncEventSourceResponse(this));
  }

  // Response

  AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
    _server = server;
    _code = 200;
    _contentType = "text/event-stream";
    _sendContentLength = false;
    addHeader("Cache-Control", "no-cache");
    addHeader("Connection","keep-alive");
  }

  void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
    String out = _assembleHead(request->version());
    request->client()->write(out.c_str(), _headLength);
    _state = RESPONSE_WAIT_ACK;
  }

  size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
    if(len){
      new AsyncEventSourceClient(request, _server);
    }
    return 0;
  }


repo_name: wheelie
function_name: finished
last_modified: 2025-06-09 04:44:46.782799
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncEventSource.h

content:
  /*
    Asynchronous WebServer library for Espressif MCUs

    Copyright (c) 2016 Hristo Gochkov. All rights reserved.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
  #ifndef ASYNCEVENTSOURCE_H_
  #define ASYNCEVENTSOURCE_H_

  #include <Arduino.h>
  #include <Arduino.h>
  #if defined(ESP32) || defined(LIBRETINY)
  #include <AsyncTCP.h>
  #else
  #include <ESPAsyncTCP.h>
  #endif

  #if defined(ESP32)
  #include <mutex>
  #endif // ESP32

  #ifndef SSE_MAX_QUEUED_MESSAGES
  #define SSE_MAX_QUEUED_MESSAGES 32
  #endif

  #include <ESPAsyncWebServer.h>

  #include "AsyncWebSynchronization.h"

  #ifdef ESP8266
  #include <Hash.h>
  #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
  #include <../src/Hash.h>
  #endif
  #endif

  #if defined(ESP32) || defined(LIBRETINY)
  #define DEFAULT_MAX_SSE_CLIENTS 8
  #else
  #define DEFAULT_MAX_SSE_CLIENTS 4
  #endif

  class AsyncEventSource;
  class AsyncEventSourceResponse;
  class AsyncEventSourceClient;
  typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
  typedef std::function<void(AsyncEventSource *source, AsyncEventSourceClient *client)> ArEventHandlerFunction2;

  class AsyncEventSourceMessage {
    private:
      uint8_t * _data;
      size_t _len;
      size_t _sent;
      //size_t _ack;
      size_t _acked;
    public:
      AsyncEventSourceMessage(const char * data, size_t len);
      ~AsyncEventSourceMessage();
      size_t ack(size_t len, uint32_t time __attribute__((unused)));
      size_t ack(size_t len);
      size_t write_buffer(AsyncClient *client);
      size_t send(AsyncClient *client);
      bool finished(){ return _acked == _len; }
      bool sent() { return _sent == _len; }
  };

  class AsyncEventSourceClient {
    private:
      AsyncClient *_client;
      AsyncEventSource *_server;
      uint32_t _lastId;
  #if defined(ESP32)
      std::mutex _messageQueue_mutex;
  #else
      bool _messageQueue_processing{false};
  #endif // ESP32
      LinkedList<AsyncEventSourceMessage *> _messageQueue;
      void _queueMessage(AsyncEventSourceMessage *dataMessage);
      bool _tryQueueMessage(AsyncEventSourceMessage *dataMessage);
      void _runQueue();

    public:

      AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
      ~AsyncEventSourceClient();

      AsyncClient* client(){ return _client; }
      void close();
      void write(const char * message, size_t len);
      bool try_write(const char * message, size_t len);
      void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
      bool connected() const { return (_client != NULL) && _client->connected(); }
      uint32_t lastId() const { return _lastId; }
      size_t  packetsWaiting() const { return _messageQueue.length(); }

      //system callbacks (do not call)
      void _onAck(size_t len, uint32_t time);
      void _onPoll();
      void _onTimeout(uint32_t time);
      void _onDisconnect();
  };

  class AsyncEventSource: public AsyncWebHandler {
    private:
      String _url;
      LinkedList<AsyncEventSourceClient *> _clients;
      ArEventHandlerFunction _connectcb;
      ArEventHandlerFunction2 _connectcb2;
      ArEventHandlerFunction2 _disconnectcb;
    public:
      AsyncEventSource(const String& url);
      ~AsyncEventSource();

      const char * url() const { return _url.c_str(); }
      void close();
      void onConnect(ArEventHandlerFunction cb);
      void onConnect(ArEventHandlerFunction2 cb);
      void onDisconnect(ArEventHandlerFunction2 cb);
      void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
      bool try_send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
      size_t count() const; //number clinets connected
      size_t  avgPacketsWaiting() const;

      //system callbacks (do not call)
      void _addClient(AsyncEventSourceClient * client);
      void _handleDisconnect(AsyncEventSourceClient * client);
      virtual bool canHandle(AsyncWebServerRequest *request) override final;
      virtual void handleRequest(AsyncWebServerRequest *request) override final;
  };

  class AsyncEventSourceResponse: public AsyncWebServerResponse {
    private:
      String _content;
      AsyncEventSource *_server;
    public:
      AsyncEventSourceResponse(AsyncEventSource *server);
      void _respond(AsyncWebServerRequest *request);
      size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
      bool _sourceValid() const { return true; }
  };


  #endif /* ASYNCEVENTSOURCE_H_ */


repo_name: wheelie
function_name: _sourceValid
last_modified: 2025-06-09 04:44:46.783799
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncJson.h

content:
  // AsyncJson.h
  /*
    Async Response to use with ArduinoJson and AsyncWebServer
    Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.

    Example of callback in use

     server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {

      AsyncJsonResponse * response = new AsyncJsonResponse();
      JsonObject& root = response->getRoot();
      root["key1"] = "key number one";
      JsonObject& nested = root.createNestedObject("nested");
      nested["key1"] = "key number one";

      response->setLength();
      request->send(response);
    });

    --------------------

    Async Request to use with ArduinoJson and AsyncWebServer
    Written by Arsène von Wyss (avonwyss)

    Example

    AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
    handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
      JsonObject& jsonObj = json.as<JsonObject>();
      // ...
    });
    server.addHandler(handler);
    
  */
  #ifndef ASYNC_JSON_H_
  #define ASYNC_JSON_H_
  #include <ArduinoJson.h>
  #include <ESPAsyncWebServer.h>
  #include <Print.h>

  #if ARDUINOJSON_VERSION_MAJOR == 5
    #define ARDUINOJSON_5_COMPATIBILITY
  #else
    #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
      #define DYNAMIC_JSON_DOCUMENT_SIZE  1024
    #endif
  #endif

  constexpr const char* JSON_MIMETYPE = "application/json";

  /*
   * Json Response
   * */

  class ChunkPrint : public Print {
    private:
      uint8_t* _destination;
      size_t _to_skip;
      size_t _to_write;
      size_t _pos;
    public:
      ChunkPrint(uint8_t* destination, size_t from, size_t len)
        : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
      virtual ~ChunkPrint(){}
      size_t write(uint8_t c){
        if (_to_skip > 0) {
          _to_skip--;
          return 1;
        } else if (_to_write > 0) {
          _to_write--;
          _destination[_pos++] = c;
          return 1;
        }
        return 0;
      }
      size_t write(const uint8_t *buffer, size_t size)
      {
        return this->Print::write(buffer, size);
      }
  };

  class AsyncJsonResponse: public AsyncAbstractResponse {
    protected:

  #ifdef ARDUINOJSON_5_COMPATIBILITY
      DynamicJsonBuffer _jsonBuffer;
  #else
      DynamicJsonDocument _jsonBuffer;
  #endif

      JsonVariant _root;
      bool _isValid;

    public:    

  #ifdef ARDUINOJSON_5_COMPATIBILITY
      AsyncJsonResponse(bool isArray=false): _isValid{false} {
        _code = 200;
        _contentType = JSON_MIMETYPE;
        if(isArray)
          _root = _jsonBuffer.createArray();
        else
          _root = _jsonBuffer.createObject();
      }
  #else
      AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
        _code = 200;
        _contentType = JSON_MIMETYPE;
        if(isArray)
          _root = _jsonBuffer.createNestedArray();
        else
          _root = _jsonBuffer.createNestedObject();
      }
  #endif

      ~AsyncJsonResponse() {}
      JsonVariant & getRoot() { return _root; }
      bool _sourceValid() const { return _isValid; }
      size_t setLength() {

  #ifdef ARDUINOJSON_5_COMPATIBILITY      
        _contentLength = _root.measureLength();
  #else
        _contentLength = measureJson(_root);
  #endif

        if (_contentLength) { _isValid = true; }
        return _contentLength;
      }

     size_t getSize() { return _jsonBuffer.size(); }

      size_t _fillBuffer(uint8_t *data, size_t len){
        ChunkPrint dest(data, _sentLength, len);

  #ifdef ARDUINOJSON_5_COMPATIBILITY      
        _root.printTo( dest ) ;
  #else
        serializeJson(_root, dest);
  #endif
        return len;
      }
  };

  class PrettyAsyncJsonResponse: public AsyncJsonResponse {	
  public:
  #ifdef ARDUINOJSON_5_COMPATIBILITY
  	PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
  #else
  	PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
  #endif
  	size_t setLength () {
  #ifdef ARDUINOJSON_5_COMPATIBILITY
  		_contentLength = _root.measurePrettyLength ();
  #else
  		_contentLength = measureJsonPretty(_root);
  #endif
  		if (_contentLength) {_isValid = true;}
  		return _contentLength;
  	}
  	size_t _fillBuffer (uint8_t *data, size_t len) {
  		ChunkPrint dest (data, _sentLength, len);
  #ifdef ARDUINOJSON_5_COMPATIBILITY
  		_root.prettyPrintTo (dest);
  #else
  		serializeJsonPretty(_root, dest);
  #endif
  		return len;
  	}
  };

  typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;

  class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
  private:
  protected:
    const String _uri;
    WebRequestMethodComposite _method;
    ArJsonRequestHandlerFunction _onRequest;
    size_t _contentLength;
  #ifndef ARDUINOJSON_5_COMPATIBILITY   
    const size_t maxJsonBufferSize;
  #endif
    size_t _maxContentLength;
  public:
  #ifdef ARDUINOJSON_5_COMPATIBILITY      
    AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) 
    : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
  #else
    AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE) 
    : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
  #endif
    
    void setMethod(WebRequestMethodComposite method){ _method = method; }
    void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
    void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }

    virtual bool canHandle(AsyncWebServerRequest *request) override final{
      if(!_onRequest)
        return false;

      if(!(_method & request->method()))
        return false;

      if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
        return false;

      if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) )
        return false;

      request->addInterestingHeader("ANY");
      return true;
    }

    virtual void handleRequest(AsyncWebServerRequest *request) override final {
      if(_onRequest) {
        if (request->_tempObject != NULL) {

  #ifdef ARDUINOJSON_5_COMPATIBILITY    
          DynamicJsonBuffer jsonBuffer;
          JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
          if (json.success()) {
  #else
          DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
          DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
          if(!error) {
            JsonVariant json = jsonBuffer.as<JsonVariant>();
  #endif

            _onRequest(request, json);
            return;
          }
        }
        request->send(_contentLength > _maxContentLength ? 413 : 400);
      } else {
        request->send(500);
      }
    }
    virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
    }
    virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
      if (_onRequest) {
        _contentLength = total;
        if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
          request->_tempObject = malloc(total);
        }
        if (request->_tempObject != NULL) {
          memcpy((uint8_t*)(request->_tempObject) + index, data, len);
        }
      }
    }
    virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
  };
  #endif


repo_name: wheelie
function_name: finished
last_modified: 2025-06-09 04:44:46.783799
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncWebSocket.cpp

content:
  /*
    Asynchronous WebServer library for Espressif MCUs

    Copyright (c) 2016 Hristo Gochkov. All rights reserved.
    This file is part of the esp8266 core for Arduino environment.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
  #include "Arduino.h"
  #include "AsyncWebSocket.h"

  #include <libb64/cencode.h>

  #ifndef ESP8266
  #include "mbedtls/sha1.h"
  #include "mbedtls/version.h"
  #else
  #include <Hash.h>
  #endif

  #define MAX_PRINTF_LEN 64

  size_t webSocketSendFrameWindow(AsyncClient *client){
    if(!client->canSend())
      return 0;
    size_t space = client->space();
    if(space < 9)
      return 0;
    return space - 8;
  }

  size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){
    if(!client->canSend())
      return 0;
    size_t space = client->space();
    if(space < 2)
      return 0;
    uint8_t mbuf[4] = {0,0,0,0};
    uint8_t headLen = 2;
    if(len && mask){
      headLen += 4;
      mbuf[0] = rand() % 0xFF;
      mbuf[1] = rand() % 0xFF;
      mbuf[2] = rand() % 0xFF;
      mbuf[3] = rand() % 0xFF;
    }
    if(len > 125)
      headLen += 2;
    if(space < headLen)
      return 0;
    space -= headLen;

    if(len > space) len = space;

    uint8_t *buf = (uint8_t*)malloc(headLen);
    if(buf == NULL){
      //os_printf("could not malloc %u bytes for frame header\n", headLen);
      return 0;
    }

    buf[0] = opcode & 0x0F;
    if(final)
      buf[0] |= 0x80;
    if(len < 126)
      buf[1] = len & 0x7F;
    else {
      buf[1] = 126;
      buf[2] = (uint8_t)((len >> 8) & 0xFF);
      buf[3] = (uint8_t)(len & 0xFF);
    }
    if(len && mask){
      buf[1] |= 0x80;
      memcpy(buf + (headLen - 4), mbuf, 4);
    }
    if(client->add((const char *)buf, headLen) != headLen){
      //os_printf("error adding %lu header bytes\n", headLen);
      free(buf);
      return 0;
    }
    free(buf);

    if(len){
      if(len && mask){
        size_t i;
        for(i=0;i<len;i++)
          data[i] = data[i] ^ mbuf[i%4];
      }
      if(client->add((const char *)data, len) != len){
        //os_printf("error adding %lu data bytes\n", len);
        return 0;
      }
    }
    if (!client->send()) return 0;
    return len;
  }


  /*
   *    AsyncWebSocketMessageBuffer
   */



  AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer()
    :_data(nullptr)
    ,_len(0)
    ,_lock(false)
    ,_count(0)
  {

  }

  AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size)
    :_data(nullptr)
    ,_len(size)
    ,_lock(false)
    ,_count(0)
  {

    if (!data) {
      return;
    }

    _data = new uint8_t[_len + 1];

    if (_data) {
      memcpy(_data, data, _len);
      _data[_len] = 0;
    }
  }


  AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
    :_data(nullptr)
    ,_len(size)
    ,_lock(false)
    ,_count(0)
  {
    _data = new uint8_t[_len + 1];

    if (_data) {
      _data[_len] = 0;
    }

  }

  AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy)
    :_data(nullptr)
    ,_len(0)
    ,_lock(false)
    ,_count(0)
  {
    _len = copy._len;
    _lock = copy._lock;
    _count = 0;

    if (_len) {
      _data = new uint8_t[_len + 1];
      _data[_len] = 0;
    }

    if (_data) {
      memcpy(_data, copy._data, _len);
      _data[_len] = 0;
    }

  }

  AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy)
    :_data(nullptr)
    ,_len(0)
    ,_lock(false)
    ,_count(0)
  {
    _len = copy._len;
    _lock = copy._lock;
    _count = 0;

    if (copy._data) {
      _data = copy._data;
      copy._data = nullptr;
    }

  }

  AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer()
  {
      if (_data) {
        delete[] _data;
      }
  }

  bool AsyncWebSocketMessageBuffer::reserve(size_t size)
  {
    _len = size;

    if (_data) {
      delete[] _data;
      _data = nullptr;
    }

    _data = new uint8_t[_len + 1];

    if (_data) {
      _data[_len] = 0;
      return true;
    } else {
      return false;
    }

  }



  /*
   * Control Frame
   */

  class AsyncWebSocketControl {
    private:
      uint8_t _opcode;
      uint8_t *_data;
      size_t _len;
      bool _mask;
      bool _finished;
    public:
      AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false)
        :_opcode(opcode)
        ,_len(len)
        ,_mask(len && mask)
        ,_finished(false)
    {
        if(data == NULL)
          _len = 0;
        if(_len){
          if(_len > 125)
            _len = 125;
          _data = (uint8_t*)malloc(_len);
          if(_data == NULL)
            _len = 0;
          else memcpy(_data, data, len);
        } else _data = NULL;
      }
      virtual ~AsyncWebSocketControl(){
        if(_data != NULL)
          free(_data);
      }
      virtual bool finished() const { return _finished; }
      uint8_t opcode(){ return _opcode; }
      uint8_t len(){ return _len + 2; }
      size_t send(AsyncClient *client){
        _finished = true;
        return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
      }
  };

  /*
   * Basic Buffered Message
   */


  AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode, bool mask)
    :_len(len)
    ,_sent(0)
    ,_ack(0)
    ,_acked(0)
  {
    _opcode = opcode & 0x07;
    _mask = mask;
    _data = (uint8_t*)malloc(_len+1);
    if(_data == NULL){
      _len = 0;
      _status = WS_MSG_ERROR;
    } else {
      _status = WS_MSG_SENDING;
      memcpy(_data, data, _len);
      _data[_len] = 0;
    }
  }
  AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask)
    :_len(0)
    ,_sent(0)
    ,_ack(0)
    ,_acked(0)
    ,_data(NULL)
  {
    _opcode = opcode & 0x07;
    _mask = mask;

  }


  AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() {
    if(_data != NULL)
      free(_data);
  }

   void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time)  {
     (void)time;
    _acked += len;
    if(_sent == _len && _acked == _ack){
      _status = WS_MSG_SENT;
    }
  }
   size_t AsyncWebSocketBasicMessage::send(AsyncClient *client)  {
    if(_status != WS_MSG_SENDING)
      return 0;
    if(_acked < _ack){
      return 0;
    }
    if(_sent == _len){
      if(_acked == _ack)
        _status = WS_MSG_SENT;
      return 0;
    }
    if(_sent > _len){
        _status = WS_MSG_ERROR;
        return 0;
    }

    size_t toSend = _len - _sent;
    size_t window = webSocketSendFrameWindow(client);

    if(window < toSend) {
        toSend = window;
    }

    _sent += toSend;
    _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4);

    bool final = (_sent == _len);
    uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend));
    uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION;

    size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
    _status = WS_MSG_SENDING;
    if(toSend && sent != toSend){
        _sent -= (toSend - sent);
        _ack -= (toSend - sent);
    }
    return sent;
  }

  // bool AsyncWebSocketBasicMessage::reserve(size_t size) {
  //   if (size) {
  //     _data = (uint8_t*)malloc(size +1);
  //     if (_data) {
  //       memset(_data, 0, size);
  //       _len = size;
  //       _status = WS_MSG_SENDING;
  //       return true;
  //     }
  //   }
  //   return false;
  //  }


  /*
   * AsyncWebSocketMultiMessage Message
   */


  AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode, bool mask)
    :_len(0)
    ,_sent(0)
    ,_ack(0)
    ,_acked(0)
    ,_WSbuffer(nullptr)
  {

    _opcode = opcode & 0x07;
    _mask = mask;

    if (buffer) {
      _WSbuffer = buffer;
      (*_WSbuffer)++;
      _data = buffer->get();
      _len = buffer->length();
      _status = WS_MSG_SENDING;
      //ets_printf("M: %u\n", _len);
    } else {
      _status = WS_MSG_ERROR;
    }

  }


  AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() {
    if (_WSbuffer) {
      (*_WSbuffer)--; // decreases the counter.
    }
  }

   void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time)  {
     (void)time;
    _acked += len;
    if(_sent >= _len && _acked >= _ack){
      _status = WS_MSG_SENT;
    }
    //ets_printf("A: %u\n", len);
  }
   size_t AsyncWebSocketMultiMessage::send(AsyncClient *client)  {
    if(_status != WS_MSG_SENDING)
      return 0;
    if(_acked < _ack){
      return 0;
    }
    if(_sent == _len){
      _status = WS_MSG_SENT;
      return 0;
    }
    if(_sent > _len){
        _status = WS_MSG_ERROR;
        //ets_printf("E: %u > %u\n", _sent, _len);
        return 0;
    }

    size_t toSend = _len - _sent;
    size_t window = webSocketSendFrameWindow(client);

    if(window < toSend) {
        toSend = window;
    }

    _sent += toSend;
    _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4);

    //ets_printf("W: %u %u\n", _sent - toSend, toSend);

    bool final = (_sent == _len);
    uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend));
    uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION;

    size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
    _status = WS_MSG_SENDING;
    if(toSend && sent != toSend){
        //ets_printf("E: %u != %u\n", toSend, sent);
        _sent -= (toSend - sent);
        _ack -= (toSend - sent);
    }
    //ets_printf("S: %u %u\n", _sent, sent);
    return sent;
  }


  /*
   * Async WebSocket Client
   */
   const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
   const size_t AWSC_PING_PAYLOAD_LEN = 22;

  AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server)
    : _controlQueue(LinkedList<AsyncWebSocketControl *>([](AsyncWebSocketControl *c){ delete  c; }))
    , _messageQueue(LinkedList<AsyncWebSocketMessage *>([](AsyncWebSocketMessage *m){ delete  m; }))
    , _tempObject(NULL)
  {
    _client = request->client();
    _server = server;
    _clientId = _server->_getNextId();
    _status = WS_CONNECTED;
    _pstate = 0;
    _lastMessageTime = millis();
    _keepAlivePeriod = 0;
    _client->setRxTimeout(0);
    _client->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
    _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
    _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
    _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
    _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
    _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
    _server->_addClient(this);
    _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0);
    delete request;
  }

  AsyncWebSocketClient::~AsyncWebSocketClient(){
    _messageQueue.free();
    _controlQueue.free();
    _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
  }

  void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){
    _lastMessageTime = millis();
    if(!_controlQueue.isEmpty()){
      auto head = _controlQueue.front();
      if(head->finished()){
        len -= head->len();
        if(_status == WS_DISCONNECTING && head->opcode() == WS_DISCONNECT){
          _controlQueue.remove(head);
          _status = WS_DISCONNECTED;
          _client->close(true);
          return;
        }
        _controlQueue.remove(head);
      }
    }
    if(len && !_messageQueue.isEmpty()){
      _messageQueue.front()->ack(len, time);
    }
    _server->_cleanBuffers();
    _runQueue();
  }

  void AsyncWebSocketClient::_onPoll(){
    if(_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())){
      _runQueue();
    } else if(_keepAlivePeriod > 0 && _controlQueue.isEmpty() && _messageQueue.isEmpty() && (millis() - _lastMessageTime) >= _keepAlivePeriod){
      ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
    }
  }

  void AsyncWebSocketClient::_runQueue(){
    while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
      _messageQueue.remove(_messageQueue.front());
    }

    if(!_controlQueue.isEmpty() && (_messageQueue.isEmpty() || _messageQueue.front()->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front()->len() - 1)){
      _controlQueue.front()->send(_client);
    } else if(!_messageQueue.isEmpty() && _messageQueue.front()->betweenFrames() && webSocketSendFrameWindow(_client)){
      _messageQueue.front()->send(_client);
    }
  }

  bool AsyncWebSocketClient::queueIsFull(){
    if((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) return true;
    return false;
  }

  void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){
    if(dataMessage == NULL)
      return;
    if(_status != WS_CONNECTED){
      delete dataMessage;
      return;
    }
    if(_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES){
        ets_printf("ERROR: Too many messages queued\n");
        delete dataMessage;
    } else {
        _messageQueue.add(dataMessage);
    }
    if(_client->canSend())
      _runQueue();
  }

  void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){
    if(controlMessage == NULL)
      return;
    _controlQueue.add(controlMessage);
    if(_client->canSend())
      _runQueue();
  }

  void AsyncWebSocketClient::close(uint16_t code, const char * message){
    if(_status != WS_CONNECTED)
      return;
    if(code){
      uint8_t packetLen = 2;
      if(message != NULL){
        size_t mlen = strlen(message);
        if(mlen > 123) mlen = 123;
        packetLen += mlen;
      }
      char * buf = (char*)malloc(packetLen);
      if(buf != NULL){
        buf[0] = (uint8_t)(code >> 8);
        buf[1] = (uint8_t)(code & 0xFF);
        if(message != NULL){
          memcpy(buf+2, message, packetLen -2);
        }
        _queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen));
        free(buf);
        return;
      }
    }
    _queueControl(new AsyncWebSocketControl(WS_DISCONNECT));
  }

  void AsyncWebSocketClient::ping(uint8_t *data, size_t len){
    if(_status == WS_CONNECTED)
      _queueControl(new AsyncWebSocketControl(WS_PING, data, len));
  }

  void AsyncWebSocketClient::_onError(int8_t){}

  void AsyncWebSocketClient::_onTimeout(uint32_t time){
    (void)time;
    _client->close(true);
  }

  void AsyncWebSocketClient::_onDisconnect(){
    _client = NULL;
    _server->_handleDisconnect(this);
  }

  void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
    _lastMessageTime = millis();
    uint8_t *data = (uint8_t*)pbuf;
    while(plen > 0){
      if(!_pstate){
        const uint8_t *fdata = data;
        _pinfo.index = 0;
        _pinfo.final = (fdata[0] & 0x80) != 0;
        _pinfo.opcode = fdata[0] & 0x0F;
        _pinfo.masked = (fdata[1] & 0x80) != 0;
        _pinfo.len = fdata[1] & 0x7F;
        data += 2;
        plen -= 2;
        if(_pinfo.len == 126){
          _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
          data += 2;
          plen -= 2;
        } else if(_pinfo.len == 127){
          _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
          data += 8;
          plen -= 8;
        }

        if(_pinfo.masked){
          memcpy(_pinfo.mask, data, 4);
          data += 4;
          plen -= 4;
        }
      }

      const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
      const auto datalast = data[datalen];

      if(_pinfo.masked){
        for(size_t i=0;i<datalen;i++)
          data[i] ^= _pinfo.mask[(_pinfo.index+i)%4];
      }

      if((datalen + _pinfo.index) < _pinfo.len){
        _pstate = 1;

        if(_pinfo.index == 0){
          if(_pinfo.opcode){
            _pinfo.message_opcode = _pinfo.opcode;
            _pinfo.num = 0;
          } else _pinfo.num += 1;
        }
        _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen);

        _pinfo.index += datalen;
      } else if((datalen + _pinfo.index) == _pinfo.len){
        _pstate = 0;
        if(_pinfo.opcode == WS_DISCONNECT){
          if(datalen){
            uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
            char * reasonString = (char*)(data+2);
            if(reasonCode > 1001){
              _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString));
            }
          }
          if(_status == WS_DISCONNECTING){
            _status = WS_DISCONNECTED;
            _client->close(true);
          } else {
            _status = WS_DISCONNECTING;
            _client->ackLater();
            _queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, datalen));
          }
        } else if(_pinfo.opcode == WS_PING){
          _queueControl(new AsyncWebSocketControl(WS_PONG, data, datalen));
        } else if(_pinfo.opcode == WS_PONG){
          if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
            _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen);
        } else if(_pinfo.opcode < 8){//continuation or text/binary frame
          _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
        }
      } else {
        //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
        //what should we do?
        break;
      }

      // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
      if (datalen > 0)
        data[datalen] = datalast;

      data += datalen;
      plen -= datalen;
    }
  }

  size_t AsyncWebSocketClient::printf(const char *format, ...) {
    va_list arg;
    va_start(arg, format);
    char* temp = new char[MAX_PRINTF_LEN];
    if(!temp){
      va_end(arg);
      return 0;
    }
    char* buffer = temp;
    size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
    va_end(arg);

    if (len > (MAX_PRINTF_LEN - 1)) {
      buffer = new char[len + 1];
      if (!buffer) {
     	  delete[] temp;
        return 0;
      }
      va_start(arg, format);
      vsnprintf(buffer, len + 1, format, arg);
      va_end(arg);
    }
    text(buffer, len);
    if (buffer != temp) {
      delete[] buffer;
    }
    delete[] temp;
    return len;
  }

  #ifndef ESP32
  size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
    va_list arg;
    va_start(arg, formatP);
    char* temp = new char[MAX_PRINTF_LEN];
    if(!temp){
      va_end(arg);
      return 0;
    }
    char* buffer = temp;
    size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
    va_end(arg);

    if (len > (MAX_PRINTF_LEN - 1)) {
      buffer = new char[len + 1];
      if (!buffer) {
     	  delete[] temp;
        return 0;
      }
      va_start(arg, formatP);
      vsnprintf_P(buffer, len + 1, formatP, arg);
      va_end(arg);
    }
    text(buffer, len);
    if (buffer != temp) {
      delete[] buffer;
    }
    delete[] temp;
    return len;
  }
  #endif

  void AsyncWebSocketClient::text(const char * message, size_t len){
    _queueMessage(new AsyncWebSocketBasicMessage(message, len));
  }
  void AsyncWebSocketClient::text(const char * message){
    text(message, strlen(message));
  }
  void AsyncWebSocketClient::text(uint8_t * message, size_t len){
    text((const char *)message, len);
  }
  void AsyncWebSocketClient::text(char * message){
    text(message, strlen(message));
  }
  void AsyncWebSocketClient::text(const String &message){
    text(message.c_str(), message.length());
  }
  void AsyncWebSocketClient::text(const __FlashStringHelper *data){
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = 0;
    while (1) {
      if (pgm_read_byte(p+n) == 0) break;
        n += 1;
    }
    char * message = (char*) malloc(n+1);
    if(message){
      for(size_t b=0; b<n; b++)
        message[b] = pgm_read_byte(p++);
      message[n] = 0;
      text(message, n);
      free(message);
    }
  }
  void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer)
  {
    _queueMessage(new AsyncWebSocketMultiMessage(buffer));
  }

  void AsyncWebSocketClient::binary(const char * message, size_t len){
    _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY));
  }
  void AsyncWebSocketClient::binary(const char * message){
    binary(message, strlen(message));
  }
  void AsyncWebSocketClient::binary(uint8_t * message, size_t len){
    binary((const char *)message, len);
  }
  void AsyncWebSocketClient::binary(char * message){
    binary(message, strlen(message));
  }
  void AsyncWebSocketClient::binary(const String &message){
    binary(message.c_str(), message.length());
  }
  void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len){
    PGM_P p = reinterpret_cast<PGM_P>(data);
    char * message = (char*) malloc(len);
    if(message){
      for(size_t b=0; b<len; b++)
        message[b] = pgm_read_byte(p++);
      binary(message, len);
      free(message);
    }

  }
  void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer)
  {
    _queueMessage(new AsyncWebSocketMultiMessage(buffer, WS_BINARY));
  }

  IPAddress AsyncWebSocketClient::remoteIP() {
      if(!_client) {
          return IPAddress((uint32_t)0);
      }
      return _client->remoteIP();
  }

  uint16_t AsyncWebSocketClient::remotePort() {
      if(!_client) {
          return 0;
      }
      return _client->remotePort();
  }



  /*
   * Async Web Socket - Each separate socket location
   */

  AsyncWebSocket::AsyncWebSocket(const String& url)
    :_url(url)
    ,_clients(LinkedList<AsyncWebSocketClient *>([](AsyncWebSocketClient *c){ delete c; }))
    ,_cNextId(1)
    ,_enabled(true)
    ,_buffers(LinkedList<AsyncWebSocketMessageBuffer *>([](AsyncWebSocketMessageBuffer *b){ delete b; }))
  {
    _eventHandler = NULL;
  }

  AsyncWebSocket::~AsyncWebSocket(){}

  void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
    if(_eventHandler != NULL){
      _eventHandler(this, client, type, arg, data, len);
    }
  }

  void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){
    _clients.add(client);
  }

  void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){

    _clients.remove_first([=](AsyncWebSocketClient * c){
      return c->id() == client->id();
    });
  }

  bool AsyncWebSocket::availableForWriteAll(){
    for(const auto& c: _clients){
      if(c->queueIsFull()) return false;
    }
    return true;
  }

  bool AsyncWebSocket::availableForWrite(uint32_t id){
    for(const auto& c: _clients){
      if(c->queueIsFull() && (c->id() == id )) return false;
    }
    return true;
  }

  size_t AsyncWebSocket::count() const {
    return _clients.count_if([](AsyncWebSocketClient * c){
      return c->status() == WS_CONNECTED;
    });
  }

  AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){
    for(const auto &c: _clients){
      if(c->id() == id && c->status() == WS_CONNECTED){
        return c;
      }
    }
    return nullptr;
  }


  void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){
    AsyncWebSocketClient * c = client(id);
    if(c)
      c->close(code, message);
  }

  void AsyncWebSocket::closeAll(uint16_t code, const char * message){
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c->close(code, message);
    }
  }

  void AsyncWebSocket::cleanupClients(uint16_t maxClients)
  {
    if (count() > maxClients){
      _clients.front()->close();
    }
  }

  void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){
    AsyncWebSocketClient * c = client(id);
    if(c)
      c->ping(data, len);
  }

  void AsyncWebSocket::pingAll(uint8_t *data, size_t len){
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c->ping(data, len);
    }
  }

  void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){
    AsyncWebSocketClient * c = client(id);
    if(c)
      c->text(message, len);
  }

  void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer){
    if (!buffer) return;
    buffer->lock();
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED){
          c->text(buffer);
      }
    }
    buffer->unlock();
    _cleanBuffers();
  }


  void AsyncWebSocket::textAll(const char * message, size_t len){
    AsyncWebSocketMessageBuffer * WSBuffer = makeBuffer((uint8_t *)message, len);
      textAll(WSBuffer);
  }

  void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){
    AsyncWebSocketClient * c = client(id);
    if(c)
      c->binary(message, len);
  }

  void AsyncWebSocket::binaryAll(const char * message, size_t len){
    AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len);
    binaryAll(buffer);
  }

  void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer)
  {
    if (!buffer) return;
    buffer->lock();
      for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c->binary(buffer);
    }
    buffer->unlock();
    _cleanBuffers();
  }

  void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){
    AsyncWebSocketClient * c = client(id);
    if(c)
      c->message(message);
  }

  void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage *message){
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c->message(message);
    }
    _cleanBuffers();
  }

  size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){
    AsyncWebSocketClient * c = client(id);
    if(c){
      va_list arg;
      va_start(arg, format);
      size_t len = c->printf(format, arg);
      va_end(arg);
      return len;
    }
    return 0;
  }

  size_t AsyncWebSocket::printfAll(const char *format, ...) {
    va_list arg;
    char* temp = new char[MAX_PRINTF_LEN];
    if(!temp){
      return 0;
    }
    va_start(arg, format);
    size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
    va_end(arg);
    delete[] temp;

    AsyncWebSocketMessageBuffer * buffer = makeBuffer(len);
    if (!buffer) {
      return 0;
    }

    va_start(arg, format);
    vsnprintf( (char *)buffer->get(), len + 1, format, arg);
    va_end(arg);

    textAll(buffer);
    return len;
  }

  #ifndef ESP32
  size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){
    AsyncWebSocketClient * c = client(id);
    if(c != NULL){
      va_list arg;
      va_start(arg, formatP);
      size_t len = c->printf_P(formatP, arg);
      va_end(arg);
      return len;
    }
    return 0;
  }
  #endif

  size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
    va_list arg;
    char* temp = new char[MAX_PRINTF_LEN];
    if(!temp){
      return 0;
    }
    va_start(arg, formatP);
    size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
    va_end(arg);
    delete[] temp;

    AsyncWebSocketMessageBuffer * buffer = makeBuffer(len + 1);
    if (!buffer) {
      return 0;
    }

    va_start(arg, formatP);
    vsnprintf_P((char *)buffer->get(), len + 1, formatP, arg);
    va_end(arg);

    textAll(buffer);
    return len;
  }

  void AsyncWebSocket::text(uint32_t id, const char * message){
    text(id, message, strlen(message));
  }
  void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){
    text(id, (const char *)message, len);
  }
  void AsyncWebSocket::text(uint32_t id, char * message){
    text(id, message, strlen(message));
  }
  void AsyncWebSocket::text(uint32_t id, const String &message){
    text(id, message.c_str(), message.length());
  }
  void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *message){
    AsyncWebSocketClient * c = client(id);
    if(c != NULL)
      c->text(message);
  }
  void AsyncWebSocket::textAll(const char * message){
    textAll(message, strlen(message));
  }
  void AsyncWebSocket::textAll(uint8_t * message, size_t len){
    textAll((const char *)message, len);
  }
  void AsyncWebSocket::textAll(char * message){
    textAll(message, strlen(message));
  }
  void AsyncWebSocket::textAll(const String &message){
    textAll(message.c_str(), message.length());
  }
  void AsyncWebSocket::textAll(const __FlashStringHelper *message){
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c->text(message);
    }
  }
  void AsyncWebSocket::binary(uint32_t id, const char * message){
    binary(id, message, strlen(message));
  }
  void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){
    binary(id, (const char *)message, len);
  }
  void AsyncWebSocket::binary(uint32_t id, char * message){
    binary(id, message, strlen(message));
  }
  void AsyncWebSocket::binary(uint32_t id, const String &message){
    binary(id, message.c_str(), message.length());
  }
  void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *message, size_t len){
    AsyncWebSocketClient * c = client(id);
    if(c != NULL)
      c-> binary(message, len);
  }
  void AsyncWebSocket::binaryAll(const char * message){
    binaryAll(message, strlen(message));
  }
  void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){
    binaryAll((const char *)message, len);
  }
  void AsyncWebSocket::binaryAll(char * message){
    binaryAll(message, strlen(message));
  }
  void AsyncWebSocket::binaryAll(const String &message){
    binaryAll(message.c_str(), message.length());
  }
  void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){
    for(const auto& c: _clients){
      if(c->status() == WS_CONNECTED)
        c-> binary(message, len);
    }
   }

  const char * WS_STR_CONNECTION = "Connection";
  const char * WS_STR_UPGRADE = "Upgrade";
  const char * WS_STR_ORIGIN = "Origin";
  const char * WS_STR_VERSION = "Sec-WebSocket-Version";
  const char * WS_STR_KEY = "Sec-WebSocket-Key";
  const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol";
  const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept";
  const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

  bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){
    if(!_enabled)
      return false;

    if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS))
      return false;

    request->addInterestingHeader(WS_STR_CONNECTION);
    request->addInterestingHeader(WS_STR_UPGRADE);
    request->addInterestingHeader(WS_STR_ORIGIN);
    request->addInterestingHeader(WS_STR_VERSION);
    request->addInterestingHeader(WS_STR_KEY);
    request->addInterestingHeader(WS_STR_PROTOCOL);
    return true;
  }

  void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){
    if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){
      request->send(400);
      return;
    }
    if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())){
      return request->requestAuthentication();
    }
    AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
    if(version->value().toInt() != 13){
      AsyncWebServerResponse *response = request->beginResponse(400);
      response->addHeader(WS_STR_VERSION,"13");
      request->send(response);
      return;
    }
    AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
    AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
    if(request->hasHeader(WS_STR_PROTOCOL)){
      AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL);
      //ToDo: check protocol
      response->addHeader(WS_STR_PROTOCOL, protocol->value());
    }
    request->send(response);
  }

  AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size)
  {
    AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size);
    if (buffer) {
      AsyncWebLockGuard l(_lock);
      _buffers.add(buffer);
    }
    return buffer;
  }

  AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size)
  {
    AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size);

    if (buffer) {
      AsyncWebLockGuard l(_lock);
      _buffers.add(buffer);
    }

    return buffer;
  }

  void AsyncWebSocket::_cleanBuffers()
  {
    AsyncWebLockGuard l(_lock);

    for(AsyncWebSocketMessageBuffer * c: _buffers){
      if(c && c->canDelete()){
          _buffers.remove(c);
      }
    }
  }

  AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const {
    return _clients;
  }

  /*
   * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
   * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480
   */

  AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server){
    _server = server;
    _code = 101;
    _sendContentLength = false;

    uint8_t * hash = (uint8_t*)malloc(20);
    if(hash == NULL){
      _state = RESPONSE_FAILED;
      return;
    }
    char * buffer = (char *) malloc(33);
    if(buffer == NULL){
      free(hash);
      _state = RESPONSE_FAILED;
      return;
    }
  #ifdef ESP8266
    sha1(key + WS_STR_UUID, hash);
  #else
    (String&)key += WS_STR_UUID;
    mbedtls_sha1_context ctx;
    mbedtls_sha1_init(&ctx);
  #if MBEDTLS_VERSION_NUMBER >= 0x02070000 && MBEDTLS_VERSION_NUMBER < 0x03000000
    mbedtls_sha1_starts_ret(&ctx);
    mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length());
    mbedtls_sha1_finish_ret(&ctx, hash);
  #else
    mbedtls_sha1_starts(&ctx);
    mbedtls_sha1_update(&ctx, (const unsigned char*)key.c_str(), key.length());
    mbedtls_sha1_finish(&ctx, hash);
  #endif
    mbedtls_sha1_free(&ctx);
  #endif
    base64_encodestate _state;
    base64_init_encodestate(&_state);
    int len = base64_encode_block((const char *) hash, 20, buffer, &_state);
    len = base64_encode_blockend((buffer + len), &_state);
    addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
    addHeader(WS_STR_UPGRADE, "websocket");
    addHeader(WS_STR_ACCEPT,buffer);
    free(buffer);
    free(hash);
  }

  void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){
    if(_state == RESPONSE_FAILED){
      request->client()->close(true);
      return;
    }
    String out = _assembleHead(request->version());
    request->client()->write(out.c_str(), _headLength);
    _state = RESPONSE_WAIT_ACK;
  }

  size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
    (void)time;
    if(len){
      new AsyncWebSocketClient(request, _server);
    }
    return 0;
  }


repo_name: wheelie
function_name: reserve
last_modified: 2025-06-09 04:44:46.783799
file_path:
  \Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncWebSocket.h

content:
  /*
    Asynchronous WebServer library for Espressif MCUs

    Copyright (c) 2016 Hristo Gochkov. All rights reserved.
    This file is part of the esp8266 core for Arduino environment.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
  #ifndef ASYNCWEBSOCKET_H_
  #define ASYNCWEBSOCKET_H_

  #include <Arduino.h>
  #if defined(ESP32) || defined(LIBRETINY)
  #include <AsyncTCP.h>
  #ifndef WS_MAX_QUEUED_MESSAGES
  #define WS_MAX_QUEUED_MESSAGES 32
  #endif
  #else
  #include <ESPAsyncTCP.h>
  #ifndef WS_MAX_QUEUED_MESSAGES
  #define WS_MAX_QUEUED_MESSAGES 8
  #endif
  #endif
  #include <ESPAsyncWebServer.h>

  #include "AsyncWebSynchronization.h"

  #ifdef ESP8266
  #include <Hash.h>
  #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
  #include <../src/Hash.h>
  #endif
  #endif

  #if defined(ESP32) || defined(LIBRETINY)
  #define DEFAULT_MAX_WS_CLIENTS 8
  #else
  #define DEFAULT_MAX_WS_CLIENTS 4
  #endif

  class AsyncWebSocket;
  class AsyncWebSocketResponse;
  class AsyncWebSocketClient;
  class AsyncWebSocketControl;

  typedef struct {
      /** Message type as defined by enum AwsFrameType.
       * Note: Applications will only see WS_TEXT and WS_BINARY.
       * All other types are handled by the library. */
      uint8_t message_opcode;
      /** Frame number of a fragmented message. */
      uint32_t num;
      /** Is this the last frame in a fragmented message ?*/
      uint8_t final;
      /** Is this frame masked? */
      uint8_t masked;
      /** Message type as defined by enum AwsFrameType.
       * This value is the same as message_opcode for non-fragmented
       * messages, but may also be WS_CONTINUATION in a fragmented message. */
      uint8_t opcode;
      /** Length of the current frame.
       * This equals the total length of the message if num == 0 && final == true */
      uint64_t len;
      /** Mask key */
      uint8_t mask[4];
      /** Offset of the data inside the current frame. */
      uint64_t index;
  } AwsFrameInfo;

  typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
  typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
  typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
  typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;

  class AsyncWebSocketMessageBuffer {
    private:
      uint8_t * _data;
      size_t _len;
      bool _lock; 
      uint32_t _count;  

    public:
      AsyncWebSocketMessageBuffer();
      AsyncWebSocketMessageBuffer(size_t size);
      AsyncWebSocketMessageBuffer(uint8_t * data, size_t size); 
      AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &); 
      AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); 
      ~AsyncWebSocketMessageBuffer(); 
      void operator ++(int i) { (void)i; _count++; }
      void operator --(int i) { (void)i; if (_count > 0) { _count--; } ;  }
      bool reserve(size_t size);
      void lock() { _lock = true; }
      void unlock() { _lock = false; }
      uint8_t * get() { return _data; }
      size_t length() { return _len; }
      uint32_t count() { return _count; }
      bool canDelete() { return (!_count && !_lock); } 

      friend AsyncWebSocket; 

  };

  class AsyncWebSocketMessage {
    protected:
      uint8_t _opcode;
      bool _mask;
      AwsMessageStatus _status;
    public:
      AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
      virtual ~AsyncWebSocketMessage(){}
      virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
      virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
      virtual bool finished(){ return _status != WS_MSG_SENDING; }
      virtual bool betweenFrames() const { return false; }
  };

  class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
    private:
      size_t _len;
      size_t _sent;
      size_t _ack;
      size_t _acked;
      uint8_t * _data;
  public:
      AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
      AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
      virtual ~AsyncWebSocketBasicMessage() override;
      virtual bool betweenFrames() const override { return _acked == _ack; }
      virtual void ack(size_t len, uint32_t time) override ;
      virtual size_t send(AsyncClient *client) override ;
  };

  class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
    private:
      uint8_t * _data;
      size_t _len;
      size_t _sent;
      size_t _ack;
      size_t _acked;
      AsyncWebSocketMessageBuffer * _WSbuffer; 
  public:
      AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false); 
      virtual ~AsyncWebSocketMultiMessage() override;
      virtual bool betweenFrames() const override { return _acked == _ack; }
      virtual void ack(size_t len, uint32_t time) override ;
      virtual size_t send(AsyncClient *client) override ;
  };

  class AsyncWebSocketClient {
    private:
      AsyncClient *_client;
      AsyncWebSocket *_server;
      uint32_t _clientId;
      AwsClientStatus _status;

      LinkedList<AsyncWebSocketControl *> _controlQueue;
      LinkedList<AsyncWebSocketMessage *> _messageQueue;

      uint8_t _pstate;
      AwsFrameInfo _pinfo;

      uint32_t _lastMessageTime;
      uint32_t _keepAlivePeriod;

      void _queueMessage(AsyncWebSocketMessage *dataMessage);
      void _queueControl(AsyncWebSocketControl *controlMessage);
      void _runQueue();

    public:
      void *_tempObject;

      AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
      ~AsyncWebSocketClient();

      //client id increments for the given server
      uint32_t id(){ return _clientId; }
      AwsClientStatus status(){ return _status; }
      AsyncClient* client(){ return _client; }
      AsyncWebSocket *server(){ return _server; }
      AwsFrameInfo const &pinfo() const { return _pinfo; }

      IPAddress remoteIP();
      uint16_t  remotePort();

      //control frames
      void close(uint16_t code=0, const char * message=NULL);
      void ping(uint8_t *data=NULL, size_t len=0);

      //set auto-ping period in seconds. disabled if zero (default)
      void keepAlivePeriod(uint16_t seconds){
        _keepAlivePeriod = seconds * 1000;
      }
      uint16_t keepAlivePeriod(){
        return (uint16_t)(_keepAlivePeriod / 1000);
      }

      //data packets
      void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
      bool queueIsFull();

      size_t printf(const char *format, ...)  __attribute__ ((format (printf, 2, 3)));
  #ifndef ESP32
      size_t printf_P(PGM_P formatP, ...)  __attribute__ ((format (printf, 2, 3)));
  #endif
      void text(const char * message, size_t len);
      void text(const char * message);
      void text(uint8_t * message, size_t len);
      void text(char * message);
      void text(const String &message);
      void text(const __FlashStringHelper *data);
      void text(AsyncWebSocketMessageBuffer *buffer); 

      void binary(const char * message, size_t len);
      void binary(const char * message);
      void binary(uint8_t * message, size_t len);
      void binary(char * message);
      void binary(const String &message);
      void binary(const __FlashStringHelper *data, size_t len);
      void binary(AsyncWebSocketMessageBuffer *buffer); 

      bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; }

      //system callbacks (do not call)
      void _onAck(size_t len, uint32_t time);
      void _onError(int8_t);
      void _onPoll();
      void _onTimeout(uint32_t time);
      void _onDisconnect();
      void _onData(void *pbuf, size_t plen);
  };

  typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;

  //WebServer Handler implementation that plays the role of a socket server
  class AsyncWebSocket: public AsyncWebHandler {
    public:
      typedef LinkedList<AsyncWebSocketClient *> AsyncWebSocketClientLinkedList;
    private:
      String _url;
      AsyncWebSocketClientLinkedList _clients;
      uint32_t _cNextId;
      AwsEventHandler _eventHandler;
      bool _enabled;
      AsyncWebLock _lock;

    public:
      AsyncWebSocket(const String& url);
      ~AsyncWebSocket();
      const char * url() const { return _url.c_str(); }
      void enable(bool e){ _enabled = e; }
      bool enabled() const { return _enabled; }
      bool availableForWriteAll();
      bool availableForWrite(uint32_t id);

      size_t count() const;
      AsyncWebSocketClient * client(uint32_t id);
      bool hasClient(uint32_t id){ return client(id) != NULL; }

      void close(uint32_t id, uint16_t code=0, const char * message=NULL);
      void closeAll(uint16_t code=0, const char * message=NULL);
      void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);

      void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
      void pingAll(uint8_t *data=NULL, size_t len=0); //  done

      void text(uint32_t id, const char * message, size_t len);
      void text(uint32_t id, const char * message);
      void text(uint32_t id, uint8_t * message, size_t len);
      void text(uint32_t id, char * message);
      void text(uint32_t id, const String &message);
      void text(uint32_t id, const __FlashStringHelper *message);

      void textAll(const char * message, size_t len);
      void textAll(const char * message);
      void textAll(uint8_t * message, size_t len);
      void textAll(char * message);
      void textAll(const String &message);
      void textAll(const __FlashStringHelper *message); //  need to convert
      void textAll(AsyncWebSocketMessageBuffer * buffer); 

      void binary(uint32_t id, const char * message, size_t len);
      void binary(uint32_t id, const char * message);
      void binary(uint32_t id, uint8_t * message, size_t len);
      void binary(uint32_t id, char * message);
      void binary(uint32_t id, const String &message);
      void binary(uint32_t id, const __FlashStringHelper *message, size_t len);

      void binaryAll(const char * message, size_t len);
      void binaryAll(const char * message);
      void binaryAll(uint8_t * message, size_t len);
      void binaryAll(char * message);
      void binaryAll(const String &message);
      void binaryAll(const __FlashStringHelper *message, size_t len);
      void binaryAll(AsyncWebSocketMessageBuffer * buffer); 

      void message(uint32_t id, AsyncWebSocketMessage *message);
      void messageAll(AsyncWebSocketMultiMessage *message);

      size_t printf(uint32_t id, const char *format, ...)  __attribute__ ((format (printf, 3, 4)));
      size_t printfAll(const char *format, ...)  __attribute__ ((format (printf, 2, 3)));
  #ifndef ESP32
      size_t printf_P(uint32_t id, PGM_P formatP, ...)  __attribute__ ((format (printf, 3, 4)));
  #endif
      size_t printfAll_P(PGM_P formatP, ...)  __attribute__ ((format (printf, 2, 3)));

      //event listener
      void onEvent(AwsEventHandler handler){
        _eventHandler = handler;
      }

      //system callbacks (do not call)
      uint32_t _getNextId(){ return _cNextId++; }
      void _addClient(AsyncWebSocketClient * client);
      void _handleDisconnect(AsyncWebSocketClient * client);
      void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
      virtual bool canHandle(AsyncWebServerRequest *request) override final;
      virtual void handleRequest(AsyncWebServerRequest *request) override final;


      //  messagebuffer functions/objects. 
      AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); 
      AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); 
      LinkedList<AsyncWebSocketMessageBuffer *> _buffers;
      void _cleanBuffers(); 

      AsyncWebSocketClientLinkedList getClients() const;
  };

  //WebServer response to authenticate the socket and detach the tcp client from the web server request
  class AsyncWebSocketResponse: public AsyncWebServerResponse {
    private:
      String _content;
      AsyncWebSocket *_server;
    public:
      AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
      void _respond(AsyncWebServerRequest *request);
      size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
      bool _sourceValid() const { return true; }
  };


  #endif /* ASYNCWEBSOCKET_H_ */


