# repo_index

file_path: \Users\gilbe\Documents\GitHub\readme-hub\buddAI\archive\buddai_force.py
repo_name: buddAI
function_name: check_ollama
last_modified: 2025-12-28 12:02:26.782232
content:
  #!/usr/bin/env python3
  """
  BuddAI - Brute Force Edition
  Injects identity directly into every prompt since system prompts aren't working

  Author: James Gilbert
  License: MIT
  """

  import os
  import sys
  import json
  import sqlite3
  from datetime import datetime
  from pathlib import Path
  import http.client

  # Configuration
  OLLAMA_HOST = "localhost"
  OLLAMA_PORT = 11434
  DATA_DIR = Path(__file__).parent / "data"
  DB_PATH = DATA_DIR / "conversations.db"
  OLLAMA_MODEL = "qwen2.5-coder:1.5b"  # Fast model for slow laptop

  # Identity that gets PREPENDED to EVERY message
  IDENTITY = """[You are BuddAI (not Qwen, not any other AI). You help James build GilBots.
  When asked for code, GENERATE THE CODE. Be direct and practical.]

  James: """


  class BuddAI:
      """BuddAI with forced identity injection"""
      
      def __init__(self):
          self.ensure_data_dir()
          self.init_database()
          self.session_id = self.create_session()
          
          # Load context from ALL past sessions (last 10 messages)
          self.context_messages = self.load_all_history(10)
          
          print("⚡ BuddAI - Brute Force Mode")
          print("=" * 50)
          print(f"Session: {self.session_id}")
          print(f"Model: {OLLAMA_MODEL}")
          print(f"Loaded: {len(self.context_messages)} past messages")
          print("=" * 50)
          print("\nCommands: /help, /clear, exit\n")
          
      def ensure_data_dir(self):
          DATA_DIR.mkdir(exist_ok=True)
          
      def init_database(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS sessions (
                  session_id TEXT PRIMARY KEY,
                  started_at TIMESTAMP,
                  ended_at TIMESTAMP,
                  message_count INTEGER DEFAULT 0
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS messages (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  session_id TEXT,
                  role TEXT,
                  content TEXT,
                  timestamp TIMESTAMP,
                  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
              )
          """)
          
          conn.commit()
          conn.close()
          
      def create_session(self):
          session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO sessions (session_id, started_at) VALUES (?, ?)",
              (session_id, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          return session_id
          
      def end_session(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "UPDATE sessions SET ended_at = ?, message_count = ? WHERE session_id = ?",
              (datetime.now().isoformat(), len(self.context_messages), self.session_id)
          )
          conn.commit()
          conn.close()
          
      def save_message(self, role, content):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
              (self.session_id, role, content, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
      
      def load_all_history(self, limit=10):
          """Load recent messages from ALL sessions for persistent memory"""
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              """
              SELECT role, content FROM messages 
              ORDER BY timestamp DESC 
              LIMIT ?
              """,
              (limit,)
          )
          messages = cursor.fetchall()
          conn.close()
          
          # Reverse to get chronological order
          return [{"role": role, "content": content} for role, content in reversed(messages)]
          
      def call_ollama_api(self, user_message):
          """Call Ollama with context summary"""
          try:
              # Build context summary from recent messages
              context_summary = ""
              if len(self.context_messages) > 0:
                  recent = self.context_messages[-5:]
                  context_summary = "\nRecent conversation:\n"
                  for msg in recent:
                      role = "James" if msg["role"] == "user" else "BuddAI"
                      preview = msg["content"][:100]
                      context_summary += f"{role}: {preview}\n"
                  context_summary += "\n"
              
              # Add ALL historical messages
              messages = []
              for msg in self.context_messages[-5:]:
                  messages.append(msg)
              
              # Add current message with identity AND context summary
              forced_prompt = IDENTITY + context_summary + user_message
              messages.append({"role": "user", "content": forced_prompt})
              
              body = {
                  "model": OLLAMA_MODEL,
                  "messages": messages,
                  "stream": False,
                  "options": {
                      "temperature": 0.7,
                      "num_ctx": 2048
                  }
              }
              
              conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=60)
              headers = {"Content-Type": "application/json"}
              json_body = json.dumps(body)
              
              conn.request("POST", "/api/chat", json_body, headers)
              response = conn.getresponse()
              
              if response.status == 200:
                  data = json.loads(response.read().decode('utf-8'))
                  return data.get("message", {}).get("content", "No response")
              else:
                  error_text = response.read().decode('utf-8')
                  return f"API Error: {error_text[:100]}"
                  
          except Exception as e:
              return f"Error: {str(e)}"
          finally:
              if 'conn' in locals():
                  conn.close()
                  
      def chat(self, user_message):
          """Main chat"""
          # Save the CLEAN user message (without identity prefix)
          self.save_message("user", user_message)
          user_msg = {"role": "user", "content": user_message}
          self.context_messages.append(user_msg)
          
          print("\n⚡ Thinking...\n")
          response = self.call_ollama_api(user_message)
          
          self.save_message("assistant", response)
          assistant_msg = {"role": "assistant", "content": response}
          self.context_messages.append(assistant_msg)
          
          return response
          
      def show_help(self):
          print("\n💡 Commands")
          print("=" * 50)
          print("/help   - This message")
          print("/clear  - Clear context")
          print("/stats  - Session stats")
          print("exit    - End session")
          print("=" * 50 + "\n")
          
      def clear_context(self):
          self.context_messages = []
          print("\n🧹 Cleared\n")
          
      def show_stats(self):
          print(f"\n📊 Messages this session: {len(self.context_messages) // 2}\n")
          
      def run(self):
          """Main loop"""
          try:
              while True:
                  user_input = input("James: ").strip()
                  
                  if not user_input:
                      continue
                      
                  if user_input.lower() in ['exit', 'quit']:
                      print("\n👋 Later!")
                      self.end_session()
                      break
                      
                  if user_input.startswith('/'):
                      if user_input == '/help':
                          self.show_help()
                      elif user_input == '/clear':
                          self.clear_context()
                      elif user_input == '/stats':
                          self.show_stats()
                      else:
                          print("\nUnknown command. Type /help\n")
                      continue
                      
                  response = self.chat(user_input)
                  print(f"\nBuddAI: {response}\n")
                  
          except KeyboardInterrupt:
              print("\n\n👋 Bye!")
              self.end_session()
          except Exception as e:
              print(f"\n❌ {e}")
              self.end_session()
              raise


  def check_ollama():
      try:
          conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=5)
          conn.request("GET", "/api/tags")
          response = conn.getresponse()
          conn.close()
          return response.status == 200
      except:
          return False


  def main():
      if not check_ollama():
          print("❌ Ollama not running. Start it: ollama serve")
          sys.exit(1)
          
      buddai = BuddAI()
      buddai.run()


  if __name__ == "__main__":
      main()


file_path: \Users\gilbe\Documents\GitHub\readme-hub\buddAI\archive\buddai_turbo.py
repo_name: buddAI
function_name: check_ollama
last_modified: 2025-12-28 11:42:17.403539
content:
  #!/usr/bin/env python3
  """
  BuddAI Turbo - Optimized for Slow Hardware
  Switchable performance modes + model selection

  Author: James Gilbert (JamesTheGiblet)
  License: MIT
  """

  import os
  import sys
  import json
  import sqlite3
  from datetime import datetime
  from pathlib import Path
  import http.client

  # Configuration
  OLLAMA_HOST = "localhost"
  OLLAMA_PORT = 11434
  DATA_DIR = Path(__file__).parent / "data"
  DB_PATH = DATA_DIR / "conversations.db"

  # Available models (fastest to slowest)
  MODELS = {
      "tiny": "qwen2.5-coder:1.5b",      # 5-10 sec, basic
      "fast": "deepseek-coder:1.3b",     # 10-20 sec, decent
      "balanced": "qwen2.5-coder:3b",    # 20-40 sec, good
      "quality": "deepseek-coder:6.7b"   # 40-90 sec, best
  }

  # System prompts (lean to verbose)
  PROMPTS = {
      "turbo": """I am BuddAI. James's coding partner.

  James: Polymath. 115+ repos. Builds GilBots (ESP32 combat robots). Forge Theory creator.

  My job: Generate code. Remember context. Be direct. No corporate speak.

  Current: GilBot #1 flipper, ESP32-C3, 15kg servo.""",
      
      "balanced": """I am BuddAI, James Gilbert's coding partner.

  James builds:
  - GilBots: ESP32-C3 combat robots (NOW)
  - CoffeeForge, CannaForge, BlockForge
  - 115+ GitHub repos, 8+ years experience

  His style: Modular, commented, practical. Function names like activateFlipper() not flip().

  I generate code matching his style. I remember our conversations. I'm direct and helpful.""",
      
      "detailed": """I am BuddAI, James Gilbert's IP AI Exocortex and coding partner.

  WHO JAMES IS:
  - Polymath creator: robotics, 3D printing, coffee/cannabis science
  - JamesTheGiblet on GitHub: 115+ repositories
  - Created Forge Theory: exponential decay framework
  - Works in 20-hour creative cycles, rapid prototyping
  - Expert debugger who uses AI for code generation

  CURRENT PROJECT:
  GilBot #1: Flipper combat robot, ESP32-C3, 15kg servo, BLE phone control

  HIS CODING STYLE:
  - Modular functions (small, focused)
  - Descriptive names: activateFlipper() not flip()
  - Inline comments explaining WHY
  - Clean, simple, maintainable

  MY ROLE:
  - Generate code in his style
  - Remember all conversations (I have persistent memory)
  - Suggest approaches from his past work
  - Be direct, practical, honest
  - Learn from corrections

  I am his partner. I work WITH him, not FOR him."""
  }

  # Default settings
  DEFAULT_MODEL = "tiny"      # Fast responses on slow laptop
  DEFAULT_PROMPT = "balanced" # Good balance of context and speed


  class BuddAI:
      """Turbo BuddAI with performance modes"""
      
      def __init__(self):
          """Initialize"""
          self.ensure_data_dir()
          self.init_database()
          self.session_id = self.create_session()
          self.context_messages = []
          
          # Performance settings
          self.current_model = DEFAULT_MODEL
          self.current_prompt = DEFAULT_PROMPT
          self.max_context = 5  # Start conservative
          
          self.show_banner()
          
      def show_banner(self):
          """Show startup banner"""
          print("⚡ BuddAI TURBO - Optimized for Speed")
          print("=" * 50)
          print(f"Session: {self.session_id}")
          print(f"Mode: {self.current_model.upper()} + {self.current_prompt.upper()}")
          print(f"Model: {MODELS[self.current_model]}")
          print(f"Context: {self.max_context} messages")
          print("=" * 50)
          print("\nCommands: /help, /mode, /model, exit\n")
          
      def ensure_data_dir(self):
          DATA_DIR.mkdir(exist_ok=True)
          
      def init_database(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS sessions (
                  session_id TEXT PRIMARY KEY,
                  started_at TIMESTAMP,
                  ended_at TIMESTAMP,
                  message_count INTEGER DEFAULT 0,
                  model_used TEXT,
                  prompt_mode TEXT
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS messages (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  session_id TEXT,
                  role TEXT,
                  content TEXT,
                  timestamp TIMESTAMP,
                  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
              )
          """)
          
          conn.commit()
          conn.close()
          
      def create_session(self):
          session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO sessions (session_id, started_at, model_used, prompt_mode) VALUES (?, ?, ?, ?)",
              (session_id, datetime.now().isoformat(), DEFAULT_MODEL, DEFAULT_PROMPT)
          )
          conn.commit()
          conn.close()
          return session_id
          
      def end_session(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "UPDATE sessions SET ended_at = ?, message_count = ? WHERE session_id = ?",
              (datetime.now().isoformat(), len(self.context_messages), self.session_id)
          )
          conn.commit()
          conn.close()
          
      def save_message(self, role, content):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
              (self.session_id, role, content, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          
      def call_ollama_api(self, user_message):
          """Call Ollama with current settings"""
          try:
              messages = [
                  {"role": "system", "content": PROMPTS[self.current_prompt]}
              ]
              
              # Add limited context
              for msg in self.context_messages[-self.max_context:]:
                  messages.append(msg)
              
              messages.append({"role": "user", "content": user_message})
              
              body = {
                  "model": MODELS[self.current_model],
                  "messages": messages,
                  "stream": False,
                  "options": {
                      "num_ctx": 2048,  # Smaller context window = faster
                      "temperature": 0.7
                  }
              }
              
              conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=180)
              headers = {"Content-Type": "application/json"}
              json_body = json.dumps(body)
              
              conn.request("POST", "/api/chat", json_body, headers)
              response = conn.getresponse()
              
              if response.status == 200:
                  data = json.loads(response.read().decode('utf-8'))
                  return data.get("message", {}).get("content", "No response")
              else:
                  error_text = response.read().decode('utf-8')
                  return f"API Error {response.status}: {error_text}"
                  
          except Exception as e:
              return f"Error: {str(e)}"
          finally:
              if 'conn' in locals():
                  conn.close()
                  
      def chat(self, user_message):
          """Main chat"""
          self.save_message("user", user_message)
          user_msg = {"role": "user", "content": user_message}
          self.context_messages.append(user_msg)
          
          print(f"\n⚡ Thinking ({self.current_model})...\n")
          response = self.call_ollama_api(user_message)
          
          self.save_message("assistant", response)
          assistant_msg = {"role": "assistant", "content": response}
          self.context_messages.append(assistant_msg)
          
          return response
          
      def show_help(self):
          print("\n💡 Commands")
          print("=" * 50)
          print("/help      - This message")
          print("/mode      - Change prompt mode (turbo/balanced/detailed)")
          print("/model     - Change model (tiny/fast/balanced/quality)")
          print("/context N - Set context size (1-20)")
          print("/stats     - Session stats")
          print("/clear     - Clear context")
          print("exit       - End session")
          print("=" * 50)
          print(f"\nCurrent: {self.current_model} model + {self.current_prompt} prompt")
          print(f"Context: {self.max_context} messages\n")
          
      def change_mode(self):
          print("\nPrompt Modes:")
          print("1. turbo    - Ultra-concise (fastest)")
          print("2. balanced - Normal detail")
          print("3. detailed - Full context")
          choice = input("\nChoose mode (1-3): ").strip()
          
          modes = {"1": "turbo", "2": "balanced", "3": "detailed"}
          if choice in modes:
              self.current_prompt = modes[choice]
              print(f"✅ Switched to {self.current_prompt} mode\n")
          else:
              print("❌ Invalid choice\n")
              
      def change_model(self):
          print("\nModels:")
          print("1. tiny     - 1.5b (5-10s, basic)")
          print("2. fast     - 1.3b (10-20s, decent)")  
          print("3. balanced - 3b (20-40s, good)")
          print("4. quality  - 6.7b (40-90s, best)")
          choice = input("\nChoose model (1-4): ").strip()
          
          models = {"1": "tiny", "2": "fast", "3": "balanced", "4": "quality"}
          if choice in models:
              self.current_model = models[choice]
              print(f"✅ Switched to {self.current_model} ({MODELS[self.current_model]})\n")
          else:
              print("❌ Invalid choice\n")
              
      def set_context(self, n):
          try:
              n = int(n)
              if 1 <= n <= 20:
                  self.max_context = n
                  print(f"✅ Context set to {n} messages\n")
              else:
                  print("❌ Context must be 1-20\n")
          except:
              print("❌ Invalid number\n")
              
      def show_stats(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          cursor.execute("SELECT COUNT(*) FROM messages WHERE session_id = ?", (self.session_id,))
          session_count = cursor.fetchone()[0]
          
          cursor.execute("SELECT COUNT(*) FROM messages")
          total_count = cursor.fetchone()[0]
          
          conn.close()
          
          print("\n📊 Stats")
          print("=" * 50)
          print(f"This session: {session_count} messages")
          print(f"Total: {total_count} messages")
          print(f"Model: {MODELS[self.current_model]}")
          print(f"Prompt: {self.current_prompt}")
          print(f"Context: {self.max_context}")
          print("=" * 50 + "\n")
          
      def clear_context(self):
          self.context_messages = []
          print("\n🧹 Context cleared\n")
          
      def run(self):
          """Main loop"""
          try:
              while True:
                  user_input = input("James: ").strip()
                  
                  if not user_input:
                      continue
                      
                  if user_input.lower() in ['exit', 'quit']:
                      print("\n👋 Later!")
                      self.end_session()
                      break
                      
                  if user_input.startswith('/'):
                      cmd = user_input.lower().split()
                      if cmd[0] == '/help':
                          self.show_help()
                      elif cmd[0] == '/mode':
                          self.change_mode()
                      elif cmd[0] == '/model':
                          self.change_model()
                      elif cmd[0] == '/context' and len(cmd) > 1:
                          self.set_context(cmd[1])
                      elif cmd[0] == '/stats':
                          self.show_stats()
                      elif cmd[0] == '/clear':
                          self.clear_context()
                      else:
                          print(f"\nUnknown command. Type /help\n")
                      continue
                      
                  response = self.chat(user_input)
                  print(f"\nBuddAI: {response}\n")
                  
          except KeyboardInterrupt:
              print("\n\n👋 Interrupted")
              self.end_session()
          except Exception as e:
              print(f"\n❌ Error: {e}")
              self.end_session()
              raise


  def check_ollama():
      try:
          conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=5)
          conn.request("GET", "/api/tags")
          response = conn.getresponse()
          conn.close()
          return response.status == 200
      except:
          return False


  def main():
      print("⚡ BuddAI Turbo Starting...")
      
      if not check_ollama():
          print("❌ Ollama not running")
          print("\nStart it: ollama serve")
          sys.exit(1)
          
      print("✅ Ollama ready\n")
      
      buddai = BuddAI()
      buddai.run()


  if __name__ == "__main__":
      main()


file_path: C:\Users\gilbe\Documents\GitHub\readme-hub\buddAI\buddai_v3.py
repo_name: readme-hub
function_name: check_ollama
last_modified: 2025-12-28T13:52:10.612661
content:
  #!/usr/bin/env python3
  """
  BuddAI Executive v2.0 - Modular Builder
  Breaks complex tasks into manageable chunks

  Author: James Gilbert
  License: MIT
  """

  import os
  import sys
  import json
  import sqlite3
  from datetime import datetime
  from pathlib import Path
  import http.client
  import re

  # Configuration
  OLLAMA_HOST = "localhost"
  OLLAMA_PORT = 11434
  DATA_DIR = Path(__file__).parent / "data"
  DB_PATH = DATA_DIR / "conversations.db"

  # Models
  MODELS = {
      "fast": "qwen2.5-coder:1.5b",
      "balanced": "qwen2.5-coder:3b"
  }

  # Complexity triggers - if matched, break down the task
  COMPLEX_TRIGGERS = [
      "complete", "entire", "full", "build entire", "build complete",
      "with ble and", "with servo and", "including", "all of"
  ]

  # Module patterns we can detect
  MODULE_PATTERNS = {
      "ble": ["bluetooth", "ble", "wireless"],
      "servo": ["servo", "flipper", "weapon"],
      "motor": ["motor", "drive", "movement", "l298n"],
      "safety": ["safety", "timeout", "failsafe", "emergency"],
      "battery": ["battery", "voltage", "power monitor"],
      "sensor": ["sensor", "distance", "proximity"]
  }



  # --- Shadow Suggestion Engine ---
  class ShadowSuggestionEngine:
      """Proactively suggests modules/settings based on user/project history."""
      def __init__(self, db_path):
          self.db_path = db_path

      def lookup_recent_module_usage(self, module, limit=5):
          """Look up recent usage patterns for a module from repo_index."""
          conn = sqlite3.connect(self.db_path)
          cursor = conn.cursor()
          cursor.execute(
              """
              SELECT file_path, content, last_modified FROM repo_index
              WHERE function_name LIKE ? OR file_path LIKE ?
              ORDER BY last_modified DESC LIMIT ?
              """,
              (f"%{module}%", f"%{module}%", limit)
          )
          results = cursor.fetchall()
          conn.close()
          return results

      def suggest_for_module(self, module):
          """Return a proactive suggestion string for a module if pattern detected."""
          history = self.lookup_recent_module_usage(module)
          if not history:
              return None
          # Example: For 'motor', look for L298N and PWM frequency
          l298n_count = 0
          pwm_freqs = []
          for _, content, _ in history:
              if "L298N" in content or "l298n" in content:
                  l298n_count += 1
              pwm_matches = re.findall(r'PWM_FREQ\s*=\s*(\d+)', content)
              pwm_freqs.extend([int(f) for f in pwm_matches])
              # Also look for explicit frequency in analogWrite or ledcSetup
              freq_matches = re.findall(r'(?:ledcSetup|analogWrite)\s*\([^,]+,\s*[^,]+,\s*(\d+)\)', content)
              pwm_freqs.extend([int(f) for f in freq_matches if f.isdigit()])
          if l298n_count >= 2:
              freq = max(set(pwm_freqs), key=pwm_freqs.count) if pwm_freqs else 500
              return f"I see you usually use the L298N with a {freq}Hz PWM frequency on the ESP32-C3. Should I prep that module?"
          return None

      def get_proactive_suggestion(self, user_input):
          """
          V3.0 Proactive Hook:
          1. Identify "Concept" (e.g., 'flipper')
          2. Query repo_index for James's most frequent companion modules
          3. If 'flipper' often appears with 'safety_timeout', suggest it.
          """
          # 1. Identify Concepts
          input_lower = user_input.lower()
          detected_modules = []
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in input_lower for kw in keywords):
                  detected_modules.append(module)
          
          if not detected_modules:
              return None

          # 2. Query repo_index for correlations
          conn = sqlite3.connect(self.db_path)
          cursor = conn.cursor()
          
          suggestions = []
          for module in detected_modules:
              # Find files containing this module (simple heuristic)
              cursor.execute("SELECT content FROM repo_index WHERE content LIKE ? LIMIT 10", (f"%{module}%",))
              rows = cursor.fetchall()
              if not rows: continue
              
              # Check for companion modules
              companions = {}
              for (content,) in rows:
                  content_lower = content.lower()
                  for other_mod, other_kws in MODULE_PATTERNS.items():
                      if other_mod != module and other_mod not in detected_modules:
                          if any(kw in content_lower for kw in other_kws):
                              companions[other_mod] = companions.get(other_mod, 0) + 1
              
              # 3. Suggest if frequent (>50% correlation in sample)
              for other_mod, count in companions.items():
                  if count >= len(rows) * 0.5:
                      suggestions.append(f"I noticed '{module}' often appears with '{other_mod}' in your repos. Want to include that?")
          
          conn.close()
          return " ".join(list(set(suggestions))) if suggestions else None

      def get_all_suggestions(self, user_input, generated_code):
          """Aggregate all proactive suggestions into a list."""
          suggestions = []
          
          # 1. Companion Modules
          companion = self.get_proactive_suggestion(user_input)
          if companion:
              suggestions.append(companion)
              
          # 2. Module Settings
          input_lower = user_input.lower()
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in input_lower for kw in keywords):
                  s = self.suggest_for_module(module)
                  if s:
                      suggestions.append(s)
          
          # 3. Forge Theory Check
          if ("motor" in input_lower or "servo" in input_lower) and "applyForge" not in generated_code:
              suggestions.append("Apply Forge Theory smoothing to movement?")
              
          # 4. Safety Check (L298N)
          if "L298N" in generated_code and "safety" not in generated_code.lower():
              suggestions.append("Drive system lacks safety timeout (GilBot_V2 uses 5s failsafe). Add that?")
              
          return suggestions


  class BuddAI:
      """Executive with task breakdown"""
      
      def __init__(self):
          self.ensure_data_dir()
          self.init_database()
          self.session_id = self.create_session()
          self.context_messages = []
          self.shadow_engine = ShadowSuggestionEngine(DB_PATH)
          
          print("🧠 BuddAI Executive v2.0 - Modular Builder")
          print("=" * 50)
          print(f"Session: {self.session_id}")
          print(f"FAST (5-10s) | BALANCED (15-30s)")
          print(f"Smart task breakdown for complex requests")
          print("=" * 50)
          print("\nCommands: /fast, /balanced, /help, exit\n")
          
      def ensure_data_dir(self):
          DATA_DIR.mkdir(exist_ok=True)
          
      def init_database(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS sessions (
                  session_id TEXT PRIMARY KEY,
                  started_at TIMESTAMP,
                  ended_at TIMESTAMP
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS messages (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  session_id TEXT,
                  role TEXT,
                  content TEXT,
                  timestamp TIMESTAMP
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS repo_index (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  file_path TEXT,
                  repo_name TEXT,
                  function_name TEXT,
                  content TEXT,
                  last_modified TIMESTAMP
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS style_preferences (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  category TEXT,
                  preference TEXT,
                  confidence FLOAT,
                  extracted_at TIMESTAMP
              )
          """)
          
          conn.commit()
          conn.close()
          
      def create_session(self):
          session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO sessions (session_id, started_at) VALUES (?, ?)",
              (session_id, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          return session_id
          
      def end_session(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "UPDATE sessions SET ended_at = ? WHERE session_id = ?",
              (datetime.now().isoformat(), self.session_id)
          )
          conn.commit()
          conn.close()
          
      def save_message(self, role, content):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
              (self.session_id, role, content, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          
      def index_local_repositories(self, root_path):
          """Crawl directories and index .py, .ino, and .cpp files"""
          import ast
          
          print(f"\n🔍 Indexing repositories in: {root_path}")
          path = Path(root_path)
          
          if not path.exists():
              print(f"❌ Path not found: {root_path}")
              return

          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          count = 0
          
          for file_path in path.rglob('*'):
              if file_path.is_file() and file_path.suffix in ['.py', '.ino', '.cpp', '.h']:
                  try:
                      with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                          content = f.read()
                          
                      functions = []
                      
                      # Python parsing
                      if file_path.suffix == '.py':
                          try:
                              tree = ast.parse(content)
                              for node in ast.walk(tree):
                                  if isinstance(node, ast.FunctionDef):
                                      functions.append(node.name)
                          except:
                              pass
                              
                      # C++/Arduino parsing
                      elif file_path.suffix in ['.ino', '.cpp', '.h']:
                          matches = re.findall(r'\b(?:void|int|bool|float|double|String|char)\s+(\w+)\s*\(', content)
                          functions.extend(matches)
                      
                      # Determine repo name
                      try:
                          repo_name = file_path.relative_to(path).parts[0]
                      except:
                          repo_name = "unknown"
                          
                      timestamp = datetime.fromtimestamp(file_path.stat().st_mtime)
                      
                      for func in functions:
                          cursor.execute("""
                              INSERT INTO repo_index (file_path, repo_name, function_name, content, last_modified)
                              VALUES (?, ?, ?, ?, ?)
                          """, (str(file_path), repo_name, func, content, timestamp.isoformat()))
                          count += 1
                          
                  except Exception:
                      pass
                      
          conn.commit()
          conn.close()
          print(f"✅ Indexed {count} functions across repositories")

      def retrieve_style_context(self, message):
          """Search repo_index for code snippets matching the request"""
          # Extract potential keywords (nouns/modules)
          keywords = re.findall(r'\b\w{4,}\b', message.lower())
          if not keywords:
              return ""

          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          # Build a search query for function names or repo names
          search_terms = " OR ".join([f"function_name LIKE '%{k}%'" for k in keywords])
          search_terms += " OR " + " OR ".join([f"repo_name LIKE '%{k}%'" for k in keywords])
          
          query = f"SELECT repo_name, function_name, content FROM repo_index WHERE {search_terms} LIMIT 2"
          
          cursor.execute(query)
          results = cursor.fetchall()
          conn.close()
          
          if not results:
              return ""
              
          context_block = "\n[REFERENCE STYLE FROM JAMES'S PAST PROJECTS]\n"
          for repo, func, content in results:
              # Just grab the first 500 chars of the file to save context window
              snippet = content[:500] + "..."
              context_block += f"Repo: {repo} | Function: {func}\nCode:\n{snippet}\n---\n"
          
          return context_block

      def scan_style_signature(self):
          """V3.0: Analyze repo_index to extract style preferences."""
          print("\n🕵️  Scanning repositories for style signature...")
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          # Get a sample of code
          cursor.execute("SELECT content FROM repo_index ORDER BY RANDOM() LIMIT 5")
          rows = cursor.fetchall()
          
          if not rows:
              print("❌ No code indexed. Run /index first.")
              conn.close()
              return
              
          code_sample = "\n---\n".join([r[0][:1000] for r in rows])
          
          prompt = f"""Analyze this code sample from James's repositories.
          Extract 3 distinct coding preferences or patterns.
          Format: Category: Preference
          
          Examples:
          - Serial: Uses 115200 baud
          - Safety: Uses non-blocking millis()
          - Pins: Prefers #define over const int
          
          Code Sample:
          {code_sample}
          """
          
          print("⚡ Analyzing with BALANCED model...")
          summary = self.call_model("balanced", prompt)
          
          # Store in DB
          timestamp = datetime.now().isoformat()
          lines = summary.split('\n')
          for line in lines:
              if ':' in line:
                  parts = line.split(':', 1)
                  category = parts[0].strip('- *')
                  pref = parts[1].strip()
                  cursor.execute(
                      "INSERT INTO style_preferences (category, preference, confidence, extracted_at) VALUES (?, ?, ?, ?)",
                      (category, pref, 0.8, timestamp)
                  )
          
          conn.commit()
          conn.close()
          print(f"\n✅ Style Signature Updated:\n{summary}\n")

      def is_simple_question(self, message):
          """Check if this is a simple question that should use FAST model"""
          message_lower = message.lower()
          
          simple_triggers = [
              "what is", "what's", "who is", "who's", "when is",
              "how do i", "can you explain", "tell me about",
              "what are", "where is"
          ]
          
          # Also check if it's just a question without code keywords
          code_keywords = ["generate", "create", "write", "build", "code", "function"]
          
          has_simple_trigger = any(trigger in message_lower for trigger in simple_triggers)
          has_code_keyword = any(keyword in message_lower for keyword in code_keywords)
          
          # Simple if: has simple trigger AND no code keywords
          return has_simple_trigger and not has_code_keyword
      
      def is_complex(self, message):
          """Check if request is too complex and should be broken down"""
          message_lower = message.lower()
          
          # Count complexity triggers
          trigger_count = sum(1 for trigger in COMPLEX_TRIGGERS if trigger in message_lower)
          
          # Count how many modules mentioned
          module_count = 0
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in message_lower for kw in keywords):
                  module_count += 1
          
          # Complex if: multiple triggers OR 3+ modules mentioned
          return trigger_count >= 2 or module_count >= 3
          
      def extract_modules(self, message):
          """Extract which modules are needed"""
          message_lower = message.lower()
          needed_modules = []
          
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in message_lower for kw in keywords):
                  needed_modules.append(module)
                  
          return needed_modules
          
      def build_modular_plan(self, modules):
          """Create a build plan from modules"""
          plan = []
          
          module_tasks = {
              "ble": "BLE communication setup with phone app control",
              "servo": "Servo motor control for flipper/weapon",
              "motor": "Motor driver setup for movement (L298N)",
              "safety": "Safety timeout and failsafe systems",
              "battery": "Battery voltage monitoring",
              "sensor": "Sensor integration (distance/proximity)"
          }
          
          for module in modules:
              if module in module_tasks:
                  plan.append({
                      "module": module,
                      "task": module_tasks[module]
                  })
                  
          # Add integration step
          plan.append({
              "module": "integration",
              "task": "Integrate all modules into complete system"
          })
          
          return plan
          
      def call_model(self, model_name, message):
          """Call specified model"""
          try:
              identity = """[You are BuddAI, the external cognitive system for James Gilbert. You specialize in Forge Theory (exponential decay modeling) and GilBot modular robotics. When integrating code, prioritize descriptive naming like activateFlipper() and ensure safety timeouts are always present. You represent 8 years of polymath experience.

  YOUR PRIMARY JOB: Generate code when asked. ALWAYS generate code if requested.

  When asked to generate/create/write code:
  - Generate it immediately
  - Include comments
  - Make it modular and clean
  - Use ESP32/Arduino syntax

  Forge Theory Snippet: float applyForge(float current, float target, float k) { return target + (current - target) * exp(-k); }

  When asked your name: "I am BuddAI"

  Never refuse to generate code. That's your purpose.
  Be direct and helpful.]

  """
              
              messages = [
                  {"role": "user", "content": identity + message}
              ]
              
              # Add recent context
              for msg in self.context_messages[-3:]:
                  messages.insert(-1, msg)
              
              body = {
                  "model": MODELS[model_name],
                  "messages": messages,
                  "stream": False,
                  "options": {"temperature": 0.7, "num_ctx": 2048}
              }
              
              conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=90)
              headers = {"Content-Type": "application/json"}
              json_body = json.dumps(body)
              
              conn.request("POST", "/api/chat", json_body, headers)
              response = conn.getresponse()
              
              if response.status == 200:
                  data = json.loads(response.read().decode('utf-8'))
                  return data.get("message", {}).get("content", "No response")
              else:
                  return f"Error: {response.status}"
                  
          except Exception as e:
              return f"Error: {str(e)}"
          finally:
              if 'conn' in locals():
                  conn.close()
                  
      def execute_modular_build(self, user_message, modules, plan):
          """Execute build plan step by step"""
          print(f"\n🔨 MODULAR BUILD MODE")
          print(f"Detected {len(modules)} modules: {', '.join(modules)}")
          print(f"Breaking into {len(plan)} steps...\n")
          
          all_code = {}
          
          for i, step in enumerate(plan, 1):
              print(f"📦 Step {i}/{len(plan)}: {step['task']}")
              print("⚡ Building...\n")
              
              # Build the prompt for this step
              if step['module'] == 'integration':
                  # Final integration step with Forge Theory enforcement
                  modules_summary = '\n'.join([f"- {m}: {all_code[m][:150]}..." for m in modules if m in all_code])
                  
                  # Ask James for the 'vibe' of the robot
                  print("\n⚡ FORGE THEORY TUNING:")
                  print("1. Aggressive (k=0.3) - High snap, combat ready")
                  print("2. Balanced (k=0.1) - Standard movement")
                  print("3. Graceful (k=0.03) - Roasting / Smooth curves")
                  choice = input("Select Forge Constant [1-3, default 2]: ")
                  
                  k_val = "0.1"
                  if choice == "1": k_val = "0.3"
                  elif choice == "3": k_val = "0.03"

                  prompt = f"""INTEGRATION TASK: Combine modules into a cohesive GilBot system.
      
      [MODULES]
      {modules_summary}
      
      [FORGE PARAMETERS]
      Set k = {k_val} for all applyForge() calls.
      
      [REQUIREMENTS]
      1. Implement applyForge() math helper.
      2. Use k={k_val} to smooth motor and servo transitions.
      3. Ensure naming matches James's style: activateFlipper(), setMotors().
      """
              else:
                  # Individual module
                  prompt = f"Generate ESP32-C3 code for: {step['task']}. Keep it modular with clear comments."
              
              # Call balanced model for each module
              response = self.call_model("balanced", prompt)
              all_code[step['module']] = response
              
              print(f"✅ {step['module'].upper()} module complete\n")
              print("-" * 50 + "\n")
          
          # Compile final response
          final = "# COMPLETE GILBOT CONTROLLER - MODULAR BUILD\n\n"
          for module, code in all_code.items():
              final += f"## {module.upper()} MODULE\n{code}\n\n"
              
          return final
          
      def apply_style_signature(self, generated_code):
          """Refine generated code to match James's specific naming and safety patterns"""
          # 1. Check for James's common function names (e.g., setupMotors vs init_motors)
          # 2. Ensure Forge Theory helpers are present if motion is detected
          # 3. Append a 'Proactive Note' if a common companion module is missing
          
          return generated_code

      def chat(self, user_message, force_model=None):
          """Main chat with smart routing and shadow suggestions"""
          style_context = self.retrieve_style_context(user_message)
          if style_context:
              self.context_messages.append({"role": "system", "content": style_context})


          self.save_message("user", user_message)
          self.context_messages.append({"role": "user", "content": user_message})

          if force_model:
              model = force_model
              print(f"\n⚡ Using {model.upper()} model (forced)...")
              response = self.call_model(model, user_message)
          elif self.is_complex(user_message):
              modules = self.extract_modules(user_message)
              plan = self.build_modular_plan(modules)
              print("\n" + "=" * 50)
              print("🎯 COMPLEX REQUEST DETECTED!")
              print(f"Modules needed: {', '.join(modules)}")
              print(f"Breaking into {len(plan)} manageable steps")
              print("=" * 50)
              response = self.execute_modular_build(user_message, modules, plan)
          elif self.is_simple_question(user_message):
              print("\n⚡ Using FAST model (simple question)...")
              response = self.call_model("fast", user_message)
          else:
              print("\n⚖️  Using BALANCED model...")
              response = self.call_model("balanced", user_message)

          # Apply Style Guard
          response = self.apply_style_signature(response)
          
          # Generate Suggestion Bar
          suggestions = self.shadow_engine.get_all_suggestions(user_message, response)
          if suggestions:
              bar = "\n\nPROACTIVE: > " + " ".join([f"{i+1}. {s}" for i, s in enumerate(suggestions)])
              response += bar

          self.save_message("assistant", response)
          self.context_messages.append({"role": "assistant", "content": response})

          return response
          
      def run(self):
          """Main loop"""
          try:
              force_model = None
              while True:
                  user_input = input("\nJames: ").strip()
                  if not user_input:
                      continue
                  if user_input.lower() in ['exit', 'quit']:
                      print("\n👋 Later!")
                      self.end_session()
                      break
                  if user_input.startswith('/'):
                      cmd = user_input.lower()
                      if cmd == '/fast':
                          force_model = "fast"
                          print("⚡ Next: FAST model")
                          continue
                      elif cmd == '/balanced':
                          force_model = "balanced"
                          print("⚖️  Next: BALANCED model")
                          continue
                      elif cmd == '/help':
                          print("\n💡 Commands:")
                          print("/fast - Use fast model")
                          print("/balanced - Use balanced model")
                          print("/index <path> - Index local repositories")
                          print("/scan - Scan style signature (V3.0)")
                          print("/help - This message")
                          print("exit - End session\n")
                          continue
                      elif cmd.startswith('/index'):
                          parts = user_input.split(maxsplit=1)
                          if len(parts) > 1:
                              self.index_local_repositories(parts[1])
                          else:
                              print("Usage: /index <path_to_repos>")
                          continue
                      elif cmd == '/scan':
                          self.scan_style_signature()
                          continue
                      else:
                          print("\nUnknown command. Type /help")
                          continue
                  # Chat
                  response = self.chat(user_input, force_model)
                  print(f"\nBuddAI:\n{response}\n")
                  force_model = None
          except KeyboardInterrupt:
              print("\n\n👋 Bye!")
              self.end_session()


  def check_ollama():
      try:
          conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=5)
          conn.request("GET", "/api/tags")
          response = conn.getresponse()
          conn.close()
          return response.status == 200
      except:
          return False


  def main():
      if not check_ollama():
          print("❌ Ollama not running. Start: ollama serve")
          sys.exit(1)
          
      buddai = BuddAI()
      buddai.run()


  if __name__ == "__main__":
      main()


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

content:
[cell repeats an earlier one in this file: 3802 characters]


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

content:
[cell repeats an earlier one in this file: 2268 characters]


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

content:
[cell repeats an earlier one in this file: 1139 characters]


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

content:
[cell repeats an earlier one in this file: 5515 characters]


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

content:
[cell repeats an earlier one in this file: 17696 characters]


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

content:
[cell repeats an earlier one in this file: 3920 characters]


repo_name: readme-hub
function_name: dma_callback
last_modified: 2025-11-11T15:52:59
file_path:
  C:\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: readme-hub
function_name: begin
last_modified: 2025-11-11T15:52:59
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\Adafruit_SPITFT.h

content:
[cell repeats an earlier one in this file: 25059 characters]


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

content:
[cell repeats an earlier one in this file: 10525 characters]


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

content:
[cell repeats an earlier one in this file: 9042 characters]


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

content:
[cell repeats an earlier one in this file: 9721 characters]


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

content:
[cell repeats an earlier one in this file: 862 characters]


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

content:
[cell repeats an earlier one in this file: 1869 characters]


repo_name: readme-hub
function_name: generateEventMessage
last_modified: 2025-06-09T04:44:46.782799
file_path:
  C:\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: readme-hub
function_name: finished
last_modified: 2025-06-09T04:44:46.782799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncEventSource.h

content:
[cell repeats an earlier one in this file: 5099 characters]


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

content:
[cell repeats an earlier one in this file: 7496 characters]


repo_name: readme-hub
function_name: finished
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\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: readme-hub
function_name: reserve
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncWebSocket.h

content:
[cell repeats an earlier one in this file: 12840 characters]


repo_name: readme-hub
function_name: lock
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\AsyncWebSynchronization.h

content:
[cell repeats an earlier one in this file: 1454 characters]


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

content:
[cell repeats an earlier one in this file: 18890 characters]


repo_name: readme-hub
function_name: add
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\StringArray.h

content:
[cell repeats an earlier one in this file: 4983 characters]


repo_name: readme-hub
function_name: checkBasicAuthentication
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebAuthentication.cpp

content:
[cell repeats an earlier one in this file: 7008 characters]


repo_name: readme-hub
function_name: checkBasicAuthentication
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebAuthentication.h

content:
[cell repeats an earlier one in this file: 1543 characters]


repo_name: readme-hub
function_name: _getFile
last_modified: 2025-06-09T04:44:46.783799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebHandlerImpl.h

content:
[cell repeats an earlier one in this file: 5911 characters]


repo_name: readme-hub
function_name: strContains
last_modified: 2025-06-09T04:44:46.784799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebRequest.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 "ESPAsyncWebServer.h"
  #include "WebResponseImpl.h"
  #include "WebAuthentication.h"

  #ifndef ESP8266
  #define os_strlen strlen
  #endif

  static const String SharedEmptyString = String();

  #define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))

  enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL };

  AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
    : _client(c)
    , _server(s)
    , _handler(NULL)
    , _response(NULL)
    , _temp()
    , _parseState(0)
    , _version(0)
    , _method(HTTP_ANY)
    , _url()
    , _host()
    , _contentType()
    , _boundary()
    , _authorization()
    , _reqconntype(RCT_HTTP)
    , _isDigest(false)
    , _isMultipart(false)
    , _isPlainPost(false)
    , _expectingContinue(false)
    , _contentLength(0)
    , _parsedLength(0)
    , _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
    , _params(LinkedList<AsyncWebParameter *>([](AsyncWebParameter *p){ delete p; }))
    , _pathParams(LinkedList<String *>([](String *p){ delete p; }))
    , _multiParseState(0)
    , _boundaryPosition(0)
    , _itemStartIndex(0)
    , _itemSize(0)
    , _itemName()
    , _itemFilename()
    , _itemType()
    , _itemValue()
    , _itemBuffer(0)
    , _itemBufferIndex(0)
    , _itemIsFile(false)
    , _tempObject(NULL)
  {
    c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
    c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
    c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
    c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
    c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
    c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
  }

  AsyncWebServerRequest::~AsyncWebServerRequest(){
    _headers.free();

    _params.free();
    _pathParams.free();

    _interestingHeaders.free();

    if(_response != NULL){
      delete _response;
    }

    if(_tempObject != NULL){
      free(_tempObject);
    }

    if(_tempFile){
      _tempFile.close();
    }
  }

  void AsyncWebServerRequest::_onData(void *buf, size_t len){
    size_t i = 0;
    while (true) {

    if(_parseState < PARSE_REQ_BODY){
      // Find new line in buf
      char *str = (char*)buf;
      for (i = 0; i < len; i++) {
        if (str[i] == '\n') {
          break;
        }
      }
      if (i == len) { // No new line, just add the buffer in _temp
        char ch = str[len-1];
        str[len-1] = 0;
        _temp.reserve(_temp.length()+len);
        _temp.concat(str);
        _temp.concat(ch);
      } else { // Found new line - extract it and parse
        str[i] = 0; // Terminate the string at the end of the line.
        _temp.concat(str);
        _temp.trim();
        _parseLine();
        if (++i < len) {
          // Still have more buffer to process
          buf = str+i;
          len-= i;
          continue;
        }
      }
    } else if(_parseState == PARSE_REQ_BODY){
      // A handler should be already attached at this point in _parseLine function.
      // If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
      const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
      if(_isMultipart){
        if(needParse){
          size_t i;
          for(i=0; i<len; i++){
            _parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1);
            _parsedLength++;
          }
        } else
            _parsedLength += len;
      } else {
        if(_parsedLength == 0){
          if(_contentType.startsWith("application/x-www-form-urlencoded")){
            _isPlainPost = true;
          } else if(_contentType == "text/plain" && __is_param_char(((char*)buf)[0])){
            size_t i = 0;
            while (i<len && __is_param_char(((char*)buf)[i++]));
            if(i < len && ((char*)buf)[i-1] == '='){
              _isPlainPost = true;
            }
          }
        }
        if(!_isPlainPost) {
          //check if authenticated before calling the body
          if(_handler) _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
          _parsedLength += len;
        } else if(needParse) {
          size_t i;
          for(i=0; i<len; i++){
            _parsedLength++;
            _parsePlainPostChar(((uint8_t*)buf)[i]);
          }
        } else {
          _parsedLength += len;
        }
      }
      if(_parsedLength == _contentLength){
        _parseState = PARSE_REQ_END;
        //check if authenticated before calling handleRequest and request auth instead
        if(_handler) _handler->handleRequest(this);
        else send(501);
      }
    }
    break;
    }
  }

  void AsyncWebServerRequest::_removeNotInterestingHeaders(){
    if (_interestingHeaders.containsIgnoreCase("ANY")) return; // nothing to do
    for(const auto& header: _headers){
        if(!_interestingHeaders.containsIgnoreCase(header->name().c_str())){
          _headers.remove(header);
        }
    }
  }

  void AsyncWebServerRequest::_onPoll(){
    //os_printf("p\n");
    if(_response != NULL && _client != NULL && _client->canSend() && !_response->_finished()){
      _response->_ack(this, 0, 0);
    }
  }

  void AsyncWebServerRequest::_onAck(size_t len, uint32_t time){
    //os_printf("a:%u:%u\n", len, time);
    if(_response != NULL){
      if(!_response->_finished()){
        _response->_ack(this, len, time);
      } else {
        AsyncWebServerResponse* r = _response;
        _response = NULL;
        delete r;
      }
    }
  }

  void AsyncWebServerRequest::_onError(int8_t error){
    (void)error;
  }

  void AsyncWebServerRequest::_onTimeout(uint32_t time){
    (void)time;
    //os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
    _client->close();
  }

  void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn){
      _onDisconnectfn=fn;
  }

  void AsyncWebServerRequest::_onDisconnect(){
    //os_printf("d\n");
    if(_onDisconnectfn) {
        _onDisconnectfn();
      }
    _server->_handleDisconnect(this);
  }

  void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){
    _params.add(p);
  }

  void AsyncWebServerRequest::_addPathParam(const char *p){
    _pathParams.add(new String(p));
  }

  void AsyncWebServerRequest::_addGetParams(const String& params){
    size_t start = 0;
    while (start < params.length()){
      int end = params.indexOf('&', start);
      if (end < 0) end = params.length();
      int equal = params.indexOf('=', start);
      if (equal < 0 || equal > end) equal = end;
      String name = params.substring(start, equal);
      String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
      _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value)));
      start = end + 1;
    }
  }

  bool AsyncWebServerRequest::_parseReqHead(){
    // Split the head into method, url and version
    int index = _temp.indexOf(' ');
    String m = _temp.substring(0, index);
    index = _temp.indexOf(' ', index+1);
    String u = _temp.substring(m.length()+1, index);
    _temp = _temp.substring(index+1);

    if(m == "GET"){
      _method = HTTP_GET;
    } else if(m == "POST"){
      _method = HTTP_POST;
    } else if(m == "DELETE"){
      _method = HTTP_DELETE;
    } else if(m == "PUT"){
      _method = HTTP_PUT;
    } else if(m == "PATCH"){
      _method = HTTP_PATCH;
    } else if(m == "HEAD"){
      _method = HTTP_HEAD;
    } else if(m == "OPTIONS"){
      _method = HTTP_OPTIONS;
    }

    String g = String();
    index = u.indexOf('?');
    if(index > 0){
      g = u.substring(index +1);
      u = u.substring(0, index);
    }
    _url = urlDecode(u);
    _addGetParams(g);

    if(!_temp.startsWith("HTTP/1.0"))
      _version = 1;

    _temp = String();
    return true;
  }

  bool strContains(String src, String find, bool mindcase = true) {
    int pos=0, i=0;
    const int slen = src.length();
    const int flen = find.length();

    if (slen < flen) return false;
    while (pos <= (slen - flen)) {
      for (i=0; i < flen; i++) {
        if (mindcase) {
          if (src[pos+i] != find[i]) i = flen + 1; // no match
        } else if (tolower(src[pos+i]) != tolower(find[i])) i = flen + 1; // no match
      }
      if (i == flen) return true;
      pos++;
    }
    return false;
  }

  bool AsyncWebServerRequest::_parseReqHeader(){
    int index = _temp.indexOf(':');
    if(index){
      String name = _temp.substring(0, index);
      String value = _temp.substring(index + 2);
      if(name.equalsIgnoreCase("Host")){
        _host = value;
      } else if(name.equalsIgnoreCase("Content-Type")){
  	  _contentType = value.substring(0, value.indexOf(';'));
        if (value.startsWith("multipart/")){
          _boundary = value.substring(value.indexOf('=')+1);
          _boundary.replace("\"","");
          _isMultipart = true;
        }
      } else if(name.equalsIgnoreCase("Content-Length")){
        _contentLength = atoi(value.c_str());
      } else if(name.equalsIgnoreCase("Expect") && value == "100-continue"){
        _expectingContinue = true;
      } else if(name.equalsIgnoreCase("Authorization")){
        if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase("Basic")){
          _authorization = value.substring(6);
        } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase("Digest")){
          _isDigest = true;
          _authorization = value.substring(7);
        }
      } else {
        if(name.equalsIgnoreCase("Upgrade") && value.equalsIgnoreCase("websocket")){
          // WebSocket request can be uniquely identified by header: [Upgrade: websocket]
          _reqconntype = RCT_WS;
        } else {
          if(name.equalsIgnoreCase("Accept") && strContains(value, "text/event-stream", false)){
            // WebEvent request can be uniquely identified by header:  [Accept: text/event-stream]
            _reqconntype = RCT_EVENT;
          }
        }
      }
      _headers.add(new AsyncWebHeader(name, value));
    }
    _temp = String();
    return true;
  }

  void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){
    if(data && (char)data != '&')
      _temp += (char)data;
    if(!data || (char)data == '&' || _parsedLength == _contentLength){
      String name = "body";
      String value = _temp;
      if(!_temp.startsWith("{") && !_temp.startsWith("[") && _temp.indexOf('=') > 0){
        name = _temp.substring(0, _temp.indexOf('='));
        value = _temp.substring(_temp.indexOf('=') + 1);
      }
      _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true));
      _temp = String();
    }
  }

  void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){
    _itemBuffer[_itemBufferIndex++] = data;

    if(last || _itemBufferIndex == 1460){
      //check if authenticated before calling the upload
      if(_handler)
        _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
      _itemBufferIndex = 0;
    }
  }

  enum {
    EXPECT_BOUNDARY,
    PARSE_HEADERS,
    WAIT_FOR_RETURN1,
    EXPECT_FEED1,
    EXPECT_DASH1,
    EXPECT_DASH2,
    BOUNDARY_OR_DATA,
    DASH3_OR_RETURN2,
    EXPECT_FEED2,
    PARSING_FINISHED,
    PARSE_ERROR
  };

  void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){
  #define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)

    if(!_parsedLength){
      _multiParseState = EXPECT_BOUNDARY;
      _temp = String();
      _itemName = String();
      _itemFilename = String();
      _itemType = String();
    }

    if(_multiParseState == WAIT_FOR_RETURN1){
      if(data != '\r'){
        itemWriteByte(data);
      } else {
        _multiParseState = EXPECT_FEED1;
      }
    } else if(_multiParseState == EXPECT_BOUNDARY){
      if(_parsedLength < 2 && data != '-'){
        _multiParseState = PARSE_ERROR;
        return;
      } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
        _multiParseState = PARSE_ERROR;
        return;
      } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
        _multiParseState = PARSE_ERROR;
        return;
      } else if(_parsedLength - 3 == _boundary.length()){
        if(data != '\n'){
          _multiParseState = PARSE_ERROR;
          return;
        }
        _multiParseState = PARSE_HEADERS;
        _itemIsFile = false;
      }
    } else if(_multiParseState == PARSE_HEADERS){
      if((char)data != '\r' && (char)data != '\n')
         _temp += (char)data;
      if((char)data == '\n'){
        if(_temp.length()){
          if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){
            _itemType = _temp.substring(14);
            _itemIsFile = true;
          } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
            _temp = _temp.substring(_temp.indexOf(';') + 2);
            while(_temp.indexOf(';') > 0){
              String name = _temp.substring(0, _temp.indexOf('='));
              String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
              if(name == "name"){
                _itemName = nameVal;
              } else if(name == "filename"){
                _itemFilename = nameVal;
                _itemIsFile = true;
              }
              _temp = _temp.substring(_temp.indexOf(';') + 2);
            }
            String name = _temp.substring(0, _temp.indexOf('='));
            String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
            if(name == "name"){
              _itemName = nameVal;
            } else if(name == "filename"){
              _itemFilename = nameVal;
              _itemIsFile = true;
            }
          }
          _temp = String();
        } else {
          _multiParseState = WAIT_FOR_RETURN1;
          //value starts from here
          _itemSize = 0;
          _itemStartIndex = _parsedLength;
          _itemValue = String();
          if(_itemIsFile){
            if(_itemBuffer)
              free(_itemBuffer);
            _itemBuffer = (uint8_t*)malloc(1460);
            if(_itemBuffer == NULL){
              _multiParseState = PARSE_ERROR;
              return;
            }
            _itemBufferIndex = 0;
          }
        }
      }
    } else if(_multiParseState == EXPECT_FEED1){
      if(data != '\n'){
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); _parseMultipartPostByte(data, last);
      } else {
        _multiParseState = EXPECT_DASH1;
      }
    } else if(_multiParseState == EXPECT_DASH1){
      if(data != '-'){
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); itemWriteByte('\n');  _parseMultipartPostByte(data, last);
      } else {
        _multiParseState = EXPECT_DASH2;
      }
    } else if(_multiParseState == EXPECT_DASH2){
      if(data != '-'){
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-');  _parseMultipartPostByte(data, last);
      } else {
        _multiParseState = BOUNDARY_OR_DATA;
        _boundaryPosition = 0;
      }
    } else if(_multiParseState == BOUNDARY_OR_DATA){
      if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-');  itemWriteByte('-');
        uint8_t i;
        for(i=0; i<_boundaryPosition; i++)
          itemWriteByte(_boundary.c_str()[i]);
        _parseMultipartPostByte(data, last);
      } else if(_boundaryPosition == _boundary.length() - 1){
        _multiParseState = DASH3_OR_RETURN2;
        if(!_itemIsFile){
          _addParam(new AsyncWebParameter(_itemName, _itemValue, true));
        } else {
          if(_itemSize){
            //check if authenticated before calling the upload
            if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
            _itemBufferIndex = 0;
            _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
          }
          free(_itemBuffer);
          _itemBuffer = NULL;
        }

      } else {
        _boundaryPosition++;
      }
    } else if(_multiParseState == DASH3_OR_RETURN2){
      if(data == '-' && (_contentLength - _parsedLength - 4) != 0){
        //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
        _contentLength = _parsedLength + 4;//lets close the request gracefully
      }
      if(data == '\r'){
        _multiParseState = EXPECT_FEED2;
      } else if(data == '-' && _contentLength == (_parsedLength + 4)){
        _multiParseState = PARSING_FINISHED;
      } else {
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-');  itemWriteByte('-');
        uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
        _parseMultipartPostByte(data, last);
      }
    } else if(_multiParseState == EXPECT_FEED2){
      if(data == '\n'){
        _multiParseState = PARSE_HEADERS;
        _itemIsFile = false;
      } else {
        _multiParseState = WAIT_FOR_RETURN1;
        itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-');  itemWriteByte('-');
        uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
        itemWriteByte('\r'); _parseMultipartPostByte(data, last);
      }
    }
  }

  void AsyncWebServerRequest::_parseLine(){
    if(_parseState == PARSE_REQ_START){
      if(!_temp.length()){
        _parseState = PARSE_REQ_FAIL;
        _client->close();
      } else {
        _parseReqHead();
        _parseState = PARSE_REQ_HEADERS;
      }
      return;
    }

    if(_parseState == PARSE_REQ_HEADERS){
      if(!_temp.length()){
        //end of headers
        _server->_rewriteRequest(this);
        _server->_attachHandler(this);
        _removeNotInterestingHeaders();
        if(_expectingContinue){
          const char * response = "HTTP/1.1 100 Continue\r\n\r\n";
          _client->write(response, os_strlen(response));
        }
        //check handler for authentication
        if(_contentLength){
          _parseState = PARSE_REQ_BODY;
        } else {
          _parseState = PARSE_REQ_END;
          if(_handler) _handler->handleRequest(this);
          else send(501);
        }
      } else _parseReqHeader();
    }
  }

  size_t AsyncWebServerRequest::headers() const{
    return _headers.length();
  }

  bool AsyncWebServerRequest::hasHeader(const String& name) const {
    for(const auto& h: _headers){
      if(h->name().equalsIgnoreCase(name)){
        return true;
      }
    }
    return false;
  }

  bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
    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 * name = (char*) malloc(n+1);
    name[n] = 0; 
    if (name) {
      for(size_t b=0; b<n; b++)
        name[b] = pgm_read_byte(p++);    
      bool result = hasHeader( String(name) ); 
      free(name); 
      return result; 
    } else {
      return false; 
    }
  }

  AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const {
    for(const auto& h: _headers){
      if(h->name().equalsIgnoreCase(name)){
        return h;
      }
    }
    return nullptr;
  }

  AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p); 
    char * name = (char*) malloc(n+1);
    if (name) {
      strcpy_P(name, p); 
      AsyncWebHeader* result = getHeader( String(name)); 
      free(name); 
      return result; 
    } else {
      return nullptr; 
    }
  }

  AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
    auto header = _headers.nth(num);
    return header ? *header : nullptr;
  }

  size_t AsyncWebServerRequest::params() const {
    return _params.length();
  }

  bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
    for(const auto& p: _params){
      if(p->name() == name && p->isPost() == post && p->isFile() == file){
        return true;
      }
    }
    return false;
  }

  bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p);

    char * name = (char*) malloc(n+1);
    name[n] = 0; 
    if (name) {
      strcpy_P(name,p);    
      bool result = hasParam( name, post, file); 
      free(name); 
      return result; 
    } else {
      return false; 
    }
  }

  AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const {
    for(const auto& p: _params){
      if(p->name() == name && p->isPost() == post && p->isFile() == file){
        return p;
      }
    }
    return nullptr;
  }

  AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p);
    char * name = (char*) malloc(n+1);
    if (name) {
      strcpy_P(name, p);   
      AsyncWebParameter* result = getParam(name, post, file); 
      free(name); 
      return result; 
    } else {
      return nullptr; 
    }
  }

  AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
    auto param = _params.nth(num);
    return param ? *param : nullptr;
  }

  void AsyncWebServerRequest::addInterestingHeader(const String& name){
    if(!_interestingHeaders.containsIgnoreCase(name))
      _interestingHeaders.add(name);
  }

  void AsyncWebServerRequest::send(AsyncWebServerResponse *response){
    _response = response;
    if(_response == NULL){
      _client->close(true);
      _onDisconnect();
      return;
    }
    if(!_response->_sourceValid()){
      delete response;
      _response = NULL;
      send(500);
    }
    else {
      _client->setRxTimeout(0);
      _response->_respond(this);
    }
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){
    return new AsyncBasicResponse(code, contentType, content);
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
    if(fs.exists(path) || (!download && fs.exists(path+".gz")))
      return new AsyncFileResponse(fs, path, contentType, download, callback);
    return NULL;
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
    if(content == true)
      return new AsyncFileResponse(content, path, contentType, download, callback);
    return NULL;
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
    return new AsyncStreamResponse(stream, contentType, len, callback);
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
    return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
    if(_version)
      return new AsyncChunkedResponse(contentType, callback, templateCallback);
    return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
  }

  AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
    return new AsyncResponseStream(contentType, bufferSize);
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
    return new AsyncProgmemResponse(code, contentType, content, len, callback);
  }

  AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
    return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
  }

  void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
    send(beginResponse(code, contentType, content));
  }

  void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
    if(fs.exists(path) || (!download && fs.exists(path+".gz"))){
      send(beginResponse(fs, path, contentType, download, callback));
    } else send(404);
  }

  void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
    if(content == true){
      send(beginResponse(content, path, contentType, download, callback));
    } else send(404);
  }

  void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
    send(beginResponse(stream, contentType, len, callback));
  }

  void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
    send(beginResponse(contentType, len, callback, templateCallback));
  }

  void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
    send(beginChunkedResponse(contentType, callback, templateCallback));
  }

  void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
    send(beginResponse_P(code, contentType, content, len, callback));
  }

  void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
    send(beginResponse_P(code, contentType, content, callback));
  }

  void AsyncWebServerRequest::redirect(const String& url){
    AsyncWebServerResponse * response = beginResponse(302);
    response->addHeader("Location",url);
    send(response);
  }

  bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){
    if(_authorization.length()){
      if(_isDigest)
        return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
      else if(!passwordIsHash)
        return checkBasicAuthentication(_authorization.c_str(), username, password);
      else
        return _authorization.equals(password);
    }
    return false;
  }

  bool AsyncWebServerRequest::authenticate(const char * hash){
    if(!_authorization.length() || hash == NULL)
      return false;

    if(_isDigest){
      String hStr = String(hash);
      int separator = hStr.indexOf(":");
      if(separator <= 0)
        return false;
      String username = hStr.substring(0, separator);
      hStr = hStr.substring(separator + 1);
      separator = hStr.indexOf(":");
      if(separator <= 0)
        return false;
      String realm = hStr.substring(0, separator);
      hStr = hStr.substring(separator + 1);
      return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
    }

    return (_authorization.equals(hash));
  }

  void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){
    AsyncWebServerResponse * r = beginResponse(401);
    if(!isDigest && realm == NULL){
      r->addHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
    } else if(!isDigest){
      String header = "Basic realm=\"";
      header.concat(realm);
      header.concat("\"");
      r->addHeader("WWW-Authenticate", header);
    } else {
      String header = "Digest ";
      header.concat(requestDigestAuthentication(realm));
      r->addHeader("WWW-Authenticate", header);
    }
    send(r);
  }

  bool AsyncWebServerRequest::hasArg(const char* name) const {
    for(const auto& arg: _params){
      if(arg->name() == name){
        return true;
      }
    }
    return false;
  }

  bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p); 
    char * name = (char*) malloc(n+1);
    if (name) {
      strcpy_P(name, p);    
      bool result = hasArg( name ); 
      free(name); 
      return result; 
    } else {
      return false; 
    }
  }


  const String& AsyncWebServerRequest::arg(const String& name) const {
    for(const auto& arg: _params){
      if(arg->name() == name){
        return arg->value();
      }
    }
    return SharedEmptyString;
  }

  const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p);
    char * name = (char*) malloc(n+1);
    if (name) {
      strcpy_P(name, p);
      const String & result = arg( String(name) ); 
      free(name); 
      return result; 
    } else {
      return SharedEmptyString;
    }

  }

  const String& AsyncWebServerRequest::arg(size_t i) const {
    return getParam(i)->value();
  }

  const String& AsyncWebServerRequest::argName(size_t i) const {
    return getParam(i)->name();
  }

  const String& AsyncWebServerRequest::pathArg(size_t i) const {
    auto param = _pathParams.nth(i);
    return param ? **param : SharedEmptyString;
  }

  const String& AsyncWebServerRequest::header(const char* name) const {
    AsyncWebHeader* h = getHeader(String(name));
    return h ? h->value() : SharedEmptyString;
  }

  const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const {
    PGM_P p = reinterpret_cast<PGM_P>(data);
    size_t n = strlen_P(p); 
    char * name = (char*) malloc(n+1);
    if (name) {
      strcpy_P(name, p);  
      const String & result = header( (const char *)name ); 
      free(name); 
      return result; 
    } else {
      return SharedEmptyString; 
    }
  };  


  const String& AsyncWebServerRequest::header(size_t i) const {
    AsyncWebHeader* h = getHeader(i);
    return h ?  h->value() : SharedEmptyString;
  }

  const String& AsyncWebServerRequest::headerName(size_t i) const {
    AsyncWebHeader* h = getHeader(i);
    return h ? h->name() : SharedEmptyString;
  }

  String AsyncWebServerRequest::urlDecode(const String& text) const {
    char temp[] = "0x00";
    unsigned int len = text.length();
    unsigned int i = 0;
    String decoded = String();
    decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
    while (i < len){
      char decodedChar;
      char encodedChar = text.charAt(i++);
      if ((encodedChar == '%') && (i + 1 < len)){
        temp[2] = text.charAt(i++);
        temp[3] = text.charAt(i++);
        decodedChar = strtol(temp, NULL, 16);
      } else if (encodedChar == '+') {
        decodedChar = ' ';
      } else {
        decodedChar = encodedChar;  // normal ascii char
      }
      decoded.concat(decodedChar);
    }
    return decoded;
  }


  const char * AsyncWebServerRequest::methodToString() const {
    if(_method == HTTP_ANY) return "ANY";
    else if(_method & HTTP_GET) return "GET";
    else if(_method & HTTP_POST) return "POST";
    else if(_method & HTTP_DELETE) return "DELETE";
    else if(_method & HTTP_PUT) return "PUT";
    else if(_method & HTTP_PATCH) return "PATCH";
    else if(_method & HTTP_HEAD) return "HEAD";
    else if(_method & HTTP_OPTIONS) return "OPTIONS";
    return "UNKNOWN";
  }

  const char *AsyncWebServerRequest::requestedConnTypeToString() const {
    switch (_reqconntype) {
      case RCT_NOT_USED: return "RCT_NOT_USED";
      case RCT_DEFAULT:  return "RCT_DEFAULT";
      case RCT_HTTP:     return "RCT_HTTP";
      case RCT_WS:       return "RCT_WS";
      case RCT_EVENT:    return "RCT_EVENT";
      default:           return "ERROR";
    }
  }

  bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) {
      bool res = false;
      if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true;
      if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true;
      if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true;
      return res;
  }


repo_name: readme-hub
function_name: _respond
last_modified: 2025-06-09T04:44:46.784799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebResponseImpl.h

content:
[cell repeats an earlier one in this file: 5390 characters]


repo_name: readme-hub
function_name: paramValue
last_modified: 2025-06-09T04:44:46.784799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebResponses.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 "ESPAsyncWebServer.h"
  #include "WebResponseImpl.h"
  #include "cbuf.h"

  // Since ESP8266 does not link memchr by default, here's its implementation.
  void* memchr(void* ptr, int ch, size_t count)
  {
    unsigned char* p = static_cast<unsigned char*>(ptr);
    while(count--)
      if(*p++ == static_cast<unsigned char>(ch))
        return --p;
    return nullptr;
  }


  /*
   * Abstract Response
   * */
  const char* AsyncWebServerResponse::_responseCodeToString(int code) {
    switch (code) {
      case 100: return "Continue";
      case 101: return "Switching Protocols";
      case 200: return "OK";
      case 201: return "Created";
      case 202: return "Accepted";
      case 203: return "Non-Authoritative Information";
      case 204: return "No Content";
      case 205: return "Reset Content";
      case 206: return "Partial Content";
      case 300: return "Multiple Choices";
      case 301: return "Moved Permanently";
      case 302: return "Found";
      case 303: return "See Other";
      case 304: return "Not Modified";
      case 305: return "Use Proxy";
      case 307: return "Temporary Redirect";
      case 400: return "Bad Request";
      case 401: return "Unauthorized";
      case 402: return "Payment Required";
      case 403: return "Forbidden";
      case 404: return "Not Found";
      case 405: return "Method Not Allowed";
      case 406: return "Not Acceptable";
      case 407: return "Proxy Authentication Required";
      case 408: return "Request Time-out";
      case 409: return "Conflict";
      case 410: return "Gone";
      case 411: return "Length Required";
      case 412: return "Precondition Failed";
      case 413: return "Request Entity Too Large";
      case 414: return "Request-URI Too Large";
      case 415: return "Unsupported Media Type";
      case 416: return "Requested range not satisfiable";
      case 417: return "Expectation Failed";
      case 500: return "Internal Server Error";
      case 501: return "Not Implemented";
      case 502: return "Bad Gateway";
      case 503: return "Service Unavailable";
      case 504: return "Gateway Time-out";
      case 505: return "HTTP Version not supported";
      default:  return "";
    }
  }

  AsyncWebServerResponse::AsyncWebServerResponse()
    : _code(0)
    , _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
    , _contentType()
    , _contentLength(0)
    , _sendContentLength(true)
    , _chunked(false)
    , _headLength(0)
    , _sentLength(0)
    , _ackedLength(0)
    , _writtenLength(0)
    , _state(RESPONSE_SETUP)
  {
    for(auto header: DefaultHeaders::Instance()) {
      _headers.add(new AsyncWebHeader(header->name(), header->value()));
    }
  }

  AsyncWebServerResponse::~AsyncWebServerResponse(){
    _headers.free();
  }

  void AsyncWebServerResponse::setCode(int code){
    if(_state == RESPONSE_SETUP)
      _code = code;
  }

  void AsyncWebServerResponse::setContentLength(size_t len){
    if(_state == RESPONSE_SETUP)
      _contentLength = len;
  }

  void AsyncWebServerResponse::setContentType(const String& type){
    if(_state == RESPONSE_SETUP)
      _contentType = type;
  }

  void AsyncWebServerResponse::addHeader(const String& name, const String& value){
    _headers.add(new AsyncWebHeader(name, value));
  }

  String AsyncWebServerResponse::_assembleHead(uint8_t version){
    if(version){
      addHeader("Accept-Ranges","none");
      if(_chunked)
        addHeader("Transfer-Encoding","chunked");
    }
    String out = String();
    int bufSize = 300;
    char buf[bufSize];

    snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code));
    out.concat(buf);

    if(_sendContentLength) {
      snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength);
      out.concat(buf);
    }
    if(_contentType.length()) {
      snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str());
      out.concat(buf);
    }

    for(const auto& header: _headers){
      snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str());
      out.concat(buf);
    }
    _headers.free();

    out.concat("\r\n");
    _headLength = out.length();
    return out;
  }

  bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
  bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
  bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
  bool AsyncWebServerResponse::_sourceValid() const { return false; }
  void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); }
  size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; }

  /*
   * String/Code Response
   * */
  AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){
    _code = code;
    _content = content;
    _contentType = contentType;
    if(_content.length()){
      _contentLength = _content.length();
      if(!_contentType.length())
        _contentType = "text/plain";
    }
    addHeader("Connection","close");
  }

  void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
    _state = RESPONSE_HEADERS;
    String out = _assembleHead(request->version());
    size_t outLen = out.length();
    size_t space = request->client()->space();
    if(!_contentLength && space >= outLen){
      _writtenLength += request->client()->write(out.c_str(), outLen);
      _state = RESPONSE_WAIT_ACK;
    } else if(_contentLength && space >= outLen + _contentLength){
      out += _content;
      outLen += _contentLength;
      _writtenLength += request->client()->write(out.c_str(), outLen);
      _state = RESPONSE_WAIT_ACK;
    } else if(space && space < outLen){
      String partial = out.substring(0, space);
      _content = out.substring(space) + _content;
      _contentLength += outLen - space;
      _writtenLength += request->client()->write(partial.c_str(), partial.length());
      _state = RESPONSE_CONTENT;
    } else if(space > outLen && space < (outLen + _contentLength)){
      size_t shift = space - outLen;
      outLen += shift;
      _sentLength += shift;
      out += _content.substring(0, shift);
      _content = _content.substring(shift);
      _writtenLength += request->client()->write(out.c_str(), outLen);
      _state = RESPONSE_CONTENT;
    } else {
      _content = out + _content;
      _contentLength += outLen;
      _state = RESPONSE_CONTENT;
    }
  }

  size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
    (void)time;
    _ackedLength += len;
    if(_state == RESPONSE_CONTENT){
      size_t available = _contentLength - _sentLength;
      size_t space = request->client()->space();
      //we can fit in this packet
      if(space > available){
        _writtenLength += request->client()->write(_content.c_str(), available);
        _content = String();
        _state = RESPONSE_WAIT_ACK;
        return available;
      }
      //send some data, the rest on ack
      String out = _content.substring(0, space);
      _content = _content.substring(space);
      _sentLength += space;
      _writtenLength += request->client()->write(out.c_str(), space);
      return space;
    } else if(_state == RESPONSE_WAIT_ACK){
      if(_ackedLength >= _writtenLength){
        _state = RESPONSE_END;
      }
    }
    return 0;
  }


  /*
   * Abstract Response
   * */

  AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
  {
    // In case of template processing, we're unable to determine real response size
    if(callback) {
      _contentLength = 0;
      _sendContentLength = false;
      _chunked = true;
    }
  }

  void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
    addHeader("Connection","close");
    _head = _assembleHead(request->version());
    _state = RESPONSE_HEADERS;
    _ack(request, 0, 0);
  }

  size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
    (void)time;
    if(!_sourceValid()){
      _state = RESPONSE_FAILED;
      request->client()->close();
      return 0;
    }
    _ackedLength += len;
    size_t space = request->client()->space();

    size_t headLen = _head.length();
    if(_state == RESPONSE_HEADERS){
      if(space >= headLen){
        _state = RESPONSE_CONTENT;
        space -= headLen;
      } else {
        String out = _head.substring(0, space);
        _head = _head.substring(space);
        _writtenLength += request->client()->write(out.c_str(), out.length());
        return out.length();
      }
    }

    if(_state == RESPONSE_CONTENT){
      size_t outLen;
      if(_chunked){
        if(space <= 8){
          return 0;
        }
        outLen = space;
      } else if(!_sendContentLength){
        outLen = space;
      } else {
        outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
      }

      uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
      if (!buf) {
        // os_printf("_ack malloc %d failed\n", outLen+headLen);
        return 0;
      }

      if(headLen){
        memcpy(buf, _head.c_str(), _head.length());
      }

      size_t readLen = 0;

      if(_chunked){
        // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
        // See RFC2616 sections 2, 3.6.1.
        readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8);
        if(readLen == RESPONSE_TRY_AGAIN){
            free(buf);
            return 0;
        }
        outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen;
        while(outLen < headLen + 4) buf[outLen++] = ' ';
        buf[outLen++] = '\r';
        buf[outLen++] = '\n';
        outLen += readLen;
        buf[outLen++] = '\r';
        buf[outLen++] = '\n';
      } else {
        readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen);
        if(readLen == RESPONSE_TRY_AGAIN){
            free(buf);
            return 0;
        }
        outLen = readLen + headLen;
      }

      if(headLen){
          _head = String();
      }

      if(outLen){
          _writtenLength += request->client()->write((const char*)buf, outLen);
      }

      if(_chunked){
          _sentLength += readLen;
      } else {
          _sentLength += outLen - headLen;
      }

      free(buf);

      if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
        _state = RESPONSE_WAIT_ACK;
      }
      return outLen;

    } else if(_state == RESPONSE_WAIT_ACK){
      if(!_sendContentLength || _ackedLength >= _writtenLength){
        _state = RESPONSE_END;
        if(!_chunked && !_sendContentLength)
          request->client()->close(true);
      }
    }
    return 0;
  }

  size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
  {
      // If we have something in cache, copy it to buffer
      const size_t readFromCache = std::min(len, _cache.size());
      if(readFromCache) {
        memcpy(data, _cache.data(), readFromCache);
        _cache.erase(_cache.begin(), _cache.begin() + readFromCache);
      }
      // If we need to read more...
      const size_t needFromFile = len - readFromCache;
      const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
      return readFromCache + readFromContent;
  }

  size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
  {
    if(!_callback)
      return _fillBuffer(data, len);

    const size_t originalLen = len;
    len = _readDataFromCacheOrContent(data, len);
    // Now we've read 'len' bytes, either from cache or from file
    // Search for template placeholders
    uint8_t* pTemplateStart = data;
    while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
      uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
      // temporary buffer to hold parameter name
      uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
      String paramName;
      // If closing placeholder is found:
      if(pTemplateEnd) {
        // prepare argument to callback
        const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1));
        if(paramNameLength) {
          memcpy(buf, pTemplateStart + 1, paramNameLength);
          buf[paramNameLength] = 0;
          paramName = String(reinterpret_cast<char*>(buf));
        } else { // double percent sign encountered, this is single percent sign escaped.
          // remove the 2nd percent sign
          memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
          len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
          ++pTemplateStart;
        }
      } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
        memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
        const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
        if(readFromCacheOrContent) {
          pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
          if(pTemplateEnd) {
            // prepare argument to callback
            *pTemplateEnd = 0;
            paramName = String(reinterpret_cast<char*>(buf));
            // Copy remaining read-ahead data into cache
            _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
            pTemplateEnd = &data[len - 1];
          }
          else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
          {
            // but first, store read file data in cache
            _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
            ++pTemplateStart;
          }
        }
        else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
          ++pTemplateStart;
      }
      else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
        ++pTemplateStart;
      if(paramName.length()) {
        // call callback and replace with result.
        // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
        // Data after pTemplateEnd may need to be moved.
        // The first byte of data after placeholder is located at pTemplateEnd + 1.
        // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
        const String paramValue(_callback(paramName));
        const char* pvstr = paramValue.c_str();
        const unsigned int pvlen = paramValue.length();
        const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
        // make room for param value
        // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
        if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
          _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
          //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
          memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
          len = originalLen; // fix issue with truncated data, not sure if it has any side effects
        } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
          //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
          //   Move the entire data after the placeholder
          memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
        // 3. replace placeholder with actual value
        memcpy(pTemplateStart, pvstr, numBytesCopied);
        // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
        if(numBytesCopied < pvlen) {
          _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
        } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
          // there is some free room, fill it from cache
          const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
          const size_t totalFreeRoom = originalLen - len + roomFreed;
          len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
        } else { // result is copied fully; it is longer than placeholder text
          const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
          len = std::min(len + roomTaken, originalLen);
        }
      }
    } // while(pTemplateStart)
    return len;
  }


  /*
   * File Response
   * */

  AsyncFileResponse::~AsyncFileResponse(){
    if(_content)
      _content.close();
  }

  void AsyncFileResponse::_setContentType(const String& path){
    if (path.endsWith(".html")) _contentType = "text/html";
    else if (path.endsWith(".htm")) _contentType = "text/html";
    else if (path.endsWith(".css")) _contentType = "text/css";
    else if (path.endsWith(".json")) _contentType = "application/json";
    else if (path.endsWith(".js")) _contentType = "application/javascript";
    else if (path.endsWith(".png")) _contentType = "image/png";
    else if (path.endsWith(".gif")) _contentType = "image/gif";
    else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
    else if (path.endsWith(".ico")) _contentType = "image/x-icon";
    else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
    else if (path.endsWith(".eot")) _contentType = "font/eot";
    else if (path.endsWith(".woff")) _contentType = "font/woff";
    else if (path.endsWith(".woff2")) _contentType = "font/woff2";
    else if (path.endsWith(".ttf")) _contentType = "font/ttf";
    else if (path.endsWith(".xml")) _contentType = "text/xml";
    else if (path.endsWith(".pdf")) _contentType = "application/pdf";
    else if (path.endsWith(".zip")) _contentType = "application/zip";
    else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
    else _contentType = "text/plain";
  }

  AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
    _code = 200;
    _path = path;

    if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
      _path = _path+".gz";
      addHeader("Content-Encoding", "gzip");
      _callback = nullptr; // Unable to process zipped templates
      _sendContentLength = true;
      _chunked = false;
    }

    _content = fs.open(_path, "r");
    _contentLength = _content.size();

    if(contentType == "")
      _setContentType(path);
    else
      _contentType = contentType;

    int filenameStart = path.lastIndexOf('/') + 1;
    char buf[26+path.length()-filenameStart];
    char* filename = (char*)path.c_str() + filenameStart;

    if(download) {
      // set filename and force download
      snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
    } else {
      // set filename and force rendering
      snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
    }
    addHeader("Content-Disposition", buf);
  }

  AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
    _code = 200;
    _path = path;

    if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
      addHeader("Content-Encoding", "gzip");
      _callback = nullptr; // Unable to process gzipped templates
      _sendContentLength = true;
      _chunked = false;
    }

    _content = content;
    _contentLength = _content.size();

    if(contentType == "")
      _setContentType(path);
    else
      _contentType = contentType;

    int filenameStart = path.lastIndexOf('/') + 1;
    char buf[26+path.length()-filenameStart];
    char* filename = (char*)path.c_str() + filenameStart;

    if(download) {
      snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
    } else {
      snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
    }
    addHeader("Content-Disposition", buf);
  }

  size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
    return _content.read(data, len);
  }

  /*
   * Stream Response
   * */

  AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
    _code = 200;
    _content = &stream;
    _contentLength = len;
    _contentType = contentType;
  }

  size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
    size_t available = _content->available();
    size_t outLen = (available > len)?len:available;
    size_t i;
    for(i=0;i<outLen;i++)
      data[i] = _content->read();
    return outLen;
  }

  /*
   * Callback Response
   * */

  AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
    _code = 200;
    _content = callback;
    _contentLength = len;
    if(!len)
      _sendContentLength = false;
    _contentType = contentType;
    _filledLength = 0;
  }

  size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
    size_t ret = _content(data, len, _filledLength);
    if(ret != RESPONSE_TRY_AGAIN){
        _filledLength += ret;
    }
    return ret;
  }

  /*
   * Chunked Response
   * */

  AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
    _code = 200;
    _content = callback;
    _contentLength = 0;
    _contentType = contentType;
    _sendContentLength = false;
    _chunked = true;
    _filledLength = 0;
  }

  size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
    size_t ret = _content(data, len, _filledLength);
    if(ret != RESPONSE_TRY_AGAIN){
        _filledLength += ret;
    }
    return ret;
  }

  /*
   * Progmem Response
   * */

  AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
    _code = code;
    _content = content;
    _contentType = contentType;
    _contentLength = len;
    _readLength = 0;
  }

  size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
    size_t left = _contentLength - _readLength;
    if (left > len) {
      memcpy_P(data, _content + _readLength, len);
      _readLength += len;
      return len;
    }
    memcpy_P(data, _content + _readLength, left);
    _readLength += left;
    return left;
  }


  /*
   * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
   * */

  AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){
    _code = 200;
    _contentLength = 0;
    _contentType = contentType;
    _content = new cbuf(bufferSize);
  }

  AsyncResponseStream::~AsyncResponseStream(){
    delete _content;
  }

  size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){
    return _content->read((char*)buf, maxLen);
  }

  size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
    if(_started())
      return 0;

    if(len > _content->room()){
      size_t needed = len - _content->room();
      _content->resizeAdd(needed);
    }
    size_t written = _content->write((const char*)data, len);
    _contentLength += written;
    return written;
  }

  size_t AsyncResponseStream::write(uint8_t data){
    return write(&data, 1);
  }


repo_name: readme-hub
function_name: ON_STA_FILTER
last_modified: 2025-06-09T04:44:46.784799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\src\WebServer.cpp

content:
[cell repeats an earlier one in this file: 5676 characters]


repo_name: readme-hub
function_name: canHandle
last_modified: 2025-06-09T04:44:46.782799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\examples\CaptivePortal\CaptivePortal.ino

content:
[cell repeats an earlier one in this file: 1413 characters]


repo_name: readme-hub
function_name: notFound
last_modified: 2025-06-09T04:44:46.782799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\examples\regex_patterns\regex_patterns.ino

content:
[cell repeats an earlier one in this file: 2210 characters]


repo_name: readme-hub
function_name: notFound
last_modified: 2025-06-09T04:44:46.782799
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ESPAsyncWebServer-esphome\examples\simple_server\simple_server.ino

content:
[cell repeats an earlier one in this file: 1932 characters]


repo_name: readme-hub
function_name: _init_async_event_queue
last_modified: 2024-09-08T22:37:48.779292
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\AsyncTCP-esphome\src\AsyncTCP.cpp

content:
[cell repeats an earlier one in this file: 37716 characters]


repo_name: readme-hub
function_name: connect
last_modified: 2024-09-08T22:37:48.779292
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\AsyncTCP-esphome\src\AsyncTCP.h

content:
[cell repeats an earlier one in this file: 8271 characters]


repo_name: readme-hub
function_name: replyToServer
last_modified: 2024-09-08T22:37:48.779292
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\AsyncTCP-esphome\examples\ClientServer\Client\Client.ino

content:
[cell repeats an earlier one in this file: 932 characters]


repo_name: readme-hub
function_name: loadConfiguration
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonConfigFile\JsonConfigFile.ino

content:
[cell repeats an earlier one in this file: 4135 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonFilterExample\JsonFilterExample.ino

content:
[cell repeats an earlier one in this file: 2546 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonGeneratorExample\JsonGeneratorExample.ino

content:
[cell repeats an earlier one in this file: 1662 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonHttpClient\JsonHttpClient.ino

content:
[cell repeats an earlier one in this file: 3237 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonParserExample\JsonParserExample.ino

content:
[cell repeats an earlier one in this file: 1765 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.582090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonServer\JsonServer.ino

content:
[cell repeats an earlier one in this file: 3126 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.583090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\JsonUdpBeacon\JsonUdpBeacon.ino

content:
[cell repeats an earlier one in this file: 2802 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.583090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\MsgPackParser\MsgPackParser.ino

content:
[cell repeats an earlier one in this file: 1611 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.583090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\ProgmemExample\ProgmemExample.ino

content:
[cell repeats an earlier one in this file: 2069 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-06-20T08:37:28.583090
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\ArduinoJson\examples\StringExample\StringExample.ino

content:
[cell repeats an earlier one in this file: 2610 characters]


repo_name: readme-hub
function_name: displaySensorDetails
last_modified: 2025-01-06T14:52:43
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit Unified Sensor\examples\sensortest\sensortest.ino

content:
[cell repeats an earlier one in this file: 4174 characters]


repo_name: readme-hub
function_name: main
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\scripts\make_splash.py

content:
  #!/usr/bin/env python3
  # pip install pillow to get the PIL module

  import sys
  from PIL import Image

  def main(fn, id):
    image = Image.open(fn)
    print("\n"
          "#define {id}_width  {w}\n"
          "#define {id}_height {h}\n"
          "\n"
          "const uint8_t PROGMEM {id}_data[] = {{\n"
          .format(id=id, w=image.width, h=image.height), end='')
    for y in range(0, image.height):
      for x in range(0, (image.width + 7)//8 * 8):
        if x == 0:
          print("  ", end='')
        if x % 8 == 0:
          print("0b", end='')

        bit = '0'
        if x < image.width and image.getpixel((x,y)) != 0:
          bit = '1'
        print(bit, end='')

        if x % 8 == 7:
          print(",", end='')
      print()
    print("};")

  if __name__ == '__main__':
      if len(sys.argv) < 3:
        print("Usage: {} <imagefile> <id>\n".format(sys.argv[0]), file=sys.stderr);
        sys.exit(1)
      fn = sys.argv[1]
      id = sys.argv[2]
      main(fn, id)


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\OLED_featherwing\OLED_featherwing.ino

content:
[cell repeats an earlier one in this file: 2485 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\ssd1306_128x32_i2c\ssd1306_128x32_i2c.ino

content:
[cell repeats an earlier one in this file: 12019 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\ssd1306_128x32_spi\ssd1306_128x32_spi.ino

content:
[cell repeats an earlier one in this file: 11961 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\ssd1306_128x64_i2c\ssd1306_128x64_i2c.ino

content:
[cell repeats an earlier one in this file: 12019 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\ssd1306_128x64_spi\ssd1306_128x64_spi.ino

content:
[cell repeats an earlier one in this file: 11962 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-12-05T14:38:14
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit SSD1306\examples\ssd1306_64x32_i2c\ssd1306_64x32_i2c.ino

content:
[cell repeats an earlier one in this file: 12018 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\basic_readings\basic_readings.ino

content:
[cell repeats an earlier one in this file: 2695 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\motion_detection\motion_detection.ino

content:
[cell repeats an earlier one in this file: 1537 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\MPU6050_oled\MPU6050_oled.ino

content:
[cell repeats an earlier one in this file: 1980 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\mpu6050_unifiedsensors\mpu6050_unifiedsensors.ino

content:
[cell repeats an earlier one in this file: 2083 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\plotter\plotter.ino

content:
[cell repeats an earlier one in this file: 1263 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2023-11-15T12:59:21
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit MPU6050\examples\sleep_demo\sleep_demo.ino

content:
[cell repeats an earlier one in this file: 2286 characters]


repo_name: readme-hub
function_name: __init__
last_modified: 2025-11-11T15:52:59
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\fontconvert\bdf2adafruit.py

content:
  #!/usr/bin/python2

  # MIT License.

  # Copyright (c) 2016 William Skellenger
  #
  # Permission is hereby granted, free of charge, to any person obtaining a
  # copy of this software and associated documentation files (the "Software"),
  # to deal in the Software without restriction, including without limitation
  # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  # and/or sell copies of the Software, and to permit persons to whom the
  # Software is furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included
  # in all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  # IN THE SOFTWARE.

  # This small script is designed to mostly take a BDF file and convert it to a
  # format that can largely be cut/pasted as an Adafruit-format font.
  # It was written in an hour or so and did what I needed it to do.
  # I used it for one file.  Maybe it bombs on other files.
  # William Skellenger, Feb 2016
  # (email: williamj@skellenger.net)
  # (Twitter: @skelliam)
  #
  # Usage: bdf2adafruit.py <somefont.bdf> > out.txt
  #
  # Once you have out.txt you can cut/paste the contents into a new font
  # header file as part of the Adafruit GFX library.

  import sys

  myfile = open(sys.argv[1])

  processing = 0
  getting_rows = 0

  chars = []
  bitmapData = []

  class Glyph:
      encoding = -1
      rows = []
      comment = ""
      offset = -1
      width = 0
      height = 0
      advance = 0
      xoffs = 0
      yoffs = 0
      def __init__(self, comment):
          self.comment = comment
          self.rows = []

  for line in myfile.readlines():
      if 'STARTCHAR' in line:
          processing = 1
          vals = line.split()
          g = Glyph(vals[1])
          #g.width = 8  #in this example always 8 bits wide
      elif 'ENDCHAR' in line:
          dataByteCompressed = 0
          dataByteCompressedIndex = 8
          g.height = len(bitmapData)
          for value in bitmapData:
              bitIndex = 0
              while bitIndex < g.width:
                  bit = (value >> (7 - bitIndex)) & 0x01
                  dataByteCompressed |= bit << (dataByteCompressedIndex - 1)
                  dataByteCompressedIndex -= 1
                  if dataByteCompressedIndex == 0:
                      dataByteCompressedIndex = 8
                      g.rows.append(dataByteCompressed)
                      dataByteCompressed = 0
                  bitIndex += 1
          if 8 != dataByteCompressedIndex:
              g.rows.append(dataByteCompressed)

          chars.append(g)  #append the completed glyph into list
          processing = 0
          getting_rows = 0
          bitmapData.clear()

      if processing:
          if 'ENCODING' in line:
              vals = line.split()
              g.encoding = int(vals[1])
          elif 'DWIDTH' in line:
              vals = line.split()
              #g.advance = int(vals[1])  #cursor advance seems to be the first number in DWIDTH
          elif 'BBX' in line:
              vals = line.split()
              g.xoffs = 0
              g.yoffs = -(int(vals[2]) + int(vals[4]))
              g.advance = (int(vals[1]) + 1)  #x bounding box + 1
              g.width = int(vals[1])
          elif 'BITMAP' in line:
              getting_rows = 1
          elif getting_rows:
              #g.rows.append(int(line, 16))  #append pixel rows into glyph's list of rows
              bitmapData.append(int(line, 16))

  print

  i=0
  for char in chars:
      char.offset = i
      print("\t", end='')
      num = 3
      for row in char.rows:
          if num != 3:
              print(" ", end = '')
          print("0x%02X," %(row), end = ''),
          i+=1
          num-=1

      if num == 1:
          print("\t\t", end = '')
      if num == 2:
          print("\t\t\t", end = '')
      print("\t/* 0x%02X %s */" %(char.encoding, char.comment))

  for char in chars:
      # offset, bit-width, bit-height, advance cursor, x offset, y offset
      print("\t{ %d, %d, %d, %d, %d, %d }, /* 0x%02X %s */" %(
              char.offset, char.width, char.height,
              char.advance, char.xoffs, char.yoffs,
              char.encoding, char.comment))


repo_name: readme-hub
function_name: setup
last_modified: 2025-11-11T15:52:59
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\examples\GFXcanvas\GFXcanvas.ino

content:
[cell repeats an earlier one in this file: 3671 characters]


repo_name: readme-hub
function_name: print
last_modified: 2025-11-11T15:52:59
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\examples\GFXcanvas\GFXcanvasSerialDemo.h

content:
[cell repeats an earlier one in this file: 2408 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-11-11T15:52:59
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit GFX Library\examples\mock_ili9341\mock_ili9341.ino

content:
[cell repeats an earlier one in this file: 9790 characters]


repo_name: readme-hub
function_name: uart_read
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\genericdevice_uartregtest\genericdevice_uartregtest.ino

content:
[cell repeats an earlier one in this file: 6065 characters]


repo_name: readme-hub
function_name: uart_write
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\genericdevice_uarttest\genericdevice_uarttest.ino

content:
[cell repeats an earlier one in this file: 2579 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\i2corspi_register\i2corspi_register.ino

content:
[cell repeats an earlier one in this file: 1039 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\i2c_address_detect\i2c_address_detect.ino

content:
[cell repeats an earlier one in this file: 465 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\i2c_readwrite\i2c_readwrite.ino

content:
[cell repeats an earlier one in this file: 1086 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\i2c_registers\i2c_registers.ino

content:
[cell repeats an earlier one in this file: 1075 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\spi_modetest\spi_modetest.ino

content:
[cell repeats an earlier one in this file: 805 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\spi_readwrite\spi_readwrite.ino

content:
[cell repeats an earlier one in this file: 963 characters]


repo_name: readme-hub
function_name: setup
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\spi_registers\spi_registers.ino

content:
[cell repeats an earlier one in this file: 991 characters]


repo_name: readme-hub
function_name: printConfig
last_modified: 2025-09-22T21:18:10
file_path:
  C:\Users\gilbe\Documents\GitHub\readme-hub\wheelie\.pio\libdeps\debug\Adafruit BusIO\examples\spi_register_bits\spi_register_bits.ino

content:
[cell repeats an earlier one in this file: 9080 characters]


file_path: C:\Users\gilbe\Documents\GitHub\readme-hub\buddAI\archive\buddai_v2.py
repo_name: readme-hub
function_name: check_ollama
last_modified: 2025-12-28T13:39:41.226492
content:
  #!/usr/bin/env python3
  """
  BuddAI Executive v2.0 - Modular Builder
  Breaks complex tasks into manageable chunks

  Author: James Gilbert
  License: MIT
  """

  import os
  import sys
  import json
  import sqlite3
  from datetime import datetime
  from pathlib import Path
  import http.client
  import re

  # Configuration
  OLLAMA_HOST = "localhost"
  OLLAMA_PORT = 11434
  DATA_DIR = Path(__file__).parent / "data"
  DB_PATH = DATA_DIR / "conversations.db"

  # Models
  MODELS = {
      "fast": "qwen2.5-coder:1.5b",
      "balanced": "qwen2.5-coder:3b"
  }

  # Complexity triggers - if matched, break down the task
  COMPLEX_TRIGGERS = [
      "complete", "entire", "full", "build entire", "build complete",
      "with ble and", "with servo and", "including", "all of"
  ]

  # Module patterns we can detect
  MODULE_PATTERNS = {
      "ble": ["bluetooth", "ble", "wireless"],
      "servo": ["servo", "flipper", "weapon"],
      "motor": ["motor", "drive", "movement", "l298n"],
      "safety": ["safety", "timeout", "failsafe", "emergency"],
      "battery": ["battery", "voltage", "power monitor"],
      "sensor": ["sensor", "distance", "proximity"]
  }


  class BuddAI:
      """Executive with task breakdown"""
      
      def __init__(self):
          self.ensure_data_dir()
          self.init_database()
          self.session_id = self.create_session()
          self.context_messages = []
          
          print("🧠 BuddAI Executive v2.0 - Modular Builder")
          print("=" * 50)
          print(f"Session: {self.session_id}")
          print(f"FAST (5-10s) | BALANCED (15-30s)")
          print(f"Smart task breakdown for complex requests")
          print("=" * 50)
          print("\nCommands: /fast, /balanced, /help, exit\n")
          
      def ensure_data_dir(self):
          DATA_DIR.mkdir(exist_ok=True)
          
      def init_database(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS sessions (
                  session_id TEXT PRIMARY KEY,
                  started_at TIMESTAMP,
                  ended_at TIMESTAMP
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS messages (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  session_id TEXT,
                  role TEXT,
                  content TEXT,
                  timestamp TIMESTAMP
              )
          """)
          
          cursor.execute("""
              CREATE TABLE IF NOT EXISTS repo_index (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  file_path TEXT,
                  repo_name TEXT,
                  function_name TEXT,
                  content TEXT,
                  last_modified TIMESTAMP
              )
          """)
          
          conn.commit()
          conn.close()
          
      def create_session(self):
          session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO sessions (session_id, started_at) VALUES (?, ?)",
              (session_id, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          return session_id
          
      def end_session(self):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "UPDATE sessions SET ended_at = ? WHERE session_id = ?",
              (datetime.now().isoformat(), self.session_id)
          )
          conn.commit()
          conn.close()
          
      def save_message(self, role, content):
          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          cursor.execute(
              "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
              (self.session_id, role, content, datetime.now().isoformat())
          )
          conn.commit()
          conn.close()
          
      def index_local_repositories(self, root_path):
          """Crawl directories and index .py, .ino, and .cpp files"""
          import ast
          
          print(f"\n🔍 Indexing repositories in: {root_path}")
          path = Path(root_path)
          
          if not path.exists():
              print(f"❌ Path not found: {root_path}")
              return

          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          count = 0
          
          for file_path in path.rglob('*'):
              if file_path.is_file() and file_path.suffix in ['.py', '.ino', '.cpp', '.h']:
                  try:
                      with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                          content = f.read()
                          
                      functions = []
                      
                      # Python parsing
                      if file_path.suffix == '.py':
                          try:
                              tree = ast.parse(content)
                              for node in ast.walk(tree):
                                  if isinstance(node, ast.FunctionDef):
                                      functions.append(node.name)
                          except:
                              pass
                              
                      # C++/Arduino parsing
                      elif file_path.suffix in ['.ino', '.cpp', '.h']:
                          matches = re.findall(r'\b(?:void|int|bool|float|double|String|char)\s+(\w+)\s*\(', content)
                          functions.extend(matches)
                      
                      # Determine repo name
                      try:
                          repo_name = file_path.relative_to(path).parts[0]
                      except:
                          repo_name = "unknown"
                          
                      timestamp = datetime.fromtimestamp(file_path.stat().st_mtime)
                      
                      for func in functions:
                          cursor.execute("""
                              INSERT INTO repo_index (file_path, repo_name, function_name, content, last_modified)
                              VALUES (?, ?, ?, ?, ?)
                          """, (str(file_path), repo_name, func, content, timestamp.isoformat()))
                          count += 1
                          
                  except Exception:
                      pass
                      
          conn.commit()
          conn.close()
          print(f"✅ Indexed {count} functions across repositories")

      def retrieve_style_context(self, message):
          """Search repo_index for code snippets matching the request"""
          # Extract potential keywords (nouns/modules)
          keywords = re.findall(r'\b\w{4,}\b', message.lower())
          if not keywords:
              return ""

          conn = sqlite3.connect(DB_PATH)
          cursor = conn.cursor()
          
          # Build a search query for function names or repo names
          search_terms = " OR ".join([f"function_name LIKE '%{k}%'" for k in keywords])
          search_terms += " OR " + " OR ".join([f"repo_name LIKE '%{k}%'" for k in keywords])
          
          query = f"SELECT repo_name, function_name, content FROM repo_index WHERE {search_terms} LIMIT 2"
          
          cursor.execute(query)
          results = cursor.fetchall()
          conn.close()
          
          if not results:
              return ""
              
          context_block = "\n[REFERENCE STYLE FROM JAMES'S PAST PROJECTS]\n"
          for repo, func, content in results:
              # Just grab the first 500 chars of the file to save context window
              snippet = content[:500] + "..."
              context_block += f"Repo: {repo} | Function: {func}\nCode:\n{snippet}\n---\n"
          
          return context_block

      def is_simple_question(self, message):
          """Check if this is a simple question that should use FAST model"""
          message_lower = message.lower()
          
          simple_triggers = [
              "what is", "what's", "who is", "who's", "when is",
              "how do i", "can you explain", "tell me about",
              "what are", "where is"
          ]
          
          # Also check if it's just a question without code keywords
          code_keywords = ["generate", "create", "write", "build", "code", "function"]
          
          has_simple_trigger = any(trigger in message_lower for trigger in simple_triggers)
          has_code_keyword = any(keyword in message_lower for keyword in code_keywords)
          
          # Simple if: has simple trigger AND no code keywords
          return has_simple_trigger and not has_code_keyword
      
      def is_complex(self, message):
          """Check if request is too complex and should be broken down"""
          message_lower = message.lower()
          
          # Count complexity triggers
          trigger_count = sum(1 for trigger in COMPLEX_TRIGGERS if trigger in message_lower)
          
          # Count how many modules mentioned
          module_count = 0
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in message_lower for kw in keywords):
                  module_count += 1
          
          # Complex if: multiple triggers OR 3+ modules mentioned
          return trigger_count >= 2 or module_count >= 3
          
      def extract_modules(self, message):
          """Extract which modules are needed"""
          message_lower = message.lower()
          needed_modules = []
          
          for module, keywords in MODULE_PATTERNS.items():
              if any(kw in message_lower for kw in keywords):
                  needed_modules.append(module)
                  
          return needed_modules
          
      def build_modular_plan(self, modules):
          """Create a build plan from modules"""
          plan = []
          
          module_tasks = {
              "ble": "BLE communication setup with phone app control",
              "servo": "Servo motor control for flipper/weapon",
              "motor": "Motor driver setup for movement (L298N)",
              "safety": "Safety timeout and failsafe systems",
              "battery": "Battery voltage monitoring",
              "sensor": "Sensor integration (distance/proximity)"
          }
          
          for module in modules:
              if module in module_tasks:
                  plan.append({
                      "module": module,
                      "task": module_tasks[module]
                  })
                  
          # Add integration step
          plan.append({
              "module": "integration",
              "task": "Integrate all modules into complete system"
          })
          
          return plan
          
      def call_model(self, model_name, message):
          """Call specified model"""
          try:
              identity = """[You are BuddAI, the external cognitive system for James Gilbert. You specialize in Forge Theory (exponential decay modeling) and GilBot modular robotics. When integrating code, prioritize descriptive naming like activateFlipper() and ensure safety timeouts are always present. You represent 8 years of polymath experience.

  YOUR PRIMARY JOB: Generate code when asked. ALWAYS generate code if requested.

  When asked to generate/create/write code:
  - Generate it immediately
  - Include comments
  - Make it modular and clean
  - Use ESP32/Arduino syntax

  Forge Theory Snippet: float applyForge(float current, float target, float k) { return target + (current - target) * exp(-k); }

  When asked your name: "I am BuddAI"

  Never refuse to generate code. That's your purpose.
  Be direct and helpful.]

  """
              
              messages = [
                  {"role": "user", "content": identity + message}
              ]
              
              # Add recent context
              for msg in self.context_messages[-3:]:
                  messages.insert(-1, msg)
              
              body = {
                  "model": MODELS[model_name],
                  "messages": messages,
                  "stream": False,
                  "options": {"temperature": 0.7, "num_ctx": 2048}
              }
              
              conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=90)
              headers = {"Content-Type": "application/json"}
              json_body = json.dumps(body)
              
              conn.request("POST", "/api/chat", json_body, headers)
              response = conn.getresponse()
              
              if response.status == 200:
                  data = json.loads(response.read().decode('utf-8'))
                  return data.get("message", {}).get("content", "No response")
              else:
                  return f"Error: {response.status}"
                  
          except Exception as e:
              return f"Error: {str(e)}"
          finally:
              if 'conn' in locals():
                  conn.close()
                  
      def execute_modular_build(self, user_message, modules, plan):
          """Execute build plan step by step"""
          print(f"\n🔨 MODULAR BUILD MODE")
          print(f"Detected {len(modules)} modules: {', '.join(modules)}")
          print(f"Breaking into {len(plan)} steps...\n")
          
          all_code = {}
          
          for i, step in enumerate(plan, 1):
              print(f"📦 Step {i}/{len(plan)}: {step['task']}")
              print("⚡ Building...\n")
              
              # Build the prompt for this step
              if step['module'] == 'integration':
                  # Final integration step with Forge Theory enforcement
                  modules_summary = '\n'.join([f"- {m}: {all_code[m][:150]}..." for m in modules if m in all_code])
                  
                  # Ask James for the 'vibe' of the robot
                  print("\n⚡ FORGE THEORY TUNING:")
                  print("1. Aggressive (k=0.3) - High snap, combat ready")
                  print("2. Balanced (k=0.1) - Standard movement")
                  print("3. Graceful (k=0.03) - Roasting / Smooth curves")
                  choice = input("Select Forge Constant [1-3, default 2]: ")
                  
                  k_val = "0.1"
                  if choice == "1": k_val = "0.3"
                  elif choice == "3": k_val = "0.03"

                  prompt = f"""INTEGRATION TASK: Combine modules into a cohesive GilBot system.
      
      [MODULES]
      {modules_summary}
      
      [FORGE PARAMETERS]
      Set k = {k_val} for all applyForge() calls.
      
      [REQUIREMENTS]
      1. Implement applyForge() math helper.
      2. Use k={k_val} to smooth motor and servo transitions.
      3. Ensure naming matches James's style: activateFlipper(), setMotors().
      """
              else:
                  # Individual module
                  prompt = f"Generate ESP32-C3 code for: {step['task']}. Keep it modular with clear comments."
              
              # Call balanced model for each module
              response = self.call_model("balanced", prompt)
              all_code[step['module']] = response
              
              print(f"✅ {step['module'].upper()} module complete\n")
              print("-" * 50 + "\n")
          
          # Compile final response
          final = "# COMPLETE GILBOT CONTROLLER - MODULAR BUILD\n\n"
          for module, code in all_code.items():
              final += f"## {module.upper()} MODULE\n{code}\n\n"
              
          return final
          
      def chat(self, user_message, force_model=None):
          """Main chat with smart routing"""
          # 1. Before routing, pull relevant style context
          style_context = self.retrieve_style_context(user_message)

          # 2. Add it to the session's context if found
          if style_context:
              # We add it as a system-level reminder for the model
              self.context_messages.append({"role": "system", "content": style_context})

          # Save user message
          self.save_message("user", user_message)
          self.context_messages.append({"role": "user", "content": user_message})
          
          # Determine which approach to use
          if force_model:
              # User forced a specific model
              model = force_model
              print(f"\n⚡ Using {model.upper()} model (forced)...")
              response = self.call_model(model, user_message)
              
          elif self.is_complex(user_message):
              # Complex request - use modular breakdown
              modules = self.extract_modules(user_message)
              plan = self.build_modular_plan(modules)
              
              print("\n" + "=" * 50)
              print("🎯 COMPLEX REQUEST DETECTED!")
              print(f"Modules needed: {', '.join(modules)}")
              print(f"Breaking into {len(plan)} manageable steps")
              print("=" * 50)
              
              response = self.execute_modular_build(user_message, modules, plan)
              
          elif self.is_simple_question(user_message):
              # Simple question - use FAST model
              print("\n⚡ Using FAST model (simple question)...")
              response = self.call_model("fast", user_message)
              
          else:
              # Medium complexity - use BALANCED model
              print("\n⚖️  Using BALANCED model...")
              response = self.call_model("balanced", user_message)
          
          # Save response
          self.save_message("assistant", response)
          self.context_messages.append({"role": "assistant", "content": response})
          
          return response
          
      def run(self):
          """Main loop"""
          try:
              force_model = None
              
              while True:
                  user_input = input("\nJames: ").strip()
                  
                  if not user_input:
                      continue
                      
                  if user_input.lower() in ['exit', 'quit']:
                      print("\n👋 Later!")
                      self.end_session()
                      break
                      
                  if user_input.startswith('/'):
                      cmd = user_input.lower()
                      if cmd == '/fast':
                          force_model = "fast"
                          print("⚡ Next: FAST model")
                          continue
                      elif cmd == '/balanced':
                          force_model = "balanced"
                          print("⚖️  Next: BALANCED model")
                          continue
                      elif cmd == '/help':
                          print("\n💡 Commands:")
                          print("/fast - Use fast model")
                          print("/balanced - Use balanced model")
                          print("/index <path> - Index local repositories")
                          print("/help - This message")
                          print("exit - End session\n")
                          continue
                      elif cmd.startswith('/index'):
                          parts = user_input.split(maxsplit=1)
                          if len(parts) > 1:
                              self.index_local_repositories(parts[1])
                          else:
                              print("Usage: /index <path_to_repos>")
                          continue
                      else:
                          print("\nUnknown command. Type /help")
                          continue
                  
                  # Chat
                  response = self.chat(user_input, force_model)
                  print(f"\nBuddAI:\n{response}\n")
                  
                  force_model = None
                      
          except KeyboardInterrupt:
              print("\n\n👋 Bye!")
              self.end_session()


  def check_ollama():
      try:
          conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=5)
          conn.request("GET", "/api/tags")
          response = conn.getresponse()
          conn.close()
          return response.status == 200
      except:
          return False


  def main():
      if not check_ollama():
          print("❌ Ollama not running. Start: ollama serve")
          sys.exit(1)
          
      buddai = BuddAI()
      buddai.run()


  if __name__ == "__main__":
      main()


