Full Code of robertoostenveld/arduino for AI

main 50b80b8019ad cached
263 files
631.7 KB
198.0k tokens
334 symbols
1 requests
Download .txt
Showing preview only (696K chars total). Download the full file or copy to clipboard to get everything.
Repository: robertoostenveld/arduino
Branch: main
Commit: 50b80b8019ad
Files: 263
Total size: 631.7 KB

Directory structure:
gitextract_yp2dqrl9/

├── .gitignore
├── .gitmodules
├── PulseSensor_v0/
│   ├── AllSerialHandling.cpp
│   ├── Interrupt.cpp
│   ├── PulseSensor_v0.ino
│   ├── README.md
│   └── Timer_Interrupt_Notes.cpp
├── PulseSensor_v1/
│   ├── PulseSensor_v1.ino
│   └── README.md
├── PulseSensor_v2/
│   ├── Interrupt.cpp
│   ├── PulseSensor_v2.ino
│   └── README.md
├── README.md
├── bitsi/
│   └── bitsi.ino
├── blenderdefender/
│   └── blenderdefender.ino
├── digispark_skateboard/
│   ├── README.md
│   ├── colorspace.cpp
│   ├── colorspace.h
│   ├── digispark_skateboard.ino
│   ├── neopixel_mode.cpp
│   └── neopixel_mode.h
├── eegsynth_cvgate_mcp4725/
│   ├── README.md
│   └── eegsynth_cvgate_mcp4725.ino
├── eegsynth_cvgate_mcp4822/
│   └── eegsynth_cvgate_mcp4822.ino
├── eegsynth_devirtualizer/
│   └── eegsynth_devirtualizer.ino
├── eegsynth_usbdmxpro/
│   ├── COPYING.txt
│   └── eegsynth_usbdmxpro.ino
├── esp32_3wd_servo/
│   ├── README.md
│   └── esp32_3wd_servo.ino
├── esp32_3wd_stepper/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   ├── waypoints.html
│   │   ├── waypoints1.csv
│   │   ├── waypoints2.csv
│   │   ├── waypoints3.csv
│   │   ├── waypoints4.csv
│   │   ├── waypoints5.csv
│   │   ├── waypoints6.csv
│   │   ├── waypoints7.csv
│   │   └── waypoints8.csv
│   ├── esp32_3wd_stepper.ino
│   ├── parseosc.cpp
│   ├── parseosc.h
│   ├── stepper.cpp
│   ├── stepper.h
│   ├── util.cpp
│   ├── util.h
│   ├── waypoints.cpp
│   ├── waypoints.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_config_webinterface_v5/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   └── esp32_config_webinterface_v5.ino
├── esp32_config_webinterface_v6/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_config_webinterface_v6.ino
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_config_webinterface_v7/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_config_webinterface_v7.ino
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_exgpill/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_exgpill.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_inmp441/
│   ├── RunningStat.h
│   ├── esp32_inmp441.ino
│   └── util.h
├── esp32_sph0645/
│   ├── RunningStat.h
│   ├── compress.cpp
│   └── esp32_sph0645.ino
├── esp32c6_feeder/
│   ├── README.md
│   └── esp32c6_feeder.ino
├── esp8266_12v_trigger/
│   └── esp8266_12v_trigger.ino
├── esp8266_ad8232_ecg/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_ad8232_ecg.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_artnet_bci/
│   └── esp8266_artnet_bci.ino
├── esp8266_artnet_neopixel/
│   ├── README.md
│   ├── colorspace.cpp
│   ├── colorspace.h
│   ├── data/
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_artnet_neopixel.ino
│   ├── font8x8_basic.h
│   ├── neopixel_mode.cpp
│   ├── neopixel_mode.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_config_spiffs/
│   ├── data/
│   │   └── config.json
│   └── esp8266_config_spiffs.ino
├── esp8266_config_webinterface/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   └── esp8266_config_webinterface.ino
├── esp8266_fan_control/
│   └── esp8266_fan_control.ino
├── esp8266_fieldtrip_buffer/
│   ├── esp8266_fieldtrip_buffer.ino
│   ├── fieldtrip_buffer.cpp
│   └── fieldtrip_buffer.h
├── esp8266_imu_osc/
│   ├── I2Cscan.cpp
│   ├── I2Cscan.h
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_imu_osc.ino
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── tca9548a.cpp
│   ├── tca9548a.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_inmp411/
│   ├── RunningStat.h
│   └── esp8266_inmp411.ino
├── esp8266_p1_thingspeak/
│   └── esp8266_p1_thingspeak.ino
├── esp8266_polar_wearlink/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_polar_wearlink.ino
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_pulse_sensor/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_pulse_sensor.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_thingspeak/
│   └── esp8266_thingspeak.ino
├── m5dial_midi/
│   ├── colormap.h
│   └── m5dial_midi.ino
├── m5nanoc6_angle8_midi/
│   ├── colormap.h
│   └── m5nanoc6_angle8_midi.ino
├── m5nanoc6_encoder8_midi/
│   ├── colormap.h
│   └── m5nanoc6_encoder8_midi.ino
├── rfm12b_recv_xxxx/
│   ├── README.md
│   └── rfm12b_recv_xxxx.ino
├── rfm12b_send_am2301/
│   ├── README.md
│   └── rfm12b_send_am2301.ino
├── rfm12b_send_bmp085/
│   ├── README.md
│   └── rfm12b_send_bmp085.ino
├── rfm12b_send_cny70/
│   ├── README.md
│   └── rfm12b_send_cny70.ino
├── rfm12b_send_ds18b20/
│   ├── README.md
│   └── rfm12b_send_ds18b20.ino
├── rfm12b_send_lm35/
│   ├── README.md
│   └── rfm12b_send_lm35.ino
├── rfm12b_send_random/
│   ├── README.md
│   └── rfm12b_send_random.ino
├── rfm12b_thingspeak/
│   ├── README.md
│   └── rfm12b_thingspeak.ino
├── rp2040_dac7578/
│   ├── blink_led.cpp
│   ├── blink_led.h
│   └── rp2040_dac7578.ino
├── teensy_cvgate_mcp4725_neopixel/
│   ├── colormap.h
│   └── teensy_cvgate_mcp4725_neopixel.ino
├── teensy_gps_temp_ttn/
│   ├── payload.h
│   ├── sensor.cpp
│   └── teensy_gps_temp_ttn.ino
├── teensy_midifilter/
│   ├── midiname.c
│   └── teensy_midifilter.ino
├── teensy_ttn1/
│   └── teensy_ttn1.ino
├── teensy_ttn2/
│   └── teensy_ttn2.ino
└── uno_dac7578/
    ├── blink_led.cpp
    ├── blink_led.h
    └── uno_dac7578.ino

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
*.swp
secret.h
.vscode


================================================
FILE: .gitmodules
================================================
[submodule "esp8266_artnet_dmx512"]
	path = esp8266_artnet_dmx512
	url = git@github.com:robertoostenveld/esp8266_artnet_dmx512.git


================================================
FILE: PulseSensor_v0/AllSerialHandling.cpp
================================================

//////////
/////////  All Serial Handling Code, 
/////////  It's Changeable with the 'serialVisual' variable
/////////  Set it to 'true' or 'false' when it's declared at start of code.  
/////////

void serialOutput(){   // Decide How To Output Serial. 
 if (serialVisual == true){  
     arduinoSerialMonitorVisual('-', Signal);   // goes to function that makes Serial Monitor Visualizer
 } else{
      sendDataToSerial('S', Signal);     // goes to sendDataToSerial function
 }        
}


//  Decides How To OutPut BPM and IBI Data
void serialOutputWhenBeatHappens(){    
 if (serialVisual == true){            //  Code to Make the Serial Monitor Visualizer Work
    Serial.print("*** Heart-Beat Happened *** ");  //ASCII Art Madness
    Serial.print("BPM: ");
    Serial.print(BPM);
    Serial.print("  ");
 } else{
        sendDataToSerial('B',BPM);   // send heart rate with a 'B' prefix
        sendDataToSerial('Q',IBI);   // send time between beats with a 'Q' prefix
 }   
}



//  Sends Data to Pulse Sensor Processing App, Native Mac App, or Third-party Serial Readers. 
void sendDataToSerial(char symbol, int data ){
    Serial.print(symbol);

    Serial.println(data);                
  }


//  Code to Make the Serial Monitor Visualizer Work
void arduinoSerialMonitorVisual(char symbol, int data ){    
  const int sensorMin = 0;      // sensor minimum, discovered through experiment
const int sensorMax = 1024;    // sensor maximum, discovered through experiment

  int sensorReading = data;
  // map the sensor range to a range of 12 options:
  int range = map(sensorReading, sensorMin, sensorMax, 0, 11);

  // do something different depending on the 
  // range value:
  switch (range) {
  case 0:     
    Serial.println("");     /////ASCII Art Madness
    break;
  case 1:   
    Serial.println("---");
    break;
  case 2:    
    Serial.println("------");
    break;
  case 3:    
    Serial.println("---------");
    break;
  case 4:   
    Serial.println("------------");
    break;
  case 5:   
    Serial.println("--------------|-");
    break;
  case 6:   
    Serial.println("--------------|---");
    break;
  case 7:   
    Serial.println("--------------|-------");
    break;
  case 8:  
    Serial.println("--------------|----------");
    break;
  case 9:    
    Serial.println("--------------|----------------");
    break;
  case 10:   
    Serial.println("--------------|-------------------");
    break;
  case 11:   
    Serial.println("--------------|-----------------------");
    break;
  
  } 
}




================================================
FILE: PulseSensor_v0/Interrupt.cpp
================================================



volatile int rate[10];                    // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0;          // used to determine pulse timing
volatile unsigned long lastBeatTime = 0;           // used to find IBI
volatile int P =512;                      // used to find peak in pulse wave, seeded
volatile int T = 512;                     // used to find trough in pulse wave, seeded
volatile int thresh = 525;                // used to find instant moment of heart beat, seeded
volatile int amp = 100;                   // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true;        // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false;      // used to seed rate array so we startup with reasonable BPM


void interruptSetup(){     
  // Initializes Timer2 to throw an interrupt every 2mS.
  TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
  TCCR2B = 0x06;     // DON'T FORCE COMPARE, 256 PRESCALER 
  OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
  TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
  sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED      
} 


// THIS IS THE TIMER 2 INTERRUPT SERVICE ROUTINE. 
// Timer 2 makes sure that we take a reading every 2 miliseconds
ISR(TIMER2_COMPA_vect){                         // triggered when Timer2 counts to 124
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor 
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise

    //  find the peak and trough of the pulse wave
  if(Signal < thresh && N > (IBI/5)*3){       // avoid dichrotic noise by waiting 3/5 of last IBI
    if (Signal < T){                        // T is the trough
      T = Signal;                         // keep track of lowest point in pulse wave 
    }
  }

  if(Signal > thresh && Signal > P){          // thresh condition helps avoid noise
    P = Signal;                             // P is the peak
  }                                        // keep track of highest point in pulse wave

  //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
  // signal surges up in value every time there is a pulse
  if (N > 250){                                   // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){        
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin,HIGH);                // turn on pin 13 LED
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse

      if(secondBeat){                        // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for(int i=0; i<=9; i++){             // seed the running total to get a realisitic BPM at startup
          rate[i] = IBI;                      
        }
      }

      if(firstBeat){                         // if it's the first time we found a beat, if firstBeat == TRUE
        firstBeat = false;                   // clear firstBeat flag
        secondBeat = true;                   // set the second beat flag
        sei();                               // enable interrupts again
        return;                              // IBI value is unreliable so discard it
      }   


      // keep a running total of the last 10 IBI values
      word runningTotal = 0;                  // clear the runningTotal variable    

      for(int i=0; i<=8; i++){                // shift data in the rate array
        rate[i] = rate[i+1];                  // and drop the oldest IBI value 
        runningTotal += rate[i];              // add up the 9 oldest IBI values
      }

      rate[9] = IBI;                          // add the latest IBI to the rate array
      runningTotal += rate[9];                // add the latest IBI to runningTotal
      runningTotal /= 10;                     // average the last 10 IBI values 
      BPM = 60000/runningTotal;               // how many beats can fit into a minute? that's BPM!
      QS = true;                              // set Quantified Self flag 
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }                       
  }

  if (Signal < thresh && Pulse == true){   // when the values are going down, the beat is over
    digitalWrite(blinkPin,LOW);            // turn off pin 13 LED
    Pulse = false;                         // reset the Pulse flag so we can do it again
    amp = P - T;                           // get amplitude of the pulse wave
    thresh = amp/2 + T;                    // set thresh at 50% of the amplitude
    P = thresh;                            // reset these for next time
    T = thresh;
  }

  if (N > 2500){                           // if 2.5 seconds go by without a beat
    thresh = 512;                          // set thresh default
    P = 512;                               // set P default
    T = 512;                               // set T default
    lastBeatTime = sampleCounter;          // bring the lastBeatTime up to date        
    firstBeat = true;                      // set these to avoid noise
    secondBeat = false;                    // when we get the heartbeat back
  }

  sei();                                   // enable interrupts when youre done!
}// end isr







================================================
FILE: PulseSensor_v0/PulseSensor_v0.ino
================================================

/*  Pulse Sensor Amped 1.4    by Joel Murphy and Yury Gitman   http://www.pulsesensor.com

----------------------  Notes ----------------------  ---------------------- 
This code:
1) Blinks an LED to User's Live Heartbeat   PIN 13
2) Fades an LED to User's Live HeartBeat
3) Determines BPM
4) Prints All of the Above to Serial

Read Me:
https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino/blob/master/README.md   
 ----------------------       ----------------------  ----------------------
*/

//  Variables
int pulsePin = 0;                 // Pulse Sensor purple wire connected to analog pin 0
int blinkPin = 13;                // pin to blink led at each beat
int fadePin = 5;                  // pin to do fancy classy fading blink at each beat
int fadeRate = 0;                 // used to fade LED on with PWM on fadePin

// Volatile Variables, used in the interrupt service routine!
volatile int BPM;                   // int that holds raw Analog in 0. updated every 2mS
volatile int Signal;                // holds the incoming raw data
volatile int IBI = 600;             // int that holds the time interval between beats! Must be seeded! 
volatile boolean Pulse = false;     // "True" when User's live heartbeat is detected. "False" when not a "live beat". 
volatile boolean QS = false;        // becomes true when Arduoino finds a beat.

// Regards Serial OutPut  -- Set This Up to your needs
static boolean serialVisual = false;   // Set to 'false' by Default.  Re-set to 'true' to see Arduino Serial Monitor ASCII Visual Pulse 


void setup(){
  pinMode(blinkPin,OUTPUT);         // pin that will blink to your heartbeat!
  pinMode(fadePin,OUTPUT);          // pin that will fade to your heartbeat!
  Serial.begin(115200);             // we agree to talk fast!
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS 
   // IF YOU ARE POWERING The Pulse Sensor AT VOLTAGE LESS THAN THE BOARD VOLTAGE, 
   // UN-COMMENT THE NEXT LINE AND APPLY THAT VOLTAGE TO THE A-REF PIN
//   analogReference(EXTERNAL);   
}


//  Where the Magic Happens
void loop(){
  
    serialOutput() ;       
    
  if (QS == true){     // A Heartbeat Was Found
                       // BPM and IBI have been Determined
                       // Quantified Self "QS" true when arduino finds a heartbeat
        fadeRate = 255;         // Makes the LED Fade Effect Happen
                                // Set 'fadeRate' Variable to 255 to fade LED with pulse
        serialOutputWhenBeatHappens();   // A Beat Happened, Output that to serial.     
        QS = false;                      // reset the Quantified Self flag for next time    
  }
     
  ledFadeToBeat();                      // Makes the LED Fade Effect Happen 
  delay(20);                             //  take a break
}





void ledFadeToBeat(){
    fadeRate -= 15;                         //  set LED fade value
    fadeRate = constrain(fadeRate,0,255);   //  keep LED fade value from going into negative numbers!
    analogWrite(fadePin,fadeRate);          //  fade LED
  }






================================================
FILE: PulseSensor_v0/README.md
================================================
# Arduino sketch for PulseSensor, see http://pulsesensor.com

This is the original code from https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino. I have only renamed it for consistency with my other PulseSense sketches.


================================================
FILE: PulseSensor_v0/Timer_Interrupt_Notes.cpp
================================================
/*
  These notes put together by Joel Murphy for Pulse Sensor Amped, 2015

  The code that this section is attached to uses a timer interrupt
  to sample the Pulse Sensor with consistent and regular timing.
  The code is setup to read Pulse Sensor signal at 500Hz (every 2mS).
  The reasoning for this can be found here:
  http://pulsesensor.com/pages/pulse-sensor-amped-arduino-v1dot1
  
  There are issues with using different timers to control the Pulse Sensor sample rate.
  Sometimes, user will need to switch timers for access to other code libraries.
  Also, some other hardware may have different timer setup requirements. This page
  will cover those different needs and reveal the necessary settings. There are two
  part of the code that will be discussed. The interruptSetup() routine, and
  the interrupt function call. Depending on your needs, or the Arduino variant that you use,
  check below for the correct settings.
  
  
  ******************************************************************************************
  ARDUINO UNO, Pro 328-5V/16MHZ, Pro-Mini 328-5V/16MHz (or any board with ATmega328P running at 16MHz)
  
 >> Timer2
  
    Pulse Sensor Arduino UNO uses Timer2 by default.
    Use of Timer2 interferes with PWM on pins 3 and 11.
    There is also a conflict with the Tone library, so if you want tones, use Timer1 below.
    
      void interruptSetup(){     
        // Initializes Timer2 to throw an interrupt every 2mS.
        TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
        TCCR2B = 0x06;     // DON'T FORCE COMPARE, 256 PRESCALER 
        OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
        TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
        sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED      
      } 
  
    use the following interrupt vector with Timer2
    
      ISR(TIMER2_COMPA_vect)
      
 >> Timer1
    
    Use of Timer1 interferes with PWM on pins 9 and 10.
    The Servo library also uses Timer1, so if you want servos, use Timer2 above.
    
      void interruptSetup(){     
        // Initializes Timer1 to throw an interrupt every 2mS.
        TCCR1A = 0x00; // DISABLE OUTPUTS AND PWM ON DIGITAL PINS 9 & 10
        TCCR1B = 0x11; // GO INTO 'PHASE AND FREQUENCY CORRECT' MODE, NO PRESCALER
        TCCR1C = 0x00; // DON'T FORCE COMPARE
        TIMSK1 = 0x01; // ENABLE OVERFLOW INTERRUPT (TOIE1)
        ICR1 = 16000;  // TRIGGER TIMER INTERRUPT EVERY 2mS  
        sei();         // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED     
      } 
      
    Use the following ISR vector for the Timer1 setup above
    
      ISR(TIMER1_OVF_vect)
      
 >> Timer0
 
    DON'T USE TIMER0! Timer0 is used for counting delay(), millis(), and micros(). 
                      Messing with Timer0 is highly unadvised!
  
  ******************************************************************************************
  ARDUINO Fio, Lilypad, ProMini328-3V/8MHz (or any board with ATmega328P running at 8MHz)
  
  >> Timer2
  
    Pulse Sensor Arduino UNO uses Timer2 by default.
    Use of Timer2 interferes with PWM on pins 3 and 11.
    There is also a conflict with the Tone library, so if you want tones, use Timer1 below.
    
      void interruptSetup(){     
        // Initializes Timer2 to throw an interrupt every 2mS.
        TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
        TCCR2B = 0x05;     // DON'T FORCE COMPARE, 128 PRESCALER 
        OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
        TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
        sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED      
      } 
  
    use the following interrupt vector with Timer2
    
      ISR(TIMER2_COMPA_vect)
      
 >> Timer1
    
    Use of Timer1 interferes with PWM on pins 9 and 10.
    The Servo library also uses Timer1, so if you want servos, use Timer2 above.
    
      void interruptSetup(){     
        // Initializes Timer1 to throw an interrupt every 2mS.
        TCCR1A = 0x00; // DISABLE OUTPUTS AND PWM ON DIGITAL PINS 9 & 10
        TCCR1B = 0x11; // GO INTO 'PHASE AND FREQUENCY CORRECT' MODE, NO PRESCALER
        TCCR1C = 0x00; // DON'T FORCE COMPARE
        TIMSK1 = 0x01; // ENABLE OVERFLOW INTERRUPT (TOIE1)
        ICR1 = 8000;  // TRIGGER TIMER INTERRUPT EVERY 2mS  
        sei();         // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED     
      } 
      
    Use the following ISR vector for the Timer1 setup above
    
      ISR(TIMER1_OVF_vect)
      
 >> Timer0
 
    DON'T USE TIMER0! Timer0 is used for counting delay(), millis(), and micros(). 
                      Messing with Timer0 is highly unadvised!
  
  
  ******************************************************************************************
  ARDUINO Leonardo (or any board with ATmega32u4 running at 16MHz) 
  
  >> Timer1
  
    Use of Timer1 interferes with PWM on pins 9 and 10.
    
      void interruptSetup(){
          TCCR1A = 0x00;
          TCCR1B = 0x0C; // prescaler = 256
          OCR1A = 0x7C;  // count to 124  
          TIMSK1 = 0x02; 
          sei();
      }
 
  The only other thing you will need is the correct ISR vector in the next step.
       
      ISR(TIMER1_COMPA_vect)
  
  
  ******************************************************************************************
  ADAFRUIT Flora, ARDUINO Fio v3 (or any other board with ATmega32u4 running at 8MHz)
  
  >> Timer1
  
    Use of Timer1 interferes with PWM on pins 9 and 10.
    
      void interruptSetup(){
          TCCR1A = 0x00;
          TCCR1B = 0x0C; // prescaler = 256
          OCR1A = 0x3E;  // count to 62
          TIMSK1 = 0x02; 
          sei();
      }
 
  The only other thing you will need is the correct ISR vector in the next step.
       
      ISR(TIMER1_COMPA_vect)

  ******************************************************************************************
  ADAFRUIT Gemma (or any other board with ATtiny85 running at 8MHz)
  
    NOTE: Gemma does not do serial communication! 
          Comment out or remove the Serial code in the Arduino sketch!
  
  Timer1
  
    Use of Timer1 breaks PWM output on pin D1
    
      void interruptSetup(){     
        TCCR1 = 0x88;      // Clear Timer on Compare, Set Prescaler to 128 TEST VALUE
        GTCCR &= 0x81;     // Disable PWM, don't connect pins to events
        OCR1C = 0x7C;      // Set the top of the count to  124 TEST VALUE
        OCR1A = 0x7C;      // Set the timer to interrupt after counting to TEST VALUE
        bitSet(TIMSK,6);   // Enable interrupt on match between TCNT1 and OCR1A
        sei();             // Enable global interrupts     
      } 

    The only other thing you will need is the correct ISR vector in the next step.
    
      ISR(TIMER1_COMPA_vect)
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
  
  ******************************************************************************************
  
  
  
  
*/


================================================
FILE: PulseSensor_v1/PulseSensor_v1.ino
================================================
// PulseSensor application that streams continuous data at 500Hz using the OpenEEG data format
// over the serial port or a BlueTooth connection

#define LSB(x) (byte)(x    & 0xff);
#define MSB(x) (byte)(x>>8 & 0xff);

#define hasUSB
// #define hasBT

unsigned int value = 0, sample = 0;
unsigned long tsample = 4; // should be 4 to approximate 256 Hz according to protocol

byte buf[17];

void setup() {

  pinMode(A0, INPUT);  // connected to PulseSensor analog output

#ifdef hasUSB
  Serial.begin(57600);  // connected to USB
  unsigned long tic = millis();
  while ((millis() - tic) < 1000 && !(Serial)); // max 1 second
#endif

#ifdef hasBT
  Serial1.begin(57600); // connected to BlueSMiRF
  unsigned long tic = millis();
  while ((millis() - tic) < 1000 && !(Serial1)); // max 1 second
#endif

  // the OpenEEG protocol sends data in 17 byte packets
  buf[ 0] = 0xA5;
  buf[ 1] = 0x5A;
  buf[ 2] = 2; // version
  buf[ 3] = 0; // count
  buf[ 4] = 0; // channel 1
  buf[ 5] = 0;
  buf[ 6] = 0; // channel 2
  buf[ 7] = 0;
  buf[ 8] = 0; // channel 3
  buf[ 9] = 0;
  buf[10] = 0; // channel 4
  buf[11] = 0;
  buf[12] = 0; // channel 5
  buf[13] = 0;
  buf[14] = 0; // channel 6
  buf[15] = 0;
  buf[16] = 0; // switches
}

void loop() {

  // read the analog value
  value = analogRead(A0);

  // scale to appropriate values
  value *= 32;

  // increment the sample number
  sample++;

  // update the data packet
  buf[ 3]++;
  buf[ 4] = MSB(value);  // channel 1, MSB
  buf[ 5] = LSB(value);  // channel 1, LSB
  buf[ 6] = MSB(sample); // channel 2, MSB
  buf[ 7] = LSB(sample); // channel 2, LSB

#ifdef hasUSB
  Serial.flush();
  for (int i = 0; i < 17; i++) {
    Serial.write(buf[i]);
  }
#endif

#ifdef hasBT
  Serial1.flush();
  for (int i = 0; i < 17; i++) {
    Serial1.write(buf[i]);
  }
#endif

  delay(tsample);

}  // loop


================================================
FILE: PulseSensor_v1/README.md
================================================
# Arduino sketch for PulseSensor, see http://pulsesensor.com

This reads the analog value of the sensor and sends it over the serial port in OpenEEG format. This allows the data to be visualized in [any of these software packages](http://openeeg.sourceforge.net/doc/sw/) or in realtime EGE processing software such as [BCI2000](http://www.bci2000.org) or [FieldTrip](http://www.fieldtriptoolbox.org/development/realtime/modulareeg).

The sample timing is relatively inaccurate. It assumes that the code to read the analog value and to write serial output takes negligible time. At the end of each loop there is a delay of 4ms, resulting in approximately 250 Hz sampling rate.


================================================
FILE: PulseSensor_v2/Interrupt.cpp
================================================

volatile int rate[10];                      // array to hold last ten IBI values
volatile unsigned long lastBeatTime = 0;    // used to find IBI
volatile int P = 512;                       // used to find peak in pulse wave, seeded
volatile int T = 512;                       // used to find trough in pulse wave, seeded
volatile int thresh = 525;                  // used to find instant moment of heart beat, seeded
volatile int amp = 100;                     // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true;          // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false;        // used to seed rate array so we startup with reasonable BPM


void interruptSetup() {
  // Initializes Timer2 to throw an interrupt every 2mS.
  TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
  TCCR2B = 0x06;     // DON'T FORCE COMPARE, 256 PRESCALER
  OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
  TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
  sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED
}


// THIS IS THE TIMER 2 INTERRUPT SERVICE ROUTINE.
// Timer 2 makes sure that we take a reading every 2 miliseconds
ISR(TIMER2_COMPA_vect) {                      // triggered when Timer2 counts to 124
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise

  //  find the peak and trough of the pulse wave
  if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI
    if (Signal < T) {                         // T is the trough
      T = Signal;                             // keep track of lowest point in pulse wave
    }
  }

  if (Signal > thresh && Signal > P) {        // thresh condition helps avoid noise
    P = Signal;                               // P is the peak
  }                                           // keep track of highest point in pulse wave

  //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
  // signal surges up in value every time there is a pulse
  if (N > 250) {                                  // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) ) {
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin, HIGH);               // turn on pin 13 LED
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse

      if (secondBeat) {                      // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for (int i = 0; i <= 9; i++) {       // seed the running total to get a realisitic BPM at startup
          rate[i] = IBI;
        }
      }

      if (firstBeat) {                       // if it's the first time we found a beat, if firstBeat == TRUE
        firstBeat = false;                   // clear firstBeat flag
        secondBeat = true;                   // set the second beat flag
        sei();                               // enable interrupts again
        return;                              // IBI value is unreliable so discard it
      }


      // keep a running total of the last 10 IBI values
      word runningTotal = 0;                  // clear the runningTotal variable

      for (int i = 0; i <= 8; i++) {          // shift data in the rate array
        rate[i] = rate[i + 1];                // and drop the oldest IBI value
        runningTotal += rate[i];              // add up the 9 oldest IBI values
      }

      rate[9] = IBI;                          // add the latest IBI to the rate array
      runningTotal += rate[9];                // add the latest IBI to runningTotal
      runningTotal /= 10;                     // average the last 10 IBI values
      BPM = 60000 / runningTotal;             // how many beats can fit into a minute? that's BPM!
      QS = true;                              // set Quantified Self flag
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }
  }

  if (Signal < thresh && Pulse == true) {  // when the values are going down, the beat is over
    digitalWrite(blinkPin, LOW);           // turn off pin 13 LED
    Pulse = false;                         // reset the Pulse flag so we can do it again
    amp = P - T;                           // get amplitude of the pulse wave
    thresh = amp / 2 + T;                  // set thresh at 50% of the amplitude
    P = thresh;                            // reset these for next time
    T = thresh;
  }

  if (N > 2500) {                          // if 2.5 seconds go by without a beat
    thresh = 512;                          // set thresh default
    P = 512;                               // set P default
    T = 512;                               // set T default
    lastBeatTime = sampleCounter;          // bring the lastBeatTime up to date
    firstBeat = true;                      // set these to avoid noise
    secondBeat = false;                    // when we get the heartbeat back
  }

  sei();                                   // enable interrupts when youre done!
}// end isr





================================================
FILE: PulseSensor_v2/PulseSensor_v2.ino
================================================
#define LSB(x) (byte)(x    & 0xff);
#define MSB(x) (byte)(x>>8 & 0xff);

#define pulsePin  A0                // Pulse Sensor purple wire connected to analog pin 0
#define blinkPin  13                // pin to blink led at each beat

// Variables
int TTL = 0;
unsigned long ledTime = 0, flushTime = 0;
unsigned long prevSample = 0;
byte buf[17];

// Volatile Variables, used in the interrupt service routine!
volatile unsigned long sampleCounter = 0;   // used to determine pulse timing
volatile int Signal;                // int that holds raw Analog in 0. This is updated every 2mS
volatile int BPM;                   // int that holds the beats per minute
volatile int IBI = 600;             // int that holds the inter beat interval. Must be seeded!
volatile boolean Pulse = false;     // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile boolean QS = false;        // becomes true when Arduino finds a beat.

void setup() {
  pinMode(pulsePin, INPUT);         // connected to PulseSensor analog output
  pinMode(blinkPin, OUTPUT);        // pin that will blink to your heartbeat!

  Serial.begin(57600);
  unsigned long tic = millis();
  while ((millis() - tic) < 1000 && !(Serial)); // max 1 second

  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS

  // analogReference(EXTERNAL);

  // the OpenEEG protocol sends data in 17 byte packets
  buf[ 0] = 0xA5;
  buf[ 1] = 0x5A;
  buf[ 2] = 2; // version
  buf[ 3] = 0; // count
  buf[ 4] = 0; // channel 1
  buf[ 5] = 0;
  buf[ 6] = 0; // channel 2
  buf[ 7] = 0;
  buf[ 8] = 0; // channel 3
  buf[ 9] = 0;
  buf[10] = 0; // channel 4
  buf[11] = 0;
  buf[12] = 0; // channel 5
  buf[13] = 0;
  buf[14] = 0; // channel 6
  buf[15] = 0;
  buf[16] = 0; // switches
}


void loop() {

  // the processing by the interrupt handler is done every 2 ms, i.e. at 500Hz
  // the OpenEEG data format expects sampling to be at 256 Hz, so we write every 4 ms
  if ((sampleCounter > prevSample) && (sampleCounter % 4) == 0) {
    buf[ 3]++;
    buf[ 4] = MSB(Signal*32);
    buf[ 5] = LSB(Signal*32);
    buf[ 6] = MSB(TTL);
    buf[ 7] = LSB(TTL);
    buf[ 8] = MSB(BPM);
    buf[ 9] = LSB(BPM);
    buf[10] = MSB(IBI);
    buf[11] = LSB(IBI);
    buf[12] = MSB(sampleCounter);
    buf[13] = LSB(sampleCounter);
    prevSample = sampleCounter;
    // Write the packet to the serial port
    for (int i = 0; i < 17; i++) {
      Serial.write(buf[i]);
    }
  }

  if ((millis() - flushTime) > 100) {
    // Flush the serial port now and then
    Serial.flush();
    flushTime = millis();
  }

  if ((millis() - ledTime) > 100) {
    // Switch off the LED and the TTL signal
    analogWrite(blinkPin, 0);
    TTL = 0;
  }

  if (QS == true) {
  // Switch on the LED and the TTL signal
  ledTime = millis();
    analogWrite(blinkPin, 255);
    TTL = 512;
    QS = false;
  }

  // All timing critical code is implemented by means of an interrupt
  // Take a short break in the main loop
  delay(2);
}




================================================
FILE: PulseSensor_v2/README.md
================================================
# Arduino sketch for PulseSensor, see http://pulsesensor.com

This code is derived from the [original code](https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino). It uses the same interrupt driven sampling and processing of the data, but the more fancy features have been removed (ASCII art serial output, fading led). It outputs the data at 250Hz over the serial connection in OpenEEG format with the following channels.

1. contains the continuously sampled value
2. contains a TTL-level representation of heart beat events
3. contains the beats per minute
4. contains the inter beat interval in milliseconds
5. contains the sample time in milliseconds


================================================
FILE: README.md
================================================
# Arduino electronics hacking

Most of the code here corresponds to electronics hardware that is documented on my [personal homepage](http://robertoostenveld.nl/category/arduino/).

The code that I wrote and/or collected in this repository is mostly a mash-up of Arduino code snippets and examples that I found at various places on the internet. There are pieces where I am not even sure if I could be considered the original author. For the pieces of code here in which I invested more substantial (creative) efforts, I am happy to share back to the community without restrictions. So unless otherwise stated, please consider the code here to be in the public domain, i.e. you can do with it what you want. See https://fairuse.stanford.edu/overview/public-domain/welcome/

Please let me know if you see code here for which you consider yourself to be the creator and for which I have not properly credited you, for example through an issue or pull request.

Note that the code for the ESP8266-based ArtNet to DMX512 module that is documented [here](https://robertoostenveld.nl/art-net-to-dmx512-with-esp8266/) is not part of this repository any more but has moved to a stand-alone [esp8266_artnet_dmx512](https://github.com/robertoostenveld/esp8266_artnet_dmx512) repository.


================================================
FILE: bitsi/bitsi.ino
================================================
/*
 * BITSI - Bits to Serial Interface
 *
 * This is an Arduino Uno based serial/usb interface that can be used to monitor responses from
 * button boxes and to send TTL-level stimulus triggers to the parallel input port of EEG or MEG 
 * acquisition systems.
 * 
 * This code originates from https://code.google.com/archive/p/dccn-lab/ where it was released under the GNU GPL v2
 *
 * Copyright (C) 2009-2010 Erik van der Boogert & Bram Daams (for the original implementation)
 * Copyright (C) 2025-2026 Robert Oostenveld (for this cleaned up version)  
 *
 * From version v94, the Bitsi trigger pulse length can be programmed. In theory, the pulse length can be 
 * between 1 and 255 ms. Therefore we send three codes in sequence to the Bitsi: [0], [1], [pulselength]. 
 * By sending [0], we put the Bitsi in programming mode. Sending [1] tells the Bitsi we want to change 
 * the pulse length, and the [pulselength] will be the value we want the trigger pulse to last.
 * 
 * Version v94 also added the level mode to the functionality of the Bitsi. This means that when you 
 * trigger an output, the output will remain 'high', until you send another code or reset the output.
 * The level mode functionality is NOT SUPPORTED by this specific implementation of the Bitsi firmware.
 *  
 */

// Constants
const unsigned long TIMEOUT_DEBOUNCE = 8; // Debounce interval in ms
const int PIN_BUILTIN_LED = 13;

// Input pins configuration
const int INPUT_PINS[8] = {10, 11, 12, 14, 15, 16, 17, 18};

// Output pins configuration
const int OUTPUT_PINS[8] = {2, 3, 4, 5, 6, 7, 8, 9};
unsigned long TIMEOUT_PULSE_LENGTH = 15;  // Pulse length in ms

// Variables
byte serialInChar;                  // To receive desired Parallel_Out value
unsigned long timeNow = 0;          // Current time reference
unsigned long timeOnsetOut = 0;     // When Parallel_Out was set
bool isParallelOutActive = false;   // Whether output pulse is active

// Input state tracking
bool buttonState[8] = {LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
bool prevInputState[8] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};

// Debounce tracking
unsigned long debounceStartTime[8] = {0, 0, 0, 0, 0, 0, 0, 0};
bool isDebouncing[8] = {false, false, false, false, false, false, false, false};

// For initialization of the duration and/or levelmode programming
typedef enum operatingMode {
  NORMAL,
  PROGRAMMING,
  PULSELENGTH,
  LEVELMODE
} operatingMode_t;
operatingMode_t operatingMode;

void setup() {
  // Initialize serial port
  Serial.begin(115200);

  // Set the initial operating mode
  operatingMode = NORMAL;

  // Configure ports
  setupInputPort();
  setupOutputPort();
  writeOutputPort(0);

  // Configure built-in LED
  pinMode(PIN_BUILTIN_LED, OUTPUT);
  digitalWrite(PIN_BUILTIN_LED, LOW);

  // Startup LED sequence
  for (byte i = 0; i < 8; i++) {
    writeOutputPort((B00000001 << (i + 1)) - 1);
    digitalWrite(PIN_BUILTIN_LED, !digitalRead(PIN_BUILTIN_LED));
    delay(100);
  }
  writeOutputPort(0);
  delay(100);

  // Show ready message
  Serial.println("BITSI mode, Ready!");
}

void loop() {
  // Get current time reference
  timeNow = millis();

  // Process serial input
  processSerialInput();

  // Process button pins
  processButtonPins();

  // Handle debounce timeouts
  handleDebounceTimeouts();
}

void processSerialInput() {
  if (Serial.available()) {
    serialInChar = Serial.read();

    // deal with the initialization of the pulse length and/or levelmode programming
    if (operatingMode == NORMAL && serialInChar == 0) {
      operatingMode = PROGRAMMING;
    }
    else if (operatingMode == PROGRAMMING && serialInChar == 1) {
      operatingMode = PULSELENGTH;
    }
    else if (operatingMode == PROGRAMMING && serialInChar == 2) {
      operatingMode = LEVELMODE;
    }
    else if (operatingMode == PULSELENGTH) {
      // the third byte is the pulse duration
      TIMEOUT_PULSE_LENGTH = serialInChar;
      operatingMode = NORMAL;
    }
    else if (operatingMode == LEVELMODE) {
      // the third byte would be the bitmask, but level mode is not supported here
      operatingMode = NORMAL;
    }
    else {
      // output the incoming character as TTL signals
      writeOutputPort(serialInChar);
      timeOnsetOut = timeNow;
      isParallelOutActive = true;
    }
  } 
  else if (isParallelOutActive && ((timeNow - timeOnsetOut) > TIMEOUT_PULSE_LENGTH)) {
    writeOutputPort(0);
    isParallelOutActive = false;
  }
}

void processButtonPins() {
  for (byte i = 0; i < 8; i++) {
    buttonState[i] = digitalRead(INPUT_PINS[i]);
    
    if ((prevInputState[i] != buttonState[i]) && !isDebouncing[i]) {
      // Send corresponding character (A-H for high, a-h for low)
      Serial.print(char(buttonState[i] == HIGH ? (65 + i) : (97 + i)));
      
      // Start debouncing
      debounceStartTime[i] = millis();
      isDebouncing[i] = true;
      prevInputState[i] = buttonState[i];
    }
  }
}

void handleDebounceTimeouts() {
  for (byte i = 0; i < 8; i++) {
    if (isDebouncing[i] && ((timeNow - debounceStartTime[i]) > TIMEOUT_DEBOUNCE)) {
      isDebouncing[i] = false;
    }
  }
}

void setupOutputPort() {
  for (int pin = 0; pin <8; pin++) {
    pinMode(OUTPUT_PINS[pin], OUTPUT);
    digitalWrite(OUTPUT_PINS[pin], LOW);  // Initialize outputs to LOW
  }
}

void setupInputPort() {
  for (int i = 0; i < 8; i++) {
    pinMode(INPUT_PINS[i], INPUT);
    digitalWrite(INPUT_PINS[i], HIGH);  // Enable pull-up resistors
  }
}

byte readInputPort() {
  byte portB = PINB;
  byte portC = PINC;
  return ((portB & B00011100) >> 2) | ((portC & B00011111) << 3);
}

byte readOutputPort() {
  byte portC = PINC;
  byte portD = PIND;
  return ((portD & B11111100) >> 2) | ((portC & B00000011) << 6);
}

void writeOutputPort(byte code) {
  // Calculate new port states
  byte portB = (code & B11000000) >> 6 | (PINB & B11111100);
  byte portD = (code & B00111111) << 2;

  // Update ports
  PORTB = portB;
  PORTD = portD;
}

================================================
FILE: blenderdefender/blenderdefender.ino
================================================
#include <JeeLib.h>
#include <Ports.h>

#define pirPin      9     // the number of the pushbutton pin
#define redLed      5     // the number of the LED pin
#define buttonPin   4     // the number of the pushbutton pin
#define greenLed    3     // the number of the LED pin

//uncomment this line for debugging through the serial interface
// #define BLENDER_DEBUG

#define KAKU_GROUP 1
#define KAKU_ADDR  1

// this is used to save some energy while sleeping
ISR(WDT_vect) { 
  Sleepy::watchdogEvent(); 
}

int active  = LOW;
int armed   = LOW;
int engaged = LOW; 

int buttonState;              // the current value from the input pin
int pirState;                 // the current value from the input pin
int lastButtonState = HIGH;   // the previous value from the input pin
int lastPirState = LOW;       // the previous value from the input pin
int lastSwitchState = -1;    // the last known state of the kaku switch

long buttonDebounceTime = 0;  // the last time the button pin was toggled
long pirDebounceTime = 0;     // the last time the PIR pin was toggled
long lastSwitchTime = 0;

#define debounceDelay      5  // the debounce time; increase if the output flickers
#define armedDelay      5000
#define blinkTime        500

/****************************************************************************************/

void setup() {
#ifdef BLENDER_DEBUG
  Serial.begin(57600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  Serial.print("\n[blenderdefender / ");
  Serial.print(__DATE__);
  Serial.print(" / ");
  Serial.print(__TIME__);
  Serial.println("]");
#endif

  // these should start in the expected value at rest 
  digitalWrite(buttonPin, HIGH);
  digitalWrite(pirPin, LOW);

  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(pirPin, INPUT);
  pinMode(redLed, OUTPUT);
  pinMode(greenLed, OUTPUT);

  // start with the switch off
  kakuSwitch(0);

  // set initial LED state
  ledStatus();
}

/****************************************************************************************/

void ledStatus() {
#ifdef BLENDER_DEBUG
  Serial.print("active = ");
  Serial.print(active);
  Serial.print("\tarmed = ");
  Serial.print(armed);
  Serial.print("\tengaged = ");
  Serial.println(engaged);
  delay(10);
#endif

  if (engaged==HIGH) {
    digitalWrite(redLed,   HIGH);
    digitalWrite(greenLed, HIGH);
  }
  else if (active==LOW) {
    digitalWrite(redLed,   LOW);
    digitalWrite(greenLed, HIGH);
  }
  else if (active==HIGH && armed==LOW) {
    digitalWrite(redLed,   (int)((millis()-buttonDebounceTime)/blinkTime) % 2);
    digitalWrite(greenLed, LOW);
  }
  else if (active==HIGH && armed==HIGH) {
    digitalWrite(redLed,   HIGH);
    digitalWrite(greenLed, LOW);
  }
} // ledStatus

/****************************************************************************************/

void loop() {
  int buttonValue = digitalRead(buttonPin);
  int pirValue    = digitalRead(pirPin);

  if (buttonValue != lastButtonState)
    buttonDebounceTime = millis();

  if (pirValue != lastPirState) 
    pirDebounceTime = millis();

  if ((millis() - buttonDebounceTime) > debounceDelay) {
    if (buttonValue != buttonState) {
      buttonState = buttonValue;
#ifdef BLENDER_DEBUG
      Serial.print("buttonState = ");
      Serial.println(buttonState);
#endif
      if (buttonState == LOW) {
        active  = !active;
        armed   = LOW;
        engaged = LOW;
      }
    }
  }

  if (active && (millis() - buttonDebounceTime) > armedDelay)
    armed = HIGH;

  if ((millis() - pirDebounceTime) > debounceDelay) {
    if (pirValue != pirState) {
#ifdef BLENDER_DEBUG
      Serial.print("pirState = ");
      Serial.println(pirState);
#endif
      pirState = pirValue;
      if ((pirState == HIGH) && active && armed) {
        engaged = HIGH;
      }
      else {
        engaged = LOW;
      }
    }
  }

  // update the LEDs
  ledStatus();

  if (active && armed && !engaged && (millis() - lastSwitchTime) > 60000) {
    // switch the blender off every minute to safe-guard that it remains off most of the time
    lastSwitchTime = millis();
    kakuSwitch(0); 
  }

  if (lastSwitchState!=engaged) {
    // toggle the blender on or off
    kakuSwitch(engaged); 
    lastSwitchState = engaged;
    lastSwitchTime = millis();
  }

  // remember these for the next iteration of the loop
  lastButtonState  = buttonValue;
  lastPirState     = pirValue;

  // spend some time in sleep mode to save energy
  Sleepy::loseSomeTime(20);
} // loop

/****************************************************************************************/

void kakuSwitch(int engaged) {
#ifdef BLENDER_DEBUG
  Serial.print("kakuSwitch ");
#endif
  rf12_initialize(0, RF12_433MHZ, 0);
#ifdef BLENDER_DEBUG
  Serial.println(engaged);
#endif
  // send it twice to improve the robustness
  kakuSend(KAKU_GROUP, KAKU_ADDR, engaged);
  kakuSend(KAKU_GROUP, KAKU_ADDR, engaged);
}

/****************************************************************************************/

// this is from RF12demo
static void kakuSend(char addr, byte device, byte on) {
  int cmd = 0x600 | ((device - 1) << 4) | ((addr - 1) & 0xF);
  if (on)
    cmd |= 0x800;
  for (byte i = 0; i < 4; ++i) {
    for (byte bit = 0; bit < 12; ++bit) {
      ookPulse(375, 1125);
      int on = bitRead(cmd, bit) ? 1125 : 375;
      ookPulse(on, 1500 - on);
    }
    ookPulse(375, 375);
    delay(11); // approximate
  }
}

/****************************************************************************************/

// this is from RF12demo
static void ookPulse(int on, int off) {
  rf12_onOff(1);
  delayMicroseconds(on + 150);
  rf12_onOff(0);
  delayMicroseconds(off - 200);
}




================================================
FILE: digispark_skateboard/README.md
================================================
# Digispark Skateboard

This is an Arduino sketch to implement a strip of WS2812b Neopixels
under a skateboard. It features multiple blinking and moving patterns
of light, which can be selected using three latching pushbuttons,
allowing for in total 8 modes.

It is implemneted using a
[Digispark](https://www.kickstarter.com/projects/digistump/digispark-the-tiny-arduino-enabled-usb-dev-board)
USB development board and two 50 cm long LED strips, each with 30
LEDS. The LED strips are directly powered from a 3.7-4.2 V 18650
LiPo battery, the Digispark is powered using a small DC-DC transformer
module.

## Light modes

One button is used to switch the power on/off, the other three
buttons allow selecting between up to 8 different light modes. The
implementation of the light modes is borrowed from one of my other
projects: which are [ESP8266 powered ArtNet neopixel
modules](https://robertoostenveld.nl/esp-8266-art-net-neopixel-module/).

- static single uniform color
- uniform blinking between two colors
- rotating color wheel with another color background
- rotating HSV rainbow

## Components

- Digispark rev4
- 18650 LiPo battery with builit-in protection
- battery holder
- USB DC-DC transformer with 5V output
- 4 latching switches, 1x for power, 3x for mode selection
- 3 pull-up resistors, few kOhm
- capacitor, 1000uF
- 3-D printed enclosure


================================================
FILE: digispark_skateboard/colorspace.cpp
================================================
#include "colorspace.h"

hsv rgb2hsv(rgb in)
{
  hsv         out;
  double      min, max, delta;

  min = in.r < in.g ? in.r : in.g;
  min = min  < in.b ? min  : in.b;

  max = in.r > in.g ? in.r : in.g;
  max = max  > in.b ? max  : in.b;

  out.v = max;                              // v
  delta = max - min;
  if (delta < 0.00001)
  {
    out.s = 0;
    out.h = 0; // undefined, maybe nan?
    return out;
  }
  if ( max > 0.0 ) { // NOTE: if Max is == 0, this divide would cause a crash
    out.s = (delta / max);                  // s
  } else {
    // if max is 0, then r = g = b = 0
    // s = 0, v is undefined
    out.s = 0.0;
    out.h = 0. / 0.;                        // its now undefined
    return out;
  }
  if ( in.r >= max )                        // > is bogus, just keeps compilor happy
    out.h = ( in.g - in.b ) / delta;        // between yellow & magenta
  else if ( in.g >= max )
    out.h = 2.0 + ( in.b - in.r ) / delta;  // between cyan & yellow
  else
    out.h = 4.0 + ( in.r - in.g ) / delta;  // between magenta & cyan

  out.h *= 60.0;                            // degrees

  if ( out.h < 0.0 )
    out.h += 360.0;

  return out;
}


rgb hsv2rgb(hsv in)
{
  double      hh, p, q, t, ff;
  long        i;
  rgb         out;

  if (in.s <= 0.0) {      // < is bogus, just shuts up warnings
    out.r = in.v;
    out.g = in.v;
    out.b = in.v;
    return out;
  }
  hh = in.h;
  if (hh >= 360.0) hh = 0.0;
  hh /= 60.0;
  i = (long)hh;
  ff = hh - i;
  p = in.v * (1.0 - in.s);
  q = in.v * (1.0 - (in.s * ff));
  t = in.v * (1.0 - (in.s * (1.0 - ff)));

  switch (i) {
    case 0:
      out.r = in.v;
      out.g = t;
      out.b = p;
      break;
    case 1:
      out.r = q;
      out.g = in.v;
      out.b = p;
      break;
    case 2:
      out.r = p;
      out.g = in.v;
      out.b = t;
      break;

    case 3:
      out.r = p;
      out.g = q;
      out.b = in.v;
      break;
    case 4:
      out.r = t;
      out.g = p;
      out.b = in.v;
      break;
    case 5:
    default:
      out.r = in.v;
      out.g = p;
      out.b = q;
      break;
  }
  return out;
}


================================================
FILE: digispark_skateboard/colorspace.h
================================================
#ifndef _COLORSPACE_H_
#define _COLORSPACE_H_

typedef struct {
  double r;       // percent
  double g;       // percent
  double b;       // percent
} rgb;

typedef struct {
  double h;       // angle in degrees
  double s;       // percent
  double v;       // percent
} hsv;

hsv rgb2hsv(rgb);
rgb hsv2rgb(hsv);

#endif


================================================
FILE: digispark_skateboard/digispark_skateboard.ino
================================================
#include <Arduino.h>
#include "neopixel_mode.h"

// the neopixel strip is connected to pin 0
#define BUILTIN_LED 1
#define BUTTON0     2
#define BUTTON1     3
#define BUTTON2     4
#define BRIGHTNESS  128  // can be up to 255

// mode1  details are specified as r, g, b
// mode4  details are specified as r1, g1, b1, r2, g2, b2, speed
// mode10 details are specified as r1, g1, b1, r2, g2, b2, speed
// mode12 details are specified as saturation, value, speed



uint8_t specification0[] = {BRIGHTNESS, BRIGHTNESS, BRIGHTNESS};    // mode1
uint8_t specification1[] = {BRIGHTNESS, 0, 0, 0, BRIGHTNESS, 0, 2}; // mode4
uint8_t specification2[] = {0, BRIGHTNESS, 0, 0, 0, BRIGHTNESS, 2}; // mode4
uint8_t specification3[] = {0, 0, BRIGHTNESS, BRIGHTNESS, 0, 0, 2}; // mode4
uint8_t specification4[] = {BRIGHTNESS, 0, 0, 0, BRIGHTNESS, 0, 2}; // mode10
uint8_t specification5[] = {0, BRIGHTNESS, 0, 0, 0, BRIGHTNESS, 2}; // mode10
uint8_t specification6[] = {0, 0, BRIGHTNESS, BRIGHTNESS, 0, 0, 2}; // mode10
uint8_t specification7[] = {220, BRIGHTNESS, 1};                    // mode12

unsigned long previous = 0, now;

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);
  pinMode(BUTTON0, INPUT);
  pinMode(BUTTON1, INPUT);
  pinMode(BUTTON2, INPUT);
  strip.begin(); // this is declared in neopixel_mode
} // setup

void loop() {
  // blink the builtin LED for diagnostics
  now = millis();
  if ((now - previous) > 1000) {
    digitalWrite(BUILTIN_LED, !digitalRead(BUILTIN_LED));
    previous = now;
  }

  // combine the three buttons in a single binary mode
  uint8_t mode = (digitalRead(BUTTON0) << 0) | (digitalRead(BUTTON1) << 1) | (digitalRead(BUTTON2) << 2);

  // update the Neopixel LED strip according to the current mode
  switch (mode) {
    case 0:
      mode1(specification0);
      break;
    case 1:
      mode4(specification1);
      break;
    case 2:
      mode4(specification2);
      break;
    case 3:
      mode4(specification3);
      break;
    case 4:
      mode10(specification4);
      break;
    case 5:
      mode10(specification5);
      break;
    case 6:
      mode10(specification6);
      break;
    case 7:
      mode12(specification7);
      break;
  }
} // loop


================================================
FILE: digispark_skateboard/neopixel_mode.cpp
================================================
/*
    This is a subset of the color modes supported by my esp8266_artnet_neopixel sketch, see
    https://github.com/robertoostenveld/arduino/tree/master/esp8266_artnet_neopixel

    The functions have the same name and more or less similar functionality, but have been
    simplified to save memory.
*/

#include "neopixel_mode.h"
#include "colorspace.h"

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB);

#define ENABLE_MODE1
#define ENABLE_MODE4
#define ENABLE_MODE10
#define ENABLE_MODE12

/************************************************************************************/

void mode1(uint8_t * data) {
#ifdef ENABLE_MODE1
  int r, g, b;

  r = data[0];
  g = data[1];
  b = data[2];

  for (int pixel = 0; pixel < strip.numPixels(); pixel++)
    strip.setPixelColor(pixel, r, g, b);
  strip.show();
#endif
}

/************************************************************************************/

void mode4(uint8_t * data) {
#ifdef ENABLE_MODE4
  int r, g, b;

  // determine the current phase in the temporal cycle
  float phase = CONFIG_SPEED * data[6] * millis() * 0.360;
  phase = WRAP360(phase);

  // pick between the two colors
  if (phase < 180) {
    r = data[0];
    g = data[1];
    b = data[2];
  }
  else {
    r = data[3];
    g = data[4];
    b = data[5];
  }

  for (int pixel = 0; pixel < strip.numPixels(); pixel++)
    strip.setPixelColor(pixel, r, g, b);
  strip.show();
#endif
}

/************************************************************************************/

void mode10(uint8_t * data) {
#ifdef ENABLE_MODE10
  int r, g, b;

  // determine the current phase in the temporal cycle
  float phase = CONFIG_SPEED * data[6] * millis() * 0.360;

  for (int pixel = 0; pixel < strip.numPixels(); pixel++) {
    float position = (360. * pixel / (strip.numPixels() - 1)) * CONFIG_SPLIT - phase;
    position = WRAP360(position);

    if (position < 180) {
      r = data[0];
      g = data[1];
      b = data[2];
    }
    else {
      r = data[3];
      g = data[4];
      b = data[5];
    }

    strip.setPixelColor(pixel, r, g, b);
  }
  strip.show();
#endif
};

/************************************************************************************/

void mode12(uint8_t * data) {
#ifdef ENABLE_MODE12

  float saturation = 1. * data[0];
  float value      = 1. * data[1] ;
  float speed      = CONFIG_SPEED * data[2];

  // determine the current phase in the temporal cycle
  float phase = (speed * millis()) * 0.360;

  for (int pixel = 0; pixel < strip.numPixels(); pixel++) {
    float hue = (360. * pixel / strip.numPixels()) * CONFIG_SPLIT - phase;
    hue = WRAP360(hue);

    int r = hue;             // hue, between 0-360
    int g = saturation;      // saturation, between 0-255
    int b = value;           // value, between 0-255
    map_hsv_to_rgb(&r, &g, &b);

    strip.setPixelColor(pixel, r, g, b);
  }
  strip.show();
#endif
};

/************************************************************************************/

void map_hsv_to_rgb(int *r, int *g, int *b) {
  hsv in;
  rgb out;
  in.h = 360. * (*r) / 256.;
  in.s =   1. * (*g) / 255.;
  in.v =   1. * (*b) / 255.;
  out = hsv2rgb(in);
  (*r) = out.r * 255;
  (*g) = out.g * 255;
  (*b) = out.b * 255;
}


================================================
FILE: digispark_skateboard/neopixel_mode.h
================================================
#ifndef _NEOPIXEL_MODE_H_
#define _NEOPIXEL_MODE_H_

#include <Adafruit_NeoPixel.h>

#define PIN            0
#define NUMPIXELS      60

// in the original version these can be configured dynamically
#define CONFIG_SPEED   1.0
#define CONFIG_SPLIT   1

#define ROUND(x)   (int(x + 0.5))
#define ABS(x)     (x * (x < 0 ? -1 : 1))
#define MIN(x, y)  (x < y ? x : y)
#define MAX(x, y)  (x > y ? x : y)
#define WRAP360(x) (x > 0 ? (x - int((x)/360)*360) : (x - int((x)/360 - 1)*360))  // between    0 and 360
#define WRAP180(x) (WRAP360(x) < 180 ? WRAP360(x) : WRAP360(x) - 360)             // between -180 and 180
#define BALANCE(l, x1, x2)  ((x1) * (1. - l) + (x2) * l)

extern Adafruit_NeoPixel strip;

// the naming of these modes corresponds to my ESP8266 Artnet Neopixel module
void mode1(uint8_t *);
void mode4(uint8_t *);
void mode10(uint8_t *);
void mode12(uint8_t *);

void map_hsv_to_rgb(int *, int *, int *);

#endif


================================================
FILE: eegsynth_cvgate_mcp4725/README.md
================================================
# Arduino sketch for an MCP4725 digital-to-analog converter

This is a sketch to control Control Voltages (CV) and Gates for an analog synthesizer using an MCP4725 connected to an Arduino board.

See https://robertoostenveld.nl/usb-to-cvgate-converter-schematics-and-bill-of-materials/ for more details.


================================================
FILE: eegsynth_cvgate_mcp4725/eegsynth_cvgate_mcp4725.ino
================================================
/*
* EEGSynth Arduino based CV/Gate controller. This sketch allows
* one to use control voltages and gates to interface a computer
* through an Arduino with an analog synthesizer. The hardware
* comprises an Arduino Nano v3.0 with a MCP4725 12-bit DAC.
* Optionally it can be extended with a number of sample-and-hold
* ICs, a step-up voltage converter and some opamps.
*
* Some example sequences of characters are
*   *c1v1024#  control 1 voltage 5*1024/4095 = 1.25 V
*   *g1v1#     gate 1 value ON
*
* This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
* See http://creativecommons.org/licenses/by-sa/4.0/
*
* Copyright (C) 2015, Robert Oostenveld, http://www.eegsynth.org/
*/

#include <Wire.h>//Include the Wire library to talk I2C

#define voltage1pin    2      // the pin controlling the voltage output
#define voltage2pin    3      // the pin controlling the voltage output
#define voltage3pin    4      // the pin controlling the voltage output
#define voltage4pin    5      // the pin controlling the voltage output
#define gate1pin A0      // the pin controlling the digital gate
#define gate2pin A1      // the pin controlling the digital gate
#define gate3pin A2      // the pin controlling the digital gate
#define gate4pin A3      // the pin controlling the digital gate

#define MCP4726_CMD_WRITEDAC            (0x40)  // Writes data to the DAC
#define MCP4726_CMD_WRITEDACEEPROM      (0x60)  // Writes data to the DAC and the EEPROM (persisting the assigned value after reset)

#define NONE    0
#define VOLTAGE 1
#define GATE    2

#define OK    1
#define ERROR 2

//This is the I2C Address of the MCP4725, by default (A0 pulled to GND).
//Please note that this breakout is for the MCP4725A0.
//For devices with A0 pulled HIGH, use 0x61
#define MCP4725_ADDR 0x60

// these remember the state of all CV and gate outputs
int voltage1 = 0, voltage2 = 0, voltage3 = 0, voltage4 = 0;
int gate1 = 0, gate2 = 0, gate3 = 0, gate4 = 0;
int enable1 = 1, enable2 = 0, enable3 = 0, enable4 = 0;

void sampleAndHold(int pin, uint16_t value) {
  uint8_t msb, lsb;
  msb = (value / 16);
  lsb = (value % 16) << 4;
  Wire.beginTransmission(MCP4725_ADDR);
  Wire.write(MCP4726_CMD_WRITEDAC);   // cmd to update the DAC
  Wire.write(msb);                    // the 8 most significant bits...
  Wire.write(lsb);                    // the 4 least significant bits...
  Wire.endTransmission();
  digitalWrite(pin, HIGH);
  delay(1);                           // give it some time to Sample and Hold
  digitalWrite(pin, LOW);
  return;
}

void setup() {
  // initialize the serial communication:
  Serial.begin(115200);
  Serial.print("\n[eegsynth_cvgate / ");
  Serial.print(__DATE__);
  Serial.print(" / ");
  Serial.print(__TIME__);
  Serial.println("]");
  Serial.setTimeout(1000);

  Wire.begin();

  // initialize the gate pins as output:
  pinMode(gate1pin, OUTPUT);
  pinMode(gate2pin, OUTPUT);
  pinMode(gate3pin, OUTPUT);
  pinMode(gate4pin, OUTPUT);

  // initialize the control voltage pins as output:
  pinMode(voltage1pin, OUTPUT);
  pinMode(voltage2pin, OUTPUT);
  pinMode(voltage3pin, OUTPUT);
  pinMode(voltage4pin, OUTPUT);
}

void loop() {
  byte b, channel = 0, command = NONE, status = OK;
  int value = 0;

  if (Serial.available()) {

    // parse the input over the serial connection
    b = Serial.read();
    if (b == '*') {
      Serial.readBytes(&b, 1);
      if (b == 'c') {
        command = VOLTAGE;
        value = 0;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b - 48) * 1000;
        Serial.readBytes(&b, 1); value += (b - 48) * 100;
        Serial.readBytes(&b, 1); value += (b - 48) * 10;
        Serial.readBytes(&b, 1); value += (b - 48) * 1;
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else if (b == 'g') {
        command = GATE;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b == '1');
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else {
        command = NONE;
      }
    }
    else {
      command = NONE;
    }

    // update the internal state of all output channels
    if (command == VOLTAGE) {
      switch (channel) {
        case 1:
          voltage1 = value;
          status = (enable1 ? OK : ERROR);
          break;
        case 2:
          voltage2 = value;
          status = (enable2 ? OK : ERROR);
          break;
        case 3:
          voltage3 = value;
          status = (enable3 ? OK : ERROR);
          break;
        case 4:
          voltage4 = value;
          status = (enable4 ? OK : ERROR);
          break;
        default:
          status = ERROR;
      }

    }
    else if (command == GATE) {
      switch (channel) {
        case 1:
          gate1 = (value != 0);
          status = OK;
          break;
        case 2:
          gate2 = (value != 0);
          status = OK;
          break;
        case 3:
          gate3 = (value != 0);
          status = OK;
          break;
        case 4:
          gate4 = (value != 0);
          status = OK;
          break;
        default:
          status = ERROR;
      }
    }
    else {
      status = ERROR;
    }
    if (status == OK)
      Serial.println("ok");
    else if (status == ERROR)
      Serial.println("error");
  }
  else {
    // refresh all enabled output channels
    if (enable1) {
      sampleAndHold(voltage1pin, voltage1);
      digitalWrite(gate1pin, gate1);
    }
    if (enable2) {
      sampleAndHold(voltage2pin, voltage2);
      digitalWrite(gate2pin, gate2);
    }
    if (enable3) {
      sampleAndHold(voltage3pin, voltage3);
      digitalWrite(gate3pin, gate3);
    }
    if (enable4) {
      sampleAndHold(voltage4pin, voltage4);
      digitalWrite(gate4pin, gate4);
    }
  }
} //main




================================================
FILE: eegsynth_cvgate_mcp4822/eegsynth_cvgate_mcp4822.ino
================================================
/*
* EEGSynth Arduino based CV/Gate controller. This sketch allows
* one to use control voltages and gates to interface a computer
* through an Arduino with an analog synthesizer. The hardware
* comprises an Arduino Nano v3.0 with one or multiple MCP4822
* 12-bit DACs.
*
* Some example sequences of characters are
*   *c1v1024#  control 1 voltage 5*1024/4095 = 1.25 V
*   *g1v1#     gate 1 value ON
*
* This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
* See http://creativecommons.org/licenses/by-sa/4.0/
*
* Copyright (C) 2015, Robert Oostenveld, http://www.eegsynth.org/
*/

#include <SPI.h>

const int GAIN_1 = 0x1;
const int GAIN_2 = 0x0;

#define voltage12cs     2      // the slave-select pin for channel 1 and 2 on DAC #1
#define voltage34cs     3      // the slave-select pin for channel 3 and 4 on DAC #2
#define gate1pin A0            // the pin controlling the digital gate
#define gate2pin A1            // the pin controlling the digital gate
#define gate3pin A2            // the pin controlling the digital gate
#define gate4pin A3            // the pin controlling the digital gate

#define NONE    0
#define VOLTAGE 1
#define GATE    2

#define OK    1
#define ERROR 2


// these remember the state of all CV and gate outputs
int voltage1 = 0, voltage2 = 0, voltage3 = 0, voltage4 = 0;
int gate1 = 0, gate2 = 0, gate3 = 0, gate4 = 0;
const int enable1 = 1, enable2 = 1, enable3 = 1, enable4 = 1;

void setDacOutput(byte channel, byte gain, byte shutdown, unsigned int val)
{
  byte lowByte = val & 0xff;
  byte highByte = ((val >> 8) & 0xff) | channel << 7 | gain << 5 | shutdown << 4;

  // note that we are not using the default pin 10 for slave select, see further down
  // PORTB &= 0xfb; // toggle pin 10 as Slave Select low
  SPI.transfer(highByte);
  SPI.transfer(lowByte);
  // PORTB |= 0x4; // toggle pin 10 as Slave Select high
}

void setup() {
  // initialize the serial communication:
  Serial.begin(115200);
  Serial.print("\n[eegsynth_cvgate_mcp4822 / ");
  Serial.print(__DATE__);
  Serial.print(" / ");
  Serial.print(__TIME__);
  Serial.println("]");
  Serial.setTimeout(1000);

  SPI.begin();

  // initialize the gate pins as output:
  pinMode(gate1pin, OUTPUT);
  pinMode(gate2pin, OUTPUT);
  pinMode(gate3pin, OUTPUT);
  pinMode(gate4pin, OUTPUT);

  // initialize the control voltage pins as output:
  pinMode(voltage12cs, OUTPUT);
  pinMode(voltage34cs, OUTPUT);

  // default is to disable the communication with all SPI devices
  digitalWrite(voltage12cs, HIGH);
  digitalWrite(voltage34cs, HIGH);
}

void loop() {
  byte b, channel = 0, command = NONE, status = OK;
  int value = 0;

  if (Serial.available()) {

    // parse the input over the serial connection
    b = Serial.read();
    if (b == '*') {
      Serial.readBytes(&b, 1);
      if (b == 'c') {
        command = VOLTAGE;
        value = 0;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b - 48) * 1000;
        Serial.readBytes(&b, 1); value += (b - 48) * 100;
        Serial.readBytes(&b, 1); value += (b - 48) * 10;
        Serial.readBytes(&b, 1); value += (b - 48) * 1;
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else if (b == 'g') {
        command = GATE;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b == '1');
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else {
        command = NONE;
      }
    }
    else {
      command = NONE;
    }

    // update the internal state of all output channels
    if (command == VOLTAGE) {
      switch (channel) {
        case 1:
          voltage1 = value;
          status = (enable1 ? OK : ERROR);
          break;
        case 2:
          voltage2 = value;
          status = (enable2 ? OK : ERROR);
          break;
        case 3:
          voltage3 = value;
          status = (enable3 ? OK : ERROR);
          break;
        case 4:
          voltage4 = value;
          status = (enable4 ? OK : ERROR);
          break;
        default:
          status = ERROR;
      }

    }
    else if (command == GATE) {
      switch (channel) {
        case 1:
          gate1 = (value != 0);
          status = OK;
          break;
        case 2:
          gate2 = (value != 0);
          status = OK;
          break;
        case 3:
          gate3 = (value != 0);
          status = OK;
          break;
        case 4:
          gate4 = (value != 0);
          status = OK;
          break;
        default:
          status = ERROR;
      }
    }
    else {
      status = ERROR;
    }
    if (status == OK)
      Serial.println("ok");
    else if (status == ERROR)
      Serial.println("error");
  }
  else {
    // refresh all enabled output channels
    if (enable1) {
      digitalWrite(voltage12cs, LOW); // enable slave select
      setDacOutput(0, GAIN_2, 1, voltage1);
      digitalWrite(voltage12cs, HIGH); // disable slave select
      digitalWrite(gate1pin, gate1);
    }
    if (enable2) {
      digitalWrite(voltage12cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage2);
      digitalWrite(voltage12cs, HIGH); // disable slave select
      digitalWrite(gate2pin, gate2);
    }
    if (enable3) {
      digitalWrite(voltage34cs, LOW); // enable slave select
      setDacOutput(0, GAIN_2, 1, voltage3);
      digitalWrite(voltage34cs, HIGH); // disable slave select
      digitalWrite(gate3pin, gate3);
    }
    if (enable4) {
      digitalWrite(voltage34cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage4);
      digitalWrite(voltage34cs, HIGH); // disable slave select
      digitalWrite(gate4pin, gate4);
    }
  }
} //main



================================================
FILE: eegsynth_devirtualizer/eegsynth_devirtualizer.ino
================================================
/*
* EEGSynth Arduino based CV/Gate controller. This sketch allows
* one to use control voltages and gates to interface a computer
* through an Arduino with an analog synthesizer. The hardware
* comprises an Arduino Nano v3.0 with one or multiple MCP4822
* 12-bit DACs.
*
* Some example sequences of characters are
*   *c1v1024#  control 1 voltage 5*1024/4095 = 1.25 V
*   *g1v1#     gate 1 value ON
*
* This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
* See http://creativecommons.org/licenses/by-sa/4.0/
*
* Copyright (C) 2015, Robert Oostenveld, http://www.eegsynth.org/
*/

#include <SPI.h>

const int GAIN_1 = 0x1;
const int GAIN_2 = 0x0;

#define voltage12cs   A0      // the slave-select pin for channel 1 and 2 on DAC #1
#define voltage34cs   A1      // the slave-select pin for channel 3 and 4 on DAC #2
#define voltage56cs   A2      // the slave-select pin for channel 1 and 2 on DAC #1
#define voltage78cs   A3      // the slave-select pin for channel 3 and 4 on DAC #2
#define gate1pin 2            // the pin controlling the digital gate
#define gate2pin 3            // the pin controlling the digital gate
#define gate3pin 4            // the pin controlling the digital gate
#define gate4pin 5            // the pin controlling the digital gate
#define gate5pin 6            // the pin controlling the digital gate
#define gate6pin 7            // the pin controlling the digital gate
#define gate7pin 8            // the pin controlling the digital gate
#define gate8pin 9            // the pin controlling the digital gate

#define NONE    0
#define VOLTAGE 1
#define GATE    2

#define OK    1
#define ERROR 2


// these remember the state of all CV and gate outputs
int voltage1 = 0, voltage2 = 0, voltage3 = 0, voltage4 = 0, voltage5, voltage6, voltage7, voltage8;
int gate1 = 0, gate2 = 0, gate3 = 0, gate4 = 0, gate5, gate6, gate7, gate8;
const int enable1 = 1, enable2 = 1, enable3 = 1, enable4 = 1, enable5 = 1, enable6 = 1, enable7 = 1, enable8 = 1;

void setDacOutput(byte channel, byte gain, byte shutdown, unsigned int val)
{
  byte lowByte = val & 0xff;
  byte highByte = ((val >> 8) & 0xff) | channel << 7 | gain << 5 | shutdown << 4;

  // note that we are not using the default pin 10 for slave select, see further down
  // PORTB &= 0xfb; // toggle pin 10 as Slave Select low
  SPI.transfer(highByte);
  SPI.transfer(lowByte);
  // PORTB |= 0x4; // toggle pin 10 as Slave Select high
}

void setup() {
  // initialize the serial communication:
  Serial.begin(115200);
  Serial.print("\n[eegsynth_devirtualizer / ");
  Serial.print(__DATE__);
  Serial.print(" / ");
  Serial.print(__TIME__);
  Serial.println("]");
  Serial.setTimeout(1000);

  SPI.begin();

  // initialize the gate pins as output:
  pinMode(gate1pin, OUTPUT);
  pinMode(gate2pin, OUTPUT);
  pinMode(gate3pin, OUTPUT);
  pinMode(gate4pin, OUTPUT);
  pinMode(gate5pin, OUTPUT);
  pinMode(gate6pin, OUTPUT);
  pinMode(gate7pin, OUTPUT);
  pinMode(gate8pin, OUTPUT);

  // initialize the control voltage pins as output:
  pinMode(voltage12cs, OUTPUT);
  pinMode(voltage34cs, OUTPUT);
  pinMode(voltage56cs, OUTPUT);
  pinMode(voltage78cs, OUTPUT);

  // default is to disable the communication with all SPI devices
  digitalWrite(voltage12cs, HIGH);
  digitalWrite(voltage34cs, HIGH);
  digitalWrite(voltage56cs, HIGH);
  digitalWrite(voltage78cs, HIGH);
}

void loop() {
  byte b, channel = 0, command = NONE, status = OK;
  int value = 0;

  if (Serial.available()) {

    // parse the input over the serial connection
    b = Serial.read();
    if (b == '*') {
      Serial.readBytes(&b, 1);
      if (b == 'c') {
        command = VOLTAGE;
        value = 0;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b - 48) * 1000;
        Serial.readBytes(&b, 1); value += (b - 48) * 100;
        Serial.readBytes(&b, 1); value += (b - 48) * 10;
        Serial.readBytes(&b, 1); value += (b - 48) * 1;
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else if (b == 'g') {
        command = GATE;
        Serial.readBytes(&b, 1); channel = b - 48; // character '1' is ascii value 49
        Serial.readBytes(&b, 1); // 'v'
        Serial.readBytes(&b, 1); value  = (b == '1');
        Serial.readBytes(&b, 1); command = (b == '#' ? command : NONE);
      }
      else {
        command = NONE;
      }
    }
    else {
      command = NONE;
    }

    // update the internal state of all output channels
    if (command == VOLTAGE) {
      switch (channel) {
        case 1:
          voltage1 = value;
          status = (enable1 ? OK : ERROR);
          break;
        case 2:
          voltage2 = value;
          status = (enable2 ? OK : ERROR);
          break;
        case 3:
          voltage3 = value;
          status = (enable3 ? OK : ERROR);
          break;
        case 4:
          voltage4 = value;
          status = (enable4 ? OK : ERROR);
          break;
        case 5:
          voltage5 = value;
          status = (enable5 ? OK : ERROR);
          break;
        case 6:
          voltage6 = value;
          status = (enable6 ? OK : ERROR);
          break;
        case 7:
          voltage7 = value;
          status = (enable7 ? OK : ERROR);
          break;
        case 8:
          voltage8 = value;
          status = (enable8 ? OK : ERROR);
          break;
        default:
          status = ERROR;
      }

    }
    else if (command == GATE) {
      switch (channel) {
        case 1:
          gate1 = (value != 0);
          status = OK;
          break;
        case 2:
          gate2 = (value != 0);
          status = OK;
          break;
        case 3:
          gate3 = (value != 0);
          status = OK;
          break;
        case 4:
          gate4 = (value != 0);
          status = OK;
          break;
        case 5:
          gate5 = (value != 0);
          status = OK;
          break;
        case 6:
          gate6 = (value != 0);
          status = OK;
          break;
        case 7:
          gate7 = (value != 0);
          status = OK;
          break;
        case 8:
          gate8 = (value != 0);
          status = OK;
          break;
        default:
          status = ERROR;
      }
    }
    else {
      status = ERROR;
    }
    if (status == OK)
      Serial.println("ok");
    else if (status == ERROR)
      Serial.println("error");
  }
  else {
    // refresh all enabled output channels
    if (enable1) {
      digitalWrite(voltage12cs, LOW); // enable slave select
      setDacOutput(0, GAIN_2, 1, voltage1);
      digitalWrite(voltage12cs, HIGH); // disable slave select
      digitalWrite(gate1pin, gate1);
    }
    if (enable2) {
      digitalWrite(voltage12cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage2);
      digitalWrite(voltage12cs, HIGH); // disable slave select
      digitalWrite(gate2pin, gate2);
    }
    if (enable3) {
      digitalWrite(voltage34cs, LOW); // enable slave select
      setDacOutput(0, GAIN_2, 1, voltage3);
      digitalWrite(voltage34cs, HIGH); // disable slave select
      digitalWrite(gate3pin, gate3);
    }
    if (enable4) {
      digitalWrite(voltage34cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage4);
      digitalWrite(voltage34cs, HIGH); // disable slave select
      digitalWrite(gate4pin, gate4);
    }
    if (enable5) {
      digitalWrite(voltage56cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage5);
      digitalWrite(voltage56cs, HIGH); // disable slave select
      digitalWrite(gate5pin, gate5);
    }
    if (enable6) {
      digitalWrite(voltage56cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage6);
      digitalWrite(voltage56cs, HIGH); // disable slave select
      digitalWrite(gate6pin, gate6);
    }
    if (enable7) {
      digitalWrite(voltage78cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage7);
      digitalWrite(voltage78cs, HIGH); // disable slave select
      digitalWrite(gate7pin, gate7);
    }
    if (enable8) {
      digitalWrite(voltage78cs, LOW); // enable slave select
      setDacOutput(1, GAIN_2, 1, voltage8);
      digitalWrite(voltage78cs, HIGH); // disable slave select
      digitalWrite(gate8pin, gate8);
    }
  }
} //main



================================================
FILE: eegsynth_usbdmxpro/COPYING.txt
================================================
/*==============================================================================
  Copyright (c) 2013 Soixante circuits

  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.
  ==============================================================================*/



================================================
FILE: eegsynth_usbdmxpro/eegsynth_usbdmxpro.ino
================================================
/*
   The purpose of this sketch is to implement a module that converts from USB to DMX512.
   This allows to use computer software to control stage lighting lamps

   This sketch is partially compatible with the Enttec DMX USB Pro module, but the startup
   sequence of this Arduino sketch is different and other/commercial software is likely not
   to recognize the module as DMX USB Pro .

   Components
   - Arduino Nano or compatible 5V board, e.g. http://ebay.to/2iAeUON
   - MAX485 module, e.g.  http://ebay.to/2iuKQlr
   - 3 or 5 pin female XLR connector

   Wiring scheme
   - connect 3.3V and GND from the Arduino to Vcc and GND of the MAX485 module
   - connect pin DE (data enable) and RE (receive enable) of the MAX485 module to 3.3V
   - connect pin D2 of the Arduino to the DI (data in) pin of the MAX485 module
   - connect pin A to XLR 3
   - connect pin B to XLR 2
   - connect GND   to XLR 1

*/

#include <DmxSimple.h>

#define DI_PIN 2 // data out of the Arduino, data in to the MAX485

// these are defined in https://www.enttec.com/docs/dmx_usb_pro_api_spec.pdf
#define DMX_PRO_START_MSG             126  // 0x7E
#define DMX_PRO_END_MSG               231  // 0xE7

// the packets can have the following labels, i.e. content
#define DMX_PRO_REPROGRAM_FIRMWARE    1
#define DMX_PRO_FLASH_PAGE            2
#define DMX_PRO_GET_WIDGET_PARAM      3
#define DMX_PRO_SET_WIDGET_PARAM      4
#define DMX_PRO_RECEIVED_DMX          5
#define DMX_PRO_SEND_DMX              6
#define DMX_PRO_SEND_RDM              7
#define DMX_PRO_RECEIVE_DMX_ON_CHANGE 8
#define DMX_PRO_RECEIVED_DMX_CHANGE   9
#define DMX_PRO_GET_SERIAL_NUMBER     10
#define DMX_PRO_SENT_DRM_DISCOVERY    11

// each DMX data packet starts with this code
#define DMX_PRO_START_CODE            0

// these are some local states
#define DMX_PRO_SEND_DMX_LSB          240
#define DMX_PRO_SEND_DMX_MSB          241
#define DMX_PRO_SEND_DMX_DATA         242

unsigned char state = DMX_PRO_END_MSG;
unsigned int dataSize;
unsigned int channel;

void setup() {
  DmxSimple.usePin(DI_PIN);
  Serial.begin(57600);
  while (!Serial);
}

void loop() {
  while (!Serial.available())
    yield();

  unsigned char c = Serial.read();

  if (c == DMX_PRO_START_MSG && state != DMX_PRO_END_MSG) {
    state = c; // this should not happen, it means that part of the previous packet was lost
  }
  if (c == DMX_PRO_START_MSG && state == DMX_PRO_END_MSG) {
    state = c;
  }
  else if (c == DMX_PRO_SEND_DMX && state == DMX_PRO_START_MSG) {
    state = c;
  }
  else if (state == DMX_PRO_SEND_DMX) {
    dataSize = c & 0xff;
    state = DMX_PRO_SEND_DMX_LSB;
  }
  else if (state == DMX_PRO_SEND_DMX_LSB) {
    dataSize += (c << 8) & 0xff00;
    state = DMX_PRO_SEND_DMX_MSB;
  }
  else if (state == DMX_PRO_SEND_DMX_MSB && c == DMX_PRO_START_CODE) {
    state = DMX_PRO_SEND_DMX_DATA;
    channel = 1;
  }
  else if (state == DMX_PRO_SEND_DMX_DATA && channel < dataSize) {
    DmxSimple.write(channel, c);
    channel++;
  }
  else if (state == DMX_PRO_SEND_DMX_DATA && channel == dataSize && c == DMX_PRO_END_MSG) {
    state = c;
  }
}


================================================
FILE: esp32_3wd_servo/README.md
================================================
# Three-wheel-drive omni-wheel robot platform

This is an Arduino sketch for an ESP32 (LOLIN32 Lite) controlled
three-wheel-drive omni-wheel robot based on servo motors, similar
to the one documented [here][1] and at various other places online.

The motors are continuous rotation TS90D servo motors, i.e., these
rotate at a given speed, not towards a given location like normal
servo motors.

The control of the robot is implemented using Open Sound Control
(OSC), using the [TouchOSC][5] app on my iPhone. Actually, the
firmware version in this sketch should not (yet) be considered a
robot since there is no autonomous movement, but more like a 
radio-controlled car.

The reason for me **not continuing** the development of this robot
platform is that the servo motors are too noisy for the intended
purpose, and that they stall when running them at a too low speed.
I switched to using 28BYJ-48 stepper motors; the sketch for that
can be found elsewhere in this repository.

[1]: https://github.com/manav20/3-wheel-omni
[2]: https://en.wikipedia.org/wiki/Omni_wheel
[3]: https://www.piscinarobots.nl/robots-y-kits/38mm%20(1.5%20inch)%20double%20plastic%20omni%20wheel%20(compatible%20met%20servo%20motor%20)%20-%2014184
[4]: https://nl.aliexpress.com/item/32478938051.html
[5]: https://hexler.net


================================================
FILE: esp32_3wd_servo/esp32_3wd_servo.ino
================================================
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <OSCMessage.h>   // https://github.com/CNMAT/OSC
#include <ESP32Servo.h>
#include <math.h>

WiFiUDP Udp;
OSCErrorCode error;
Servo servo1, servo2, servo3;

const char *host = "3WD-SERVO";
const char *version = __DATE__ " / " __TIME__;

const unsigned int inPort = 8000;   // local OSC port
const unsigned int outPort = 9000;  // remote OSC port (not used here)

// GPIO connections to the servo control
const int servo1_pin = 25;
const int servo2_pin = 26;
const int servo3_pin = 27;

// published values for the TS90D servo, neutral is 1500 uS
const int minUs = 500;
const int maxUs = 2500;

const float pr = 0.06;            // platform radius, distance from the platform center to each of the wheels, in meter
float vx = 0, vy = 0, vt = 0;     // speed in meter per second, and in radians per second
float x = 0, y = 0, theta = 0;    // absolute position (in meter) and rotation (in radians)
float r1 = 0, r2 = 0, r3 = 0;     // speed of the stepper motors, in steps per second

// don't use the built-in version of map(), as that is only for long int values
// see https://docs.arduino.cc/language-reference/en/functions/math/map/
#define map(value, fromLow, fromHigh, toLow, toHigh) ((toHigh - toLow) * (value - fromLow) / (fromHigh - fromLow) + toLow)

void printCallback(OSCMessage &msg) {
  Serial.print(msg.getAddress());
  Serial.print(" : ");

  for (int i = 0; i < msg.size(); i++) {
    if (msg.isInt(i)) {
      Serial.print(msg.getInt(i));
    } else if (msg.isFloat(i)) {
      Serial.print(msg.getFloat(i));
    } else if (msg.isDouble(i)) {
      Serial.print(msg.getDouble(i));
    } else if (msg.isBoolean(i)) {
      Serial.print(msg.getBoolean(i));
    } else if (msg.isString(i)) {
      char buffer[256];
      msg.getString(i, buffer);
      Serial.print(buffer);
    } else {
      Serial.print("?");
    }

    if (i < (msg.size() - 1)) {
      Serial.print(", ");  // there are more to come
    }
  }
  Serial.println();
}

void accxyzCallback(OSCMessage &msg) {
  vx = msg.getFloat(0);
  vy = msg.getFloat(1);
  vt = 0;
  updateSpeed();
}

void updateSpeed() {
  // convert the speed in world-coordinates into rotation speed of the motors and into servo controls
  // see https://github.com/manav20/3-wheel-omni

  // pr is the platform radius, i.e. the distance of the center of the platform to each wheel 
  // vx, vy, and vt is the speed in world-coordinates
  // r1, r2, and r3 is the rotation speed of the three motors
  // theta is the heading of the robot (FIXME, needs to be integrated over time)

  r1 = -sin(theta) * vx + cos(theta) * vy + pr * vt;
  r2 = -sin(PI / 3 - theta) * vx - cos(PI / 3 - theta) * vy + pr * vt;
  r3 = sin(PI / 3 + theta) * vx - cos(PI / 3 + theta) * vy + pr * vt;

  // convert the rotation speed of the motors into servo control values between 0 and 180, where 90 is neutral
  r1 = map(r1, -1, 1, 0, 180);
  r2 = map(r2, -1, 1, 0, 180);
  r3 = map(r3, -1, 1, 0, 180);

  servo1.write(r1);
  servo2.write(r2);
  servo3.write(r3);

  Serial.print(r1);
  Serial.print(", ");
  Serial.print(r2);
  Serial.print(", ");
  Serial.print(r3);
  Serial.println();
}

void wheel1Callback(OSCMessage &msg) {
  r1 = msg.getFloat(0);
  servo1.write(r1);
}

void wheel2Callback(OSCMessage &msg) {
  r2 = msg.getFloat(0);
  servo2.write(r2);
}

void wheel3Callback(OSCMessage &msg) {
  r3 = msg.getFloat(0);
  servo3.write(r3);
}

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

  Serial.print("[ ");
  Serial.print(host);
  Serial.print(" / ");
  Serial.print(version);
  Serial.println(" ]");

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  WiFi.hostname(host);
  WiFi.begin();

  WiFiManager wifiManager;
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
  wifiManager.autoConnect(host);
  Serial.println("connected");

  // Used for OSC
  Udp.begin(inPort);

  servo1.attach(servo1_pin, minUs, maxUs);
  servo2.attach(servo2_pin, minUs, maxUs);
  servo3.attach(servo3_pin, minUs, maxUs);
}

void loop() {
  OSCMessage msg;
  int size = Udp.parsePacket();

  if (size > 0) {
    while (size--) {
      msg.fill(Udp.read());
    }
    if (!msg.hasError()) {
      msg.dispatch("/accxyz", accxyzCallback);
      msg.dispatch("/wheel1", wheel1Callback);
      msg.dispatch("/wheel2", wheel2Callback);
      msg.dispatch("/wheel3", wheel3Callback);
      msg.dispatch("/*/*/*", printCallback) || msg.dispatch("/*/*", printCallback) || msg.dispatch("/*", printCallback);
    } else {
      error = msg.getError();
      Serial.print("error: ");
      Serial.println(error);
    }
  }
}


================================================
FILE: esp32_3wd_stepper/README.md
================================================
# Three-wheel-drive omni-wheel robot platform

This is an Arduino sketch for an ESP32 (LOLIN32 Lite) controlled three-wheel-drive omni-wheel robot based on 28BYJ-48 stepper motors, similar to the one documented [here][1] and various other places online.

The robot can rotate in-place and move in any direction using [omni wheels][2]. I ordered 38 mm diameter wheels from [Piscinarobots][3], but they are also available on [AliExpress][4].

To position the motors and wheels at 120 degree angles, I designed and 3D-printed a circular base. I also designed and 3D-printed the first set of omni-wheels following the dimensions of the commercial 38 mm wheels, but found that those were too rough and not running sideways smoothly enough. Hence I switched to the commercially fabricated wheels.

The control of the robot is implemented using Open Sound Control (OSC), using the [TouchOSC][5] app on my iPhone. The robot can move by itself along a prespecified list of waypoints. The waypoints are uploaded as tabular CSV file, which also contains a column with the time at which each waypoint is to be reached.

## Coordinate system

This sketch aligns with the convention used by [WPILib][6] and uses an NWU axes convention (North-West-Up as external reference in the world frame.) In the NWU axes convention, the positive X axis points ahead, the positive Y axis points left, and the positive Z axis points up referenced from the floor. When viewed with each positive axis pointing toward you, counter-clockwise (CCW) is a positive value and clockwise (CW) is a negative value.

Considering the 3-wheel-drive robot platform, wheel 1 is placed at the front (the "nose") and wheel 2 and 3 at the left and right back. The x-axis is pointing fron the center to wheel 1, and the y-axix pointing to the left. The angle theta is positive when (seen from the top) the robot turns to the left (CCW) and negative to the right.

## Settings

In the settings menu you can change the following parameters.

### Repeat

This can be 0 (false) or 1 (true) and specifies whether a route should be repeated when the end of the route is reached.

### Absolute

This can be 0 (false) for relative coordinates) or 1 (true) for absolute coordinates.

When configured as relative, you specify the waypoints and movements _relative_ to the robot's position at the moment each route starts. The starting position (and angle) is set to zero and every new route is executed without taking any previously executed routes into consideration.

When configured as absolute, you specify all waypoints relative to the robot's initial position and orientation in the room. WHen starting a new route, the latest known position (and orientation) is taken as the starting point.

### Warp

This is a floating point number by which the time and distances are scaled. By default the warp is 1.00.

If you specify the warp as 2.00, all positions along the route will be multiplied by a factor of 2x and the time between waypoints will be also be multiplied by a factor of 2x. Consequently, the speed for each segment will remain the same, and the overall route will be 2x larger and will take 2x longer.

If you specify the warp as 0.5, the overall route will be 2x shorter and faster

### Serial

This can be 0 (false) or 1 (true) and specifies how much information will be printed on the serial console when the robot is connected to a computer. This is only for debugging.

## Links

[1]: https://github.com/manav20/3-wheel-omni
[2]: https://en.wikipedia.org/wiki/Omni_wheel
[3]: https://www.piscinarobots.nl/robots-y-kits/38mm%20(1.5%20inch)%20double%20plastic%20omni%20wheel%20(compatible%20met%20servo%20motor%20)%20-%2014184
[4]: https://nl.aliexpress.com/item/32478938051.html
[5]: https://hexler.net
[6]: https://docs.wpilib.org/en/stable/docs/software/basic-programming/coordinate-system.html


================================================
FILE: esp32_3wd_stepper/blink_led.cpp
================================================
#include "blink_led.h"

Ticker blinker;

enum {
  LED_ON,
  LED_OFF,
  LED_SLOW,
  LED_MEDIUM,
  LED_FAST,
} ledState;

void changeState() {
  digitalWrite(LED, !(digitalRead(LED)));  // Invert the current state of the LED
}

void ledInit() {
  pinMode(LED, OUTPUT);
}

void ledOn() {
  if (ledState != LED_ON) {
    ledState = LED_ON;
    blinker.detach();
    digitalWrite(LED, LOW);
  }
}

void ledOff() {
  if (ledState != LED_OFF) {
    ledState = LED_OFF;
    blinker.detach();
    digitalWrite(LED, HIGH);
  }
}

void ledSlow() {
  if (ledState != LED_SLOW) {
    ledState = LED_SLOW;
    blinker.detach();
    blinker.attach_ms(1000, changeState);
  }
}

void ledMedium() {
  if (ledState != LED_MEDIUM) {
    ledState = LED_MEDIUM;
    blinker.detach();
    blinker.attach_ms(250, changeState);
  }
}

void ledFast() {
  if (ledState != LED_FAST) {
    ledState = LED_FAST;
    blinker.detach();
    blinker.attach_ms(100, changeState);
  }
}


================================================
FILE: esp32_3wd_stepper/blink_led.h
================================================
#ifndef _BLINK_LED_H_
#define _BLINK_LED_H_

#include <Arduino.h>
#include <Ticker.h>

#define LED 22 // GPIO22 is connected to the builtin LED on the Lolin32 Lite

void ledInit(void);
void ledOn(void);
void ledOff(void);
void ledSlow(void);
void ledMedium(void);
void ledFast(void);

#endif


================================================
FILE: esp32_3wd_stepper/data/config.json
================================================
{
"repeat":0,
"absolute":0,
"warp": 1,
"debug":0
}


================================================
FILE: esp32_3wd_stepper/data/hello.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Hello</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Hello tortoise!</p>
<img src="tortoise.jpg">
</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Index</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<a href="/hello.html">Hello</a></br>
<a href="/monitor.html">Monitor</a></br>
<a href="/settings.html">Change settings</a></br>
<a href="/waypoints.html">Edit waypoints</a></br>
<a href="/reconnect">Reconnect WiFi</a></br>
<a href="/defaults">Default settings</a></br>
<a href="/restart">Restart hardware</a></br>
</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/monitor.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Monitor</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>

<h1>Monitor</h1>

Uptime:
<div id="uptime" name"uptime">?</div>

Firmware version:
<div id="version" name="version">?</div>

MAC address:
<div id="macaddress" name"macaddress">?</div>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("uptime").innerHTML = data["uptime"];
    document.getElementById("version").innerHTML = data["version"];
    document.getElementById("macaddress").innerHTML = data["macaddress"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/reload_failure.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Failure</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Failure</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/reload_success.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Success</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Success</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/settings.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Settings</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<h1>Settings</h1>

<form id="settings-form" method="post" action="json">
    <div class="field">
        <label for="repeat">repeat:</label><br>
        <input type="text" id="repeat" name="repeat" value="?" required>
    </div>

    <div class="field">
      <label for="absolute">absolute:</label><br>
      <input type="text" id="absolute" name="absolute" value="?" required>
    </div>

    <div class="field">
      <label for="warp">warp:</label><br>
      <input type="text" id="warp" name="warp" value="?" required>
    </div>

    <div class="field">
      <label for="debug">debug:</label><br>
      <input type="text" id="debug" name="debug" value="?" required>
    </div>

    <div class="field">
        <button type="submit">Save</button>
    </div>
</form>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("repeat").value = data["repeat"];
    document.getElementById("absolute").value = data["absolute"];
    document.getElementById("warp").value = data["warp"];
    document.getElementById("debug").value = data["debug"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/style.css
================================================
.c{
    text-align: center;
}
div,input{
    padding:5px;font-size:1em;
}
input{
    width:95%;
}
body{
    text-align: center;font-family:verdana;
}
button{
    border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;
}
.q{
    float: right;width: 64px;text-align: right;
}
.l{
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==") no-repeat left center;background-size: 1em;
}


================================================
FILE: esp32_3wd_stepper/data/waypoints.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Edit waypoints</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<h1>Edit waypoints</h1>

<form id="waypoints-form" method="post" action="json">
    <div class="field">
      <label for="waypoints1">waypoints route 1:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints1" name="waypoints1" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints2">waypoints route 2:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints2" name="waypoints2" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints3">waypoints route 3:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints3" name="waypoints3" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints4">waypoints route 4:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints4" name="waypoints4" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints5">waypoints route 5:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints5" name="waypoints5" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints6">waypoints route 6:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints6" name="waypoints6" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints7">waypoints route 7:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints7" name="waypoints7" required>?</textarea>
    </div>

    <div class="field">
      <label for="waypoints8">waypoints route 8:</label><br>
      <textarea type="text" rows="10" cols="80" id="waypoints8" name="waypoints8" required>?</textarea>
    </div>

    <div class="field">
        <button type="submit">Save</button>
    </div>
</form>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("waypoints1").value = data["waypoints1"];
    document.getElementById("waypoints2").value = data["waypoints2"];
    document.getElementById("waypoints3").value = data["waypoints3"];
    document.getElementById("waypoints4").value = data["waypoints4"];
    document.getElementById("waypoints5").value = data["waypoints5"];
    document.getElementById("waypoints6").value = data["waypoints6"];
    document.getElementById("waypoints7").value = data["waypoints7"];
    document.getElementById("waypoints8").value = data["waypoints8"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_3wd_stepper/data/waypoints1.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints2.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints3.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints4.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints5.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints6.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints7.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/data/waypoints8.csv
================================================
0,0,0,0


================================================
FILE: esp32_3wd_stepper/esp32_3wd_stepper.ino
================================================
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiManager.h>        // https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <ESPmDNS.h>
#include <math.h>

#include "webinterface.h"
#include "waypoints.h"
#include "parseosc.h"
#include "stepper.h"
#include "blink_led.h"
#include "util.h"

const char *host = "3WD-STEPPER";
const char *version = __DATE__ " / " __TIME__;

WebServer server(80);
WiFiUDP Udp;

Stepper wheel1;
Stepper wheel2;
Stepper wheel3;

const unsigned int inPort = 8000;   // local OSC port for receiving commands
const float pd = 0.084252;          // platform diameter, in meter
const float wd = 0.038;             // wheel diameter, in meter
const float pc = pd * M_PI;         // platform circumference, in meter
const float wc = wd * M_PI;         // wheel circumference, in meter
const float totalsteps = 2048;      // steps per revolution, see http://www.mjblythe.com/hacks/2016/09/28byj-48-stepper-motor/
const float maxsteps = 512;         // maximum steps per seconds, determined experimentally

unsigned long previous = 0;         // timer to integrate the speed over time
unsigned long feedback = 0;         // timer for feedback on the serial console

float x = 0, y = 0, a = 0;          // absolute position (in meter) and angle (in radians)
float vx = 0, vy = 0, va = 0;       // speed in meter per second, and angular speed in radians per second
float r1 = 0, r2 = 0, r3 = 0;       // speed of the stepper motors, in steps per second

float target_x = 0, target_y = 0, target_a = 0;
unsigned long route_starttime = 4294967295;
unsigned long target_eta = 4294967295;

// when there is an active route, the speed is determined by the distance to the target waypoint
// when there is no route, the speed is determined manually by the user
int current_route = -1;
int current_segment = -1;
bool route_pause = false;

/********************************************************************************/

void startRoute(int route) {
  unsigned long now = millis();

  // stop the current movement
  vx = 0;
  vy = 0;
  va = 0;

  if (!config.absolute) {
    // set the current position and orientation as zero
    x = 0;
    y = 0;
    a = 0;
  }

  // read the waypoints from the SPIFFS filesystem
  parseWaypoints(route);

  // insert the current position and orientation as the starting point
  waypoints_t.insert(waypoints_t.begin(), 0);
  waypoints_x.insert(waypoints_x.begin(), x);
  waypoints_y.insert(waypoints_y.begin(), y);
  waypoints_a.insert(waypoints_a.begin(), a);

  // start the new route
  route_starttime = now;
  current_route = route;
  route_pause = false;

  if (config.debug)
    printWaypoints(route);
} // startRoute

/********************************************************************************/

void pauseRoute() {
  // pause the current route
  vx = 0;
  vy = 0;
  va = 0;
  route_pause = true;
} // pauseRoute

/********************************************************************************/

void resumeRoute() {
  // resume the current route
  route_pause = false;
} // resumeRoute

/********************************************************************************/

void stopRoute() {
  // stop the current route
  vx = 0;
  vy = 0;
  va = 0;
  current_route = -1;
  route_starttime = 4294967295;
  target_eta = 4294967295;
  route_pause = false;
} // stopRoute

/********************************************************************************/

void resetRoute() {
  // stop the current route and reset the current position to zero
  stopRoute();
  x = 0;
  y = 0;
  a = 0;
} // resetRoute

/********************************************************************************/

void updatePosition() {
  unsigned long now = millis();
  if (route_pause) {
    // shift the time so that we remain in the same state
    // this is as if the time has stopped
    route_starttime += (now - previous);
    target_eta += (now - previous);
  }
  else {
    // integrate the speed over time to keep track of the current position and angle
    x += vx * (now - previous) / 1000;
    y += vy * (now - previous) / 1000;
    a += va * (now - previous) / 1000;
  }
  previous = now;
}  // updatePosition

/********************************************************************************/

void updateTarget() {
  unsigned long now = millis();

  if (current_route < 0) {
    // when there is no route, the speed is determined manually by the user
    // hence the current position is as good a target as anywhere else
    target_x = x;
    target_y = y;
    target_a = a;
    target_eta = 4294967295;
  }
  else {
    // when there is an active route, the speed is determined by the distance to the target waypoint
    // determine the segment or leg that we are currently on
    current_segment = -1;

    int n = waypoints_t.size();
    unsigned long route_endtime = route_starttime + 1000 * waypoints_t.at(n - 1);

    if (now < route_starttime) {
      // the route has not yet started
      // stay where we are
      target_x = x;
      target_y = y;
      target_a = a;
      target_eta = 4294967295;
    }
    else if (now >= route_endtime) {
      // the route has finished
      if (config.repeat) {
        // start the same route again
        startRoute(current_route);
      }
      else {
        // stop the route
        current_route = -1;
        vx = 0;
        vy = 0;
        va = 0;
        // stay where we are
        target_x = x;
        target_y = y;
        target_a = a;
        target_eta = 4294967295;
      }
    }
    else {
      // determine the segment or leg that we are currently on
      // the N waypoints are connected by N-1 segments
      for (int i = 0; i < (n - 1); i++) {
        unsigned long segment_starttime = route_starttime + 1000 * waypoints_t.at(i);
        unsigned long segment_endtime   = route_starttime + 1000 * waypoints_t.at(i + 1);
        if (now >= segment_starttime && now < segment_endtime) {
          current_segment = i + 1;
          target_x = waypoints_x.at(i + 1);
          target_y = waypoints_y.at(i + 1);
          target_a = waypoints_a.at(i + 1);
          target_eta = segment_endtime;
          break;
        }
      } // for all segments
    } // if route_starttime
  } // if current_route

} // updateTarget

/********************************************************************************/

void updateSpeed() {
  unsigned long now = millis();

  if (current_route < 0) {
    // when there is no route, the speed is determined manually by the user
  }
  else if (route_pause) {
    // don't move if the route is paused
    vx = 0;
    vy = 0;
    va = 0;
  }
  else {
    // when there is an active route, the speed is determined by the distance to the target waypoint
    if (current_segment > 0) {
      // determine the speed needed to reach the target at the desired time
      float dt = 0.001 * (target_eta - now); // in seconds
      if (dt > 0) {
        vx = (target_x - x) / dt;
        vy = (target_y - y) / dt;
        va = (target_a - a) / dt;
      }
      else {
        // infinite or negative speed is not allowed
        vx = 0;
        vy = 0;
        va = 0;
      }
    }
    else {
      // the current segment could not be determined
      vx = 0;
      vy = 0;
      va = 0;
    } // if current_segment
  } // if current_route

} // updateSpeed

/********************************************************************************/

void updateWheels() {
  // convert the speed from world-coordinates into the rotation speed of the motors
  r1 = sin(a               ) * vx / wc - cos(a               ) * vy / wc - va * (pc / wc) / (M_PI * 2);
  r2 = sin(a + M_PI * 2 / 3) * vx / wc - cos(a + M_PI * 2 / 3) * vy / wc - va * (pc / wc) / (M_PI * 2);
  r3 = sin(a - M_PI * 2 / 3) * vx / wc - cos(a - M_PI * 2 / 3) * vy / wc - va * (pc / wc) / (M_PI * 2);

  // convert from rotations per second into steps per second
  r1 *= totalsteps;
  r2 *= totalsteps;
  r3 *= totalsteps;

  // the stepper motors cannot rotate at more than ~512 steps/second
  float r = maxOfThree(abs(r1), abs(r2), abs(r3));
  if (r > maxsteps) {
    float reduction = r / maxsteps;
    Serial.print("reducing speed by factor ");
    Serial.println(reduction);
    vx /= reduction;
    vy /= reduction;
    va /= reduction;
    r1 /= reduction;
    r2 /= reduction;
    r3 /= reduction;
  }

  if (r1 == 0 && r2 == 0 && r3 == 0) {
    // save energy by switching the motors off
    wheel1.powerOff();
    wheel2.powerOff();
    wheel3.powerOff();
    ledOn();
  }
  else {
    wheel1.powerOn();
    wheel2.powerOn();
    wheel3.powerOn();
    ledSlow();
  }

  // set the motor speed in steps per seconds
  wheel1.spin(r1);
  wheel2.spin(r2);
  wheel3.spin(r3);
}  // updateWheels

/********************************************************************************/

void printDebug() {
  unsigned long now = millis();

  // do not print more often than once every 500ms
  if ((now - feedback) > 500) {

    Serial.print("current = ");
    Serial.print(current_route);
    Serial.print(", ");
    Serial.print(current_segment);

    Serial.print(", time = ");
    Serial.print(now);
    Serial.print(", ");
    Serial.print(target_eta);

    Serial.print(", position = ");
    Serial.print(x);
    Serial.print(", ");
    Serial.print(y);
    Serial.print(", ");
    Serial.print(a * 180 / M_PI); // in degrees

    Serial.print(", target = ");
    Serial.print(target_x);
    Serial.print(", ");
    Serial.print(target_y);
    Serial.print(", ");
    Serial.print(target_a * 180 / M_PI); // in degrees

    Serial.print(", speed = ");
    Serial.print(vx);
    Serial.print(", ");
    Serial.print(vy);
    Serial.print(", ");
    Serial.print(va * 180 / M_PI); // in degrees per second

    Serial.print(", wheel speed = ");
    Serial.print(r1);
    Serial.print(", ");
    Serial.print(r2);
    Serial.print(", ");
    Serial.print(r3);

    Serial.println();
    feedback = now;
  }
}
/********************************************************************************/

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

  Serial.print("[ ");
  Serial.print(host);
  Serial.print(" / ");
  Serial.print(version);
  Serial.println(" ]");

  ledInit();
  ledFast();

  // The SPIFFS file system contains the html and javascript code for the web interface, and the "config.json" file
  if (!SPIFFS.begin()) {
    Serial.println("Error opening file system");
    while (true);
  }
  else {
    Serial.println("Opened file system");
    loadConfig();
    printConfig();
  }

  WiFi.hostname(host);
  WiFi.begin();

  printMacAddress();

  WiFiManager wifiManager;
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
  wifiManager.autoConnect(host);
  Serial.println("Connected to WiFi");

  // incoming port for OSC messages
  Udp.begin(inPort);

  // this serves all URIs that can be resolved to a file on the SPIFFS filesystem
  server.onNotFound(handleNotFound);

  server.on("/", HTTP_GET, []() {
    handleRedirect("/index.html");
  });

  server.on("/defaults", HTTP_GET, []() {
    Serial.println("handleDefaults");
    handleStaticFile("/reload_success.html");
    defaultConfig();
    printConfig();
    saveConfig();
    server.close();
    server.stop();
    ESP.restart();
  });

  server.on("/reconnect", HTTP_GET, []() {
    Serial.println("handleReconnect");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    delay(5000);
    WiFiManager wifiManager;
    wifiManager.resetSettings();
    wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
    wifiManager.startConfigPortal(host);
    server.begin();
    if (WiFi.status() == WL_CONNECTED)
      Serial.println("WiFi status ok");
  });

  server.on("/restart", HTTP_GET, []() {
    Serial.println("handleRestart");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    SPIFFS.end();
    delay(5000);
    ESP.restart();
  });

  server.on("/json", HTTP_PUT, handleJSON);

  server.on("/json", HTTP_POST, handleJSON);

  server.on("/json", HTTP_GET, [] {
    JsonDocument root;
    N_CONFIG_TO_JSON(repeat, "repeat");
    N_CONFIG_TO_JSON(absolute, "absolute");
    N_CONFIG_TO_JSON(warp, "warp");
    N_CONFIG_TO_JSON(debug, "debug");
    root["version"] = version;
    root["uptime"] = long(millis() / 1000);
    root["macaddress"] = getMacAddress();
    root["waypoints1"] = loadWaypoints(1);
    root["waypoints2"] = loadWaypoints(2);
    root["waypoints3"] = loadWaypoints(3);
    root["waypoints4"] = loadWaypoints(4);
    root["waypoints5"] = loadWaypoints(5);
    root["waypoints6"] = loadWaypoints(6);
    root["waypoints7"] = loadWaypoints(7);
    root["waypoints8"] = loadWaypoints(8);
    String str;
    serializeJson(root, str);
    server.setContentLength(str.length());
    server.send(200, "application/json", str);
  });

  // start the web server
  server.begin();

  // announce the hostname and web server through zeroconf
  MDNS.begin(host);
  MDNS.addService("http", "tcp", 80);

  // connect the timer to the stepper motor driver pins
  wheel1.begin(14, 27, 26, 25);
  wheel2.begin(13, 15, 2, 4);
  wheel3.begin(16, 17, 5, 18);

  Serial.println("Setup done");
} // setup

/********************************************************************************/

void loop() {
  updatePosition();       // update the current location
  updateTarget();         // update the target location
  updateSpeed();          // update the world speed
  updateWheels();         // update the speed of the wheels

  if (config.debug)
    printDebug();

  parseOSC();             // parse the incoming OSC messages
  server.handleClient();  // handle webserver requests, note that this may disrupt other processes
} // loop


================================================
FILE: esp32_3wd_stepper/parseosc.cpp
================================================
#include "parseosc.h"

/********************************************************************************/

void parseOSC() {
  int size = Udp.parsePacket();

  if (size > 0) {
    OSCMessage msg;
    OSCBundle bundle;
    OSCErrorCode error;

    char c = Udp.peek();
    if (c == '#') {
      // it is a bundle, these are sent by TouchDesigner
      while (size--) {
        bundle.fill(Udp.read());
      }
      if (!bundle.hasError()) {
        bundle.dispatch("/*", printCallback) || bundle.dispatch("/*/*", printCallback) || bundle.dispatch("/*/*/*", printCallback);
        bundle.dispatch("/route/1",     route1Callback);
        bundle.dispatch("/route/2",     route2Callback);
        bundle.dispatch("/route/3",     route3Callback);
        bundle.dispatch("/route/4",     route4Callback);
        bundle.dispatch("/route/5",     route5Callback);
        bundle.dispatch("/route/6",     route6Callback);
        bundle.dispatch("/route/7",     route7Callback);
        bundle.dispatch("/route/8",     route8Callback);
        bundle.dispatch("/pause",       pauseCallback);
        bundle.dispatch("/resume",      resumeCallback);
        bundle.dispatch("/stop",        stopCallback);
        bundle.dispatch("/reset",       resetCallback);
        bundle.dispatch("/adjust/x",    dxCallback);
        bundle.dispatch("/adjust/y",    dyCallback);
        bundle.dispatch("/adjust/a",    daCallback);
        bundle.dispatch("/manual/x",    xCallback);
        bundle.dispatch("/manual/y",    yCallback);
        bundle.dispatch("/manual/a",    aCallback);
        bundle.dispatch("/manual/xy/1", xyCallback);      // this is for the 2D panel in touchOSC
        bundle.dispatch("/accxyz",      accxyzCallback);  // this is for the accelerometer in touchOSC
      } else {
        error = bundle.getError();
        Serial.print("error: ");
        Serial.println(error);
      }
    } else if (c == '/') {
      // it is a message, these are sent by touchOSC
      while (size--) {
        msg.fill(Udp.read());
      }
      if (!msg.hasError()) {
        msg.dispatch("/*", printCallback) || msg.dispatch("/*/*", printCallback) || msg.dispatch("/*/*/*", printCallback);
        msg.dispatch("/route/1",     route1Callback);
        msg.dispatch("/route/2",     route2Callback);
        msg.dispatch("/route/3",     route3Callback);
        msg.dispatch("/route/4",     route4Callback);
        msg.dispatch("/route/5",     route5Callback);
        msg.dispatch("/route/6",     route6Callback);
        msg.dispatch("/route/7",     route7Callback);
        msg.dispatch("/route/8",     route8Callback);
        msg.dispatch("/pause",       pauseCallback);
        msg.dispatch("/resume",      resumeCallback);
        msg.dispatch("/stop",        stopCallback);
        msg.dispatch("/reset",       resetCallback);
        msg.dispatch("/adjust/x",    dxCallback);
        msg.dispatch("/adjust/y",    dyCallback);
        msg.dispatch("/adjust/a",    daCallback);
        msg.dispatch("/manual/x",    xCallback);
        msg.dispatch("/manual/y",    yCallback);
        msg.dispatch("/manual/a",    aCallback);
        msg.dispatch("/manual/xy/1", xyCallback);      // this is for the 2D panel in touchOSC
        msg.dispatch("/accxyz",      accxyzCallback);  // this is for the accelerometer in touchOSC
      } else {
        error = msg.getError();
        Serial.print("error: ");
        Serial.println(error);
      }
    }
  }  // if size
}  // parseOSC

/********************************************************************************/

void printCallback(OSCMessage &msg) {
  Serial.print(msg.getAddress());
  Serial.print(" : ");

  for (int i = 0; i < msg.size(); i++) {
    if (msg.isInt(i)) {
      Serial.print(msg.getInt(i));
    } else if (msg.isFloat(i)) {
      Serial.print(msg.getFloat(i));
    } else if (msg.isDouble(i)) {
      Serial.print(msg.getDouble(i));
    } else if (msg.isBoolean(i)) {
      Serial.print(msg.getBoolean(i));
    } else if (msg.isString(i)) {
      char buffer[256];
      msg.getString(i, buffer);
      Serial.print(buffer);
    } else {
      Serial.print("?");
    }

    if (i < (msg.size() - 1)) {
      Serial.print(", ");  // there are more to come
    }
  }
  Serial.println();
}

/********************************************************************************/

void route1Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(1);
}

void route2Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(2);
}

void route3Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(3);
}

void route4Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(4);
}

void route5Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(5);
}

void route6Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(6);
}

void route7Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(7);
}

void route8Callback(OSCMessage &msg) {
  if (msg.getInt(0))
    startRoute(8);
}

void pauseCallback(OSCMessage &msg) {
  if (msg.getInt(0))
    pauseRoute();
}

void resumeCallback(OSCMessage &msg) {
  if (msg.getInt(0))
    resumeRoute();
}

void stopCallback(OSCMessage &msg) {
  if (msg.getInt(0))
    stopRoute();
}

void resetCallback(OSCMessage & msg) {
  if (msg.getInt(0))
    resetRoute();
}

void xCallback(OSCMessage & msg) {
  vx = msg.getFloat(0);
}

void yCallback(OSCMessage & msg) {
  vy = msg.getFloat(0);
}

void aCallback(OSCMessage & msg) {
  va = msg.getFloat(0);
}

void dxCallback(OSCMessage & msg) {
  // adjust the current position with 1 cm
  // the negative adjustment will cause the robot to move towards the positive side
  x -= msg.getFloat(0) * 0.01;
}

void dyCallback(OSCMessage & msg) {
  // adjust the current position with 1 cm
  // the negative adjustment will cause the robot to move towards the positive side
  y -= msg.getFloat(0) * 0.01;
}

void daCallback(OSCMessage & msg) {
  // adjust the current angle with 5 degrees (in radians)
  // the negative adjustment will cause the robot to move towards a positive angle
  a -= msg.getFloat(0) * 5.00 * PI / 180;
}

void xyCallback(OSCMessage & msg) {
  vx = msg.getFloat(0);
  vy = msg.getFloat(1);
}

void accxyzCallback(OSCMessage & msg) {
  // values are between -1 and 1, scale them to a more appropriate speed of 3 cm/s
  vx = msg.getFloat(0) * 0.03;
  vy = msg.getFloat(1) * 0.03;
}


================================================
FILE: esp32_3wd_stepper/parseosc.h
================================================
#ifndef _PARSEOSC_H_
#define _PARSEOSC_H_

#include <WiFiUdp.h>
#include <OSCMessage.h>         // https://github.com/CNMAT/OSC
#include <OSCBundle.h>

// these are defined in the main sketch
extern float vx, vy, va;
extern float x, y, a;
extern WiFiUDP Udp;
void startRoute(int);
void pauseRoute();
void resumeRoute();
void stopRoute();
void resetRoute();

// these are defined in the corresponding CPP file
void parseOSC();
void printCallback(OSCMessage &);
void route1Callback(OSCMessage &);
void route2Callback(OSCMessage &);
void route3Callback(OSCMessage &);
void route4Callback(OSCMessage &);
void route5Callback(OSCMessage &);
void route6Callback(OSCMessage &);
void route7Callback(OSCMessage &);
void route8Callback(OSCMessage &);
void pauseCallback(OSCMessage &);
void resumeCallback(OSCMessage &);
void stopCallback(OSCMessage &);
void resetCallback(OSCMessage &);
void xCallback(OSCMessage &);
void yCallback(OSCMessage &);
void aCallback(OSCMessage &);
void dxCallback(OSCMessage &);
void dyCallback(OSCMessage &);
void daCallback(OSCMessage &);
void xyCallback(OSCMessage &);
void accxyzCallback(OSCMessage &);

#endif


================================================
FILE: esp32_3wd_stepper/stepper.cpp
================================================
#include "stepper.h"

// This implements the ESP32 control for the 28BYJ-48 motor and the ULN2003 driver board.
//
// It is specifically written to keep up to four stepper motors spinning at a constant speed
// and it uses ESP32 hardware timers for precise timing.
//
// The https://docs.arduino.cc/libraries/stepper/ was not convenient to set and 
// keep the stepper motor runnning at a specific speed.
//
// The https://github.com/bblanchon/ArduinoContinuousStepper library was not 
// precise enough to simultaneously control three stepper motors and to keep 
// my 3-wheel robot platform with omni wheels going straight.
//
// See https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/timer.html

/******************************************************************************/

Stepper::Stepper() {
  // constructor
}

/******************************************************************************/

void Stepper::begin(unsigned int _in1, int _in2, int _in3, int _in4) {
  in1 = _in1;
  in2 = _in2;
  in3 = _in3;
  in4 = _in4;

  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(in3, OUTPUT);
  pinMode(in4, OUTPUT);

  timer = timerBegin(frequency);
  timerAttachInterruptArg(timer, &onTimer, (void*)this);
}

/******************************************************************************/

void Stepper::spin(int speed) {
  if (speed > 0) {
    direction = +1;
    timerAlarm(timer, frequency / (+speed * 2), true, 0); // speed times two since half-steps
  }
  else if (speed < 0) {
    direction = -1;
    timerAlarm(timer, frequency / (-speed * 2), true, 0); // speed times two since half-steps
  }
  else if (speed == 0) {
    direction = 0;
    timerAlarm(timer, 0, false, 0); // execute the alarm once, do not repeat
  }
}

/******************************************************************************/

void Stepper::powerOn() {
  power = true;
}

void Stepper::powerOff() {
  power = false;
}

/******************************************************************************/

void IRAM_ATTR Stepper::onTimer(void* arg) {
  // This is to work around the error that ISO C++ forbids taking the address of
  // an unqualified or parenthesized non-static member function to form a pointer
  // to member function.
  Stepper* pObj = (Stepper*) arg;
  pObj->doStep();
}

/******************************************************************************/

void IRAM_ATTR Stepper::doStep() {
  if (power) {
    digitalWrite(in1, bitRead(halfstep[step], 0));
    digitalWrite(in2, bitRead(halfstep[step], 1));
    digitalWrite(in3, bitRead(halfstep[step], 2));
    digitalWrite(in4, bitRead(halfstep[step], 3));

    // increment or decrement, depending on the direction
    step += direction;

    // wrap between 0 and 7
    if (step < 0)
      step = 7;
    else if (step > 7)
      step = 0;
  }
  else {
    digitalWrite(in1, 0);
    digitalWrite(in2, 0);
    digitalWrite(in3, 0);
    digitalWrite(in4, 0);
  }
}


================================================
FILE: esp32_3wd_stepper/stepper.h
================================================
#ifndef _STEPPER_H_
#define _STEPPER_H_

#include <Arduino.h>

class Stepper {
  public:
    Stepper();
    void begin(unsigned int in1, int in2, int in3, int in4);
    void spin(int speed);
    void powerOn();
    void powerOff();

  private:
    unsigned int frequency = 10000000;
    unsigned int halfstep[8] = {0b1000, 0b1100, 0b0100, 0b0110, 0b0010, 0b0011, 0b0001, 0b1001};  // half-step drive
    unsigned int in1, in2, in3, in4;  // the ESP32 pins connected to the ULN2003 driver board
    int step = 0;                     // from 0 to 7, wraps around
    int direction = 1;                // +1 or -1
    bool power = true;                // true or false
    hw_timer_t *timer = NULL;

    static void IRAM_ATTR onTimer(void* arg);   // this calls doStep
    void IRAM_ATTR doStep();                    // this implements the actual stepping
};

#endif // _STEPPER_H_


================================================
FILE: esp32_3wd_stepper/util.cpp
================================================
#include "util.h"

/********************************************************************************/

void printMacAddress() {
  uint8_t baseMac[6];
  esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
  if (ret == ESP_OK) {
    Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
                  baseMac[0], baseMac[1], baseMac[2],
                  baseMac[3], baseMac[4], baseMac[5]);
  } else {
    Serial.println("Failed to read MAC address");
  }
}


/********************************************************************************/

String getMacAddress() {
  uint8_t baseMac[6];
  String s;
  esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
  if (ret != ESP_OK) {
    s += "unknown";
  }
  else {
    for (byte i = 0; i < 6; ++i) {
      char buf[3];
      sprintf(buf, "%02X", baseMac[i]);
      s += buf;
      if (i < 5) s += ':';
    }
  }
  return s;
}


/********************************************************************************/

float maxOfThree(float a, float b, float c) {
  if (a >= b && a >= c) {
    return a;
  }
  else if (b >= a && b >= c) {
    return b;
  }
  else {
    return c;
  }
}


================================================
FILE: esp32_3wd_stepper/util.h
================================================
#ifndef _UTIL_H_
#define _UTIL_H_

#include <Arduino.h>
#include <WiFi.h>
#include <esp_wifi.h>

void printMacAddress(void);
String getMacAddress(void);
float maxOfThree(float, float, float);

#endif // _UTIL_H_


================================================
FILE: esp32_3wd_stepper/waypoints.cpp
================================================
#include "waypoints.h"

// read the sequence of waypoints from the CSV file on SPIFFS
//
// the file should have no header
// each line should consist of four comma-separated values: time, x, y, theta
// time is incremental and in seconds
// x and y are the position in meter
// theta is the angle in degrees in the file, it is converted to radians in memory

vector<float> waypoints_t;
vector<float> waypoints_x;
vector<float> waypoints_y;
vector<float> waypoints_a;

/****************************************************************************************/

void printWaypoints(int route) {
  Serial.print("printWaypoints ");
  Serial.println(route);

  for (int i = 0; i < waypoints_t.size(); i++) {
    Serial.print(waypoints_t.at(i));
    Serial.print(",");
    Serial.print(waypoints_x.at(i));
    Serial.print(",");
    Serial.print(waypoints_y.at(i));
    Serial.print(",");
    Serial.print(waypoints_a.at(i));
    Serial.print("\n");
  }
  return;
}

/****************************************************************************************/

size_t saveWaypoints(int route, String s) {
  Serial.print("saveWaypoints ");
  Serial.println(route);

  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
    return 0;
  }

  String filename = String("/waypoints") + String(route) + String(".csv");
  File file = SPIFFS.open(filename, "w");
  if (!file) {
    Serial.print("Failed to open ");
    Serial.print(filename);
    Serial.println(" for writing");
  }
  
  size_t bytes = file.print(s);
  return bytes;
}

/****************************************************************************************/

String loadWaypoints(int route) {
  Serial.print("loadWaypoints ");
  Serial.println(route);

  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
    String s = "";
    return s;
  }

  String filename = String("/waypoints") + String(route) + String(".csv");
  File file = SPIFFS.open(filename, "r");
  if (!file) {
    Serial.print("Failed to open ");
    Serial.print(filename);
    Serial.println(" for reading");
  }

  String s;
  while (file.available())
  {
    char charRead = file.read();
    s += charRead;
  }
  return s;
}

/****************************************************************************************/

void parseWaypoints(int route) {
  Serial.print("parseWaypoints ");
  Serial.println(route);

  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
    return;
  }

  String filename = String("/waypoints") + String(route) + String(".csv");
  File file = SPIFFS.open(filename, "r");
  if (!file) {
    Serial.print("Failed to open ");
    Serial.print(filename);
    Serial.println(" for reading");
  }

  waypoints_t.clear();
  waypoints_x.clear();
  waypoints_y.clear();
  waypoints_a.clear();

  vector<String> v;
  while (file.available()) {
    String s = file.readStringUntil('\n');
    v.push_back(s);

    int ind1 = s.indexOf(',');                    // finds location of 1st ,
    int ind2 = s.indexOf(',', ind1 + 1);          // finds location of 2nd ,
    int ind3 = s.indexOf(',', ind2 + 1);          // finds location of 3rd ,
    int ind4 = s.length();                        // end of the string

    String tok1 = s.substring(0, ind1);           //captures 1st value
    String tok2 = s.substring(ind1 + 1, ind2);    //captures 2nd value
    String tok3 = s.substring(ind2 + 1, ind3);    //captures 3rd value
    String tok4 = s.substring(ind3 + 1, ind4);    //captures 4th value

    // convert the values to float and store each in their own vector
    waypoints_t.push_back(atof(tok1.c_str()));
    waypoints_x.push_back(atof(tok2.c_str()));
    waypoints_y.push_back(atof(tok3.c_str()));
    waypoints_a.push_back(atof(tok4.c_str()) * M_PI / 180);  // convert from degrees to radians
  }

  file.close();
  return;
}


================================================
FILE: esp32_3wd_stepper/waypoints.h
================================================
#ifndef _WAYPOINTS_H_
#define _WAYPOINTS_H_

#include <FS.h>
#include <SPIFFS.h>
#include <vector>

using namespace std;

extern vector<float> waypoints_t;
extern vector<float> waypoints_x;
extern vector<float> waypoints_y;
extern vector<float> waypoints_a;

// the following functions take an integer as first input, which is the route number
// the route number is between 1 to 8, so you can use 8 predefined sets of waypoints

void printWaypoints(int);            // print the waypoints on the serial console
void parseWaypoints(int);            // read the waypoints from the file and represent as vectors
String loadWaypoints(int);           // read the waypoints from the file and return as a string
size_t saveWaypoints(int, String);   // save the waypoints (as string) to the file

#endif // _WAYPOINTS_H_


================================================
FILE: esp32_3wd_stepper/webinterface.cpp
================================================
#include "webinterface.h"
#include "waypoints.h"

Config config;
extern WebServer server;

/***************************************************************************/

static String getContentType(const String& path) {
  if (path.endsWith(".html")) return "text/html";
  else if (path.endsWith(".htm")) return "text/html";
  else if (path.endsWith(".css")) return "text/css";
  else if (path.endsWith(".txt")) return "text/plain";
  else if (path.endsWith(".js")) return "application/javascript";
  else if (path.endsWith(".png")) return "image/png";
  else if (path.endsWith(".gif")) return "image/gif";
  else if (path.endsWith(".jpg")) return "image/jpeg";
  else if (path.endsWith(".jpeg")) return "image/jpeg";
  else if (path.endsWith(".ico")) return "image/x-icon";
  else if (path.endsWith(".svg")) return "image/svg+xml";
  else if (path.endsWith(".xml")) return "text/xml";
  else if (path.endsWith(".pdf")) return "application/pdf";
  else if (path.endsWith(".zip")) return "application/zip";
  else if (path.endsWith(".gz")) return "application/x-gzip";
  else if (path.endsWith(".json")) return "application/json";
  return "application/octet-stream";
}

/***************************************************************************/

bool defaultConfig() {
  Serial.println("defaultConfig");

  config.repeat = 0;
  config.absolute = 0;
  config.warp = 1.000;
  config.debug = 0;
  return true;
}

bool loadConfig() {
  Serial.println("loadConfig");

  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  std::unique_ptr<char[]> buf(new char[size]);
  configFile.readBytes(buf.get(), size);
  configFile.close();

  JsonDocument root;
  DeserializationError error = deserializeJson(root, buf.get());
  if (error) {
    Serial.println("Failed to parse config file");
    return false;
  }

  N_JSON_TO_CONFIG(repeat, "repeat");
  N_JSON_TO_CONFIG(absolute, "absolute");
  N_JSON_TO_CONFIG(warp, "warp");
  N_JSON_TO_CONFIG(debug, "debug");

  return true;
}

bool saveConfig() {
  Serial.println("saveConfig");
  JsonDocument root;

  N_CONFIG_TO_JSON(repeat, "repeat");
  N_CONFIG_TO_JSON(absolute, "absolute");
  N_CONFIG_TO_JSON(warp, "warp");
  N_CONFIG_TO_JSON(debug, "debug");

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  } else {
    Serial.println("Writing to config file");
    serializeJson(root, configFile);
    configFile.close();
    return true;
  }
}

void printConfig() {
  Serial.print("repeat = ");
  Serial.println(config.repeat);
  Serial.print("absolute = ");
  Serial.println(config.absolute);
  Serial.print("warp = ");
  Serial.println(config.warp);
  Serial.print("debug = ");
  Serial.println(config.debug);
}

void printRequest() {
  String message = "HTTP Request\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nHeaders: ";
  message += server.headers();
  message += "\n";
  for (uint8_t i = 0; i < server.headers(); i++) {
    message += " " + server.headerName(i) + ": " + server.header(i) + "\n";
  }
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  Serial.println(message);
}

void handleNotFound() {
  Serial.print("handleNotFound: ");
  Serial.println(server.uri());
  if (SPIFFS.exists(server.uri())) {
    handleStaticFile(server.uri());
  } else {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    for (uint8_t i = 0; i < server.args(); i++) {
      message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }
    server.setContentLength(message.length());
    server.send(404, "text/plain", message);
  }
}

void handleRedirect(const char* filename) {
  handleRedirect((String)filename);
}

void handleRedirect(String filename) {
  Serial.println("handleRedirect: " + filename);
  server.sendHeader("Location", filename, true);
  server.setContentLength(0);
  server.send(302, "text/plain", "");
}

bool handleStaticFile(const char* path) {
  return handleStaticFile((String)path);
}

bool handleStaticFile(String path) {
  Serial.println("handleStaticFile: " + path);
  String contentType = getContentType(path);  // Get the MIME type
  if (SPIFFS.exists(path)) {                  // If the file exists
    File file = SPIFFS.open(path, "r");       // Open it
    server.setContentLength(file.size());
    server.streamFile(file, contentType);  // And send it to the client
    file.close();                          // Then close the file again
    return true;
  }
  Serial.println("\tFile Not Found");
  return false;  // If the file doesn't exist, return false
}

void handleJSON() {
  // this gets called in response to either a PUT or a POST
  Serial.println("handleJSON");
  printRequest();

  if (server.hasArg("repeat") || server.hasArg("absolute") || server.hasArg("warp") || server.hasArg("debug")
      || server.hasArg("waypoints1")
      || server.hasArg("waypoints2")
      || server.hasArg("waypoints3")
      || server.hasArg("waypoints4")
      || server.hasArg("waypoints5")
      || server.hasArg("waypoints6")
      || server.hasArg("waypoints7")
      || server.hasArg("waypoints8")) {
    // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it
    N_KEYVAL_TO_CONFIG(repeat, "repeat");
    N_KEYVAL_TO_CONFIG(absolute, "absolute");
    N_KEYVAL_TO_CONFIG(warp, "warp");
    N_KEYVAL_TO_CONFIG(debug, "debug");

    if (server.hasArg("waypoints1"))
      saveWaypoints(1, server.arg("waypoints1"));
    if (server.hasArg("waypoints2"))
      saveWaypoints(2, server.arg("waypoints2"));
    if (server.hasArg("waypoints3"))
      saveWaypoints(3, server.arg("waypoints3"));
    if (server.hasArg("waypoints4"))
      saveWaypoints(4, server.arg("waypoints4"));
    if (server.hasArg("waypoints5"))
      saveWaypoints(5, server.arg("waypoints5"));
    if (server.hasArg("waypoints6"))
      saveWaypoints(6, server.arg("waypoints6"));
    if (server.hasArg("waypoints7"))
      saveWaypoints(7, server.arg("waypoints7"));
    if (server.hasArg("waypoints8"))
      saveWaypoints(8, server.arg("waypoints8"));

    handleStaticFile("/reload_success.html");

  } else if (server.hasArg("plain")) {
    // parse the body as JSON object
    JsonDocument root;
    DeserializationError error = deserializeJson(root, server.arg("plain"));
    if (error) {
      Serial.println("either here");
      handleStaticFile("/reload_failure.html");
      return;
    }
    N_JSON_TO_CONFIG(repeat, "repeat");
    N_JSON_TO_CONFIG(absolute, "absolute");
    N_JSON_TO_CONFIG(warp, "warp");
    N_JSON_TO_CONFIG(debug, "debug");

    if (root.containsKey("waypoints1"))
      saveWaypoints(1, root["waypoints1"]);
    if (root.containsKey("waypoints2"))
      saveWaypoints(2, root["waypoints2"]);
    if (root.containsKey("waypoints3"))
      saveWaypoints(3, root["waypoints3"]);
    if (root.containsKey("waypoints4"))
      saveWaypoints(4, root["waypoints4"]);
    if (root.containsKey("waypoints5"))
      saveWaypoints(5, root["waypoints5"]);
    if (root.containsKey("waypoints6"))
      saveWaypoints(6, root["waypoints6"]);
    if (root.containsKey("waypoints7"))
      saveWaypoints(7, root["waypoints7"]);
    if (root.containsKey("waypoints8"))
      saveWaypoints(8, root["waypoints8"]);

    handleStaticFile("/reload_success.html");

  } else {
    handleStaticFile("/reload_failure.html");
    return;  // do not save the configuration
  }

  saveConfig();
  printConfig();
}


================================================
FILE: esp32_3wd_stepper/webinterface.h
================================================
#ifndef _WEBINTERFACE_H_
#define _WEBINTERFACE_H_

#include <Arduino.h>
#include <WebServer.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <FS.h>
#include <SPIFFS.h>

#ifndef ARDUINOJSON_VERSION
#error ArduinoJson version 7 not found, please include ArduinoJson.h in your .ino file
#endif

#if ARDUINOJSON_VERSION_MAJOR != 7
#error ArduinoJson version 7 is required
#endif

/* these are for numbers */
#define N_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { config.x = root[y]; } }
#define N_CONFIG_TO_JSON(x, y)   { root[y] = config.x; }
#define N_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); config.x = str.toFloat(); } }

/* these are for strings */
#define S_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { strcpy(config.x, root[y]); } }
#define S_CONFIG_TO_JSON(x, y)   { root.set(y, config.x); }
#define S_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); strcpy(config.x, str.c_str()); } }

struct Config {
  int repeat;
  int absolute;
  float warp;
  int debug;
};

extern Config config;

bool defaultConfig(void);
bool loadConfig(void);
bool saveConfig(void);
void printConfig(void);

void handleNotFound(void);
void handleRedirect(String);
void handleRedirect(const char *);
bool handleStaticFile(String);
bool handleStaticFile(const char *);
void handleJSON();

#endif // _WEBINTERFACE_H_


================================================
FILE: esp32_config_webinterface_v5/README.md
================================================
# Overview

This is a demonstration and test sketch for configuring persistent options via the web interface. This strategy is used in a number of my functional ESP32 and ESP8266 sketches. It consists of a `settings.json` file in the SPIFFS filesystem and a `settings.html` file with some javascript.

**This specific version is for ArduinoJson 5, there are other examples for version 6 and 7.**

The settings can be changed in the webbrowser at http://192.168.1.xxx/settings and clicking "Send". This results in `Webserver` parsing the arguments and placing the variables in the header, but also places the query string in the body.

The following results in `Webserver` parsing the arguments and placing the variables in the header.

    curl -X POST 'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'
    curl -X PUT  'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'

The following is not parsed, but only places the JSON data in the body.

    curl -X POST http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'
    curl -X PUT  http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'

## SPIFFS for static files

You should not only write the firmware to the ESP32 module, but also the static content for the web interface. The html, css and javascript files located in the data directory should be written to the SPIFS filesystem on the ESP32. See https://github.com/me-no-dev/arduino-esp32fs-plugin.

You will get a "file not found" error if the firmware cannot access the data files.

## Arduino ESP32 filesystem uploader

This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP32 using the [SPIFFS filesystem uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin) tool. At the moment (Feb 2024) the Arduino 2.x IDE does *not* support the SPIFFS filesystem uploader plugin. You have to use the Arduino 1.8.x IDE (recommended), or the command line utilities for uploading the data.


================================================
FILE: esp32_config_webinterface_v5/data/config.json
================================================
{
"var1":10,
"var2":20,
"var3":30,
"var4":40
}


================================================
FILE: esp32_config_webinterface_v5/data/hello.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Hello</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Hello panda!</p>
<img src="panda.jpg">
</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Index</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<a href="/hello.html">Hello</a></br>
<a href="/monitor.html">Monitor</a></br>
<a href="/settings.html">Change settings</a></br>
<a href="/reconnect">Reconnect WiFi</a></br>
<a href="/defaults">Default settings</a></br>
<a href="/restart">Restart hardware</a></br>
</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/monitor.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Monitor</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>

<h1>Monitor</h1>

Firmware version:
<div id="version" name="version">?</div>

Uptime:
<div id="uptime" name"uptime">?</div>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("version").innerHTML = data["version"];
    document.getElementById("uptime").innerHTML = data["uptime"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/reload_failure.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Failure</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Failure</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/reload_success.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Success</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Success</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/settings.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Settings</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<h1>Settings</h1>

<form id="settings-form" method="post" action="json">
    <div class="field">
        <label for="var1">var1:</label>
        <input type="text" id="var1" name="var1" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var2:</label>
        <input type="text" id="var2" name="var2" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var3:</label>
        <input type="text" id="var3" name="var3" value="?" required>
    </div>

    <div class="field">
        <button type="submit">Save</button>
    </div>
</form>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("var1").value = data["var1"];
    document.getElementById("var2").value = data["var2"];
    document.getElementById("var3").value = data["var3"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v5/data/style.css
================================================
.c{
    text-align: center;
}
div,input{
    padding:5px;font-size:1em;
}
input{
    width:95%;
}
body{
    text-align: center;font-family:verdana;
}
button{
    border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;
}
.q{
    float: right;width: 64px;text-align: right;
}
.l{
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==") no-repeat left center;background-size: 1em;
}


================================================
FILE: esp32_config_webinterface_v5/esp32_config_webinterface_v5.ino
================================================
#if not defined(ESP32)
#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32
#endif

#include <Arduino.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <WiFi.h>
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <FS.h>
#include <SPIFFS.h>

#ifndef ARDUINOJSON_VERSION
#error ArduinoJson version 5 not found, please include ArduinoJson.h in your .ino file
#endif

#if ARDUINOJSON_VERSION_MAJOR != 5
#error ArduinoJson version 5 is required
#endif

WebServer server(80);
const char* host = "ESP32";
const char* version = __DATE__ " / " __TIME__;
int var1, var2, var3;

static String getContentType(const String& path) {
  if (path.endsWith(".html")) return "text/html";
  else if (path.endsWith(".htm")) return "text/html";
  else if (path.endsWith(".css")) return "text/css";
  else if (path.endsWith(".txt")) return "text/plain";
  else if (path.endsWith(".js")) return "application/javascript";
  else if (path.endsWith(".png")) return "image/png";
  else if (path.endsWith(".gif")) return "image/gif";
  else if (path.endsWith(".jpg")) return "image/jpeg";
  else if (path.endsWith(".jpeg")) return "image/jpeg";
  else if (path.endsWith(".ico")) return "image/x-icon";
  else if (path.endsWith(".svg")) return "image/svg+xml";
  else if (path.endsWith(".xml")) return "text/xml";
  else if (path.endsWith(".pdf")) return "application/pdf";
  else if (path.endsWith(".zip")) return "application/zip";
  else if (path.endsWith(".gz")) return "application/x-gzip";
  else if (path.endsWith(".json")) return "application/json";
  return "application/octet-stream";
}

bool defaultConfig() {
  Serial.println("defaultConfig");
  var1 = 11;
  var2 = 22;
  var3 = 33;
  return true;
}

bool loadConfig() {
  Serial.println("loadConfig");
  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  std::unique_ptr<char[]> buf(new char[size]);
  configFile.readBytes(buf.get(), size);

  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(buf.get());

  if (!root.success()) {
    Serial.println("Failed to parse config file");
    return false;
  }

  if (root.containsKey("var1"))
    var1 = root["var1"];
  if (root.containsKey("var2"))
    var2 = root["var2"];
  if (root.containsKey("var3"))
    var3 = root["var3"];

  return true;
}

bool saveConfig() {
  Serial.println("saveConfig");
  printConfig();
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["var1"] = var1;
  root["var2"] = var2;
  root["var3"] = var3;

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  } else {
    root.printTo(configFile);
    return true;
  }
}

void printConfig() {
  Serial.print("var1 = ");
  Serial.println(var1);
  Serial.print("var2 = ");
  Serial.println(var2);
  Serial.print("var3 = ");
  Serial.println(var3);
}

void printRequest() {
  String message = "HTTP Request\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nHeaders: ";
  message += server.headers();
  message += "\n";
  for (uint8_t i = 0; i < server.headers(); i++) {
    message += " " + server.headerName(i) + ": " + server.header(i) + "\n";
  }
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  Serial.println(message);
}

void handleNotFound() {
  Serial.print("handleNotFound: ");
  Serial.println(server.uri());
  if (SPIFFS.exists(server.uri())) {
    handleStaticFile(server.uri());
  } else {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    for (uint8_t i = 0; i < server.args(); i++) {
      message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }
    server.setContentLength(message.length());
    server.send(404, "text/plain", message);
  }
}

void handleRedirect(const char* filename) {
  handleRedirect((String)filename);
}

void handleRedirect(String filename) {
  Serial.println("handleRedirect: " + filename);
  server.sendHeader("Location", filename, true);
  server.setContentLength(0);
  server.send(302, "text/plain", "");
}

bool handleStaticFile(const char* path) {
  return handleStaticFile((String)path);
}

bool handleStaticFile(String path) {
  Serial.println("handleStaticFile: " + path);
  String contentType = getContentType(path);  // Get the MIME type
  if (SPIFFS.exists(path)) {                  // If the file exists
    File file = SPIFFS.open(path, "r");       // Open it
    server.setContentLength(file.size());
    server.streamFile(file, contentType);     // And send it to the client
    file.close();                             // Then close the file again
    return true;
  } else {
    Serial.println("\tFile Not Found");
    return false;  // If the file doesn't exist, return false
  }
}

void handleJSON() {
  // this gets called in response to either a PUT or a POST
  Serial.println("handleJSON");
  printRequest();

  if (server.hasArg("var1") || server.hasArg("var2") || server.hasArg("var3")) {
    // the body is key1=val1&key2=val2&key3=val3 and the Webserver has already parsed it
    String str;
    if (server.hasArg("var1")) {
      str = server.arg("var1");
      var1 = str.toInt();
    }
    if (server.hasArg("var2")) {
      str = server.arg("var2");
      var2 = str.toInt();
    }
    if (server.hasArg("var3")) {
      str = server.arg("var3");
      var3 = str.toInt();
    }
    handleStaticFile("/reload_success.html");
  } else if (server.hasArg("plain")) {
    // parse the body as JSON object
    String body = server.arg("plain");
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.parseObject(body);
    if (!root.success()) {
      handleStaticFile("/reload_failure.html");
      return;
    }
    if (root.containsKey("var1"))
      var1 = root["var1"];
    if (root.containsKey("var2"))
      var2 = root["var2"];
    if (root.containsKey("var3"))
      var3 = root["var3"];
    handleStaticFile("/reload_success.html");
  } else {
    handleStaticFile("/reload_failure.html");
    return; // do not save the configuration
  }
  
  saveConfig();
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    ;
  }
  Serial.println("");
  Serial.println("setup");

  // The SPIFFS file system contains the config.json, and the html and javascript code for the web interface
  SPIFFS.begin();
  loadConfig();
  printConfig();

  WiFiManager wifiManager;
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
  wifiManager.autoConnect(host);
  Serial.println("connected");

  // this serves all URIs that can be resolved to a file on the SPIFFS filesystem
  server.onNotFound(handleNotFound);

  server.on("/", HTTP_GET, []() {
    handleRedirect("/index.html");
  });

  server.on("/defaults", HTTP_GET, []() {
    Serial.println("handleDefaults");
    handleStaticFile("/reload_success.html");
    defaultConfig();
    printConfig();
    saveConfig();
    server.close();
    server.stop();
    delay(5000);
    ESP.restart();
  });

  server.on("/reconnect", HTTP_GET, []() {
    Serial.println("handleReconnect");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    delay(5000);
    WiFiManager wifiManager;
    wifiManager.resetSettings();
    wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
    wifiManager.startConfigPortal(host);
    server.begin();
    if (WiFi.status() == WL_CONNECTED)
      Serial.println("WiFi status ok");
  });

  server.on("/restart", HTTP_GET, []() {
    Serial.println("handleRestart");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    SPIFFS.end();
    delay(5000);
    ESP.restart();
  });

  server.on("/json", HTTP_PUT, handleJSON);

  server.on("/json", HTTP_POST, handleJSON);

  server.on("/json", HTTP_GET, [] {
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.createObject();
    root["var1"] = var1;
    root["var2"] = var2;
    root["var3"] = var3;
    root["version"] = version;
    root["uptime"] = long(millis() / 1000);
    String content;
    root.printTo(content);
    server.send(200, "application/json", content);
  });

  // ask server to track these headers
  const char* headerkeys[] = { "User-Agent", "Content-Type" };
  size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
  server.collectHeaders(headerkeys, headerkeyssize);

  // start the web server
  server.begin();

  // announce the hostname and web server through zeroconf
  MDNS.begin(host);
  MDNS.addService("http", "tcp", 80);

  Serial.println("Setup done");
}

void loop() {
  // put your main code here, to run repeatedly
  server.handleClient();
  delay(10);  // in milliseconds
}


================================================
FILE: esp32_config_webinterface_v6/README.md
================================================
# Overview

This is a demonstration and test sketch for configuring persistent options via the web interface. This strategy is used in a number of my functional ESP32 and ESP8266 sketches. It consists of a `settings.json` file in the SPIFFS filesystem and a `settings.html` file with some javascript.

**This specific version is for ArduinoJson 6, there are other examples for version 5 and 7.**

The settings can be changed in the webbrowser at http://192.168.1.xxx/settings and clicking "Send". This results in `Webserver` parsing the arguments and placing the variables in the header, but also places the query string in the body.

The following also results in `Webserver` parsing the arguments and placing the variables in the header.

    curl -X POST 'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'
    curl -X PUT  'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'

The following is not parsed, but only places the JSON data in the body.

    curl -X POST http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'
    curl -X PUT  http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'

## SPIFFS for static files

You should not only write the firmware to the ESP32 module, but also the static content for the web interface. The html, css and javascript files located in the data directory should be written to the SPIFS filesystem on the ESP32. See https://github.com/me-no-dev/arduino-esp32fs-plugin.

You will get a "file not found" error if the firmware cannot access the data files.

## Arduino ESP32 filesystem uploader

This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP32 using the [SPIFFS filesystem uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin) tool. At the moment (Feb 2024) the Arduino 2.x IDE does *not* support the SPIFFS filesystem uploader plugin. You have to use the Arduino 1.8.x IDE (recommended), or the command line utilities for uploading the data.


================================================
FILE: esp32_config_webinterface_v6/data/config.json
================================================
{
"var1":10,
"var2":20,
"var3":30
}


================================================
FILE: esp32_config_webinterface_v6/data/hello.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Hello</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Hello panda!</p>
<img src="panda.jpg">
</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Index</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<a href="/hello.html">Hello</a></br>
<a href="/monitor.html">Monitor</a></br>
<a href="/settings.html">Change settings</a></br>
<a href="/reconnect">Reconnect WiFi</a></br>
<a href="/defaults">Default settings</a></br>
<a href="/restart">Restart hardware</a></br>
</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/monitor.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Monitor</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>

<h1>Monitor</h1>

Firmware version:
<div id="version" name="version">?</div>

Uptime:
<div id="uptime" name"uptime">?</div>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("version").innerHTML = data["version"];
    document.getElementById("uptime").innerHTML = data["uptime"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/reload_failure.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Failure</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Failure</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/reload_success.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Success</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Success</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/settings.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Settings</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<h1>Settings</h1>

<form id="settings-form" method="post" action="json">
    <div class="field">
        <label for="var1">var1:</label>
        <input type="text" id="var1" name="var1" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var2:</label>
        <input type="text" id="var2" name="var2" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var3:</label>
        <input type="text" id="var3" name="var3" value="?" required>
    </div>

    <div class="field">
        <button type="submit">Save</button>
    </div>
</form>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("var1").value = data["var1"];
    document.getElementById("var2").value = data["var2"];
    document.getElementById("var3").value = data["var3"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v6/data/style.css
================================================
.c{
    text-align: center;
}
div,input{
    padding:5px;font-size:1em;
}
input{
    width:95%;
}
body{
    text-align: center;font-family:verdana;
}
button{
    border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;
}
.q{
    float: right;width: 64px;text-align: right;
}
.l{
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==") no-repeat left center;background-size: 1em;
}


================================================
FILE: esp32_config_webinterface_v6/esp32_config_webinterface_v6.ino
================================================
#if not defined(ESP32)
#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32
#endif

#include <Arduino.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <WiFi.h>
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <FS.h>
#include <SPIFFS.h>

#include "webinterface.h"

/*********************************************************************************/

const char* host = "ESP32";
const char* version = __DATE__ " / " __TIME__;

WebServer server(80);
WiFiManager wifiManager;

/*********************************************************************************/

void setup() {

  Serial.begin(115200);
  while (!Serial) {
    ;
  }
  Serial.println("Setup starting");

  // The SPIFFS file system contains the config.json, and the html and javascript code for the web interface
  SPIFFS.begin();
  loadConfig();
  printConfig();

  WiFiManager wifiManager;
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
  wifiManager.autoConnect(host);
  Serial.println("connected");

  // this serves all URIs that can be resolved to a file on the SPIFFS filesystem
  server.onNotFound(handleNotFound);

  server.on("/", HTTP_GET, []() {
    handleRedirect("/index.html");
  });

  server.on("/defaults", HTTP_GET, []() {
    Serial.println("handleDefaults");
    handleStaticFile("/reload_success.html");
    defaultConfig();
    printConfig();
    saveConfig();
    server.close();
    server.stop();
    ESP.restart();
  });

  server.on("/reconnect", HTTP_GET, []() {
    Serial.println("handleReconnect");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    delay(5000);
    WiFiManager wifiManager;
    wifiManager.resetSettings();
    wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
    wifiManager.startConfigPortal(host);
    server.begin();
    if (WiFi.status() == WL_CONNECTED)
      Serial.println("WiFi status ok");
  });

  server.on("/restart", HTTP_GET, []() {
    Serial.println("handleRestart");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    SPIFFS.end();
    delay(5000);
    ESP.restart();
  });

  server.on("/json", HTTP_PUT, handleJSON);

  server.on("/json", HTTP_POST, handleJSON);

  server.on("/json", HTTP_GET, [] {
    DynamicJsonDocument root(300);
    N_CONFIG_TO_JSON(var1, "var1");
    N_CONFIG_TO_JSON(var2, "var2");
    N_CONFIG_TO_JSON(var3, "var3");
    root["version"] = version;
    root["uptime"] = long(millis() / 1000);
    String str;
    serializeJson(root, str);
    server.setContentLength(str.length());
    server.send(200, "application/json", str);
  });

  // start the web server
  server.begin();

  // announce the hostname and web server through zeroconf
  MDNS.begin(host);
  MDNS.addService("http", "tcp", 80);

  Serial.println("Setup done");
}

/*********************************************************************************/

void loop() {
  server.handleClient();
  delay(10);  // in milliseconds
}


================================================
FILE: esp32_config_webinterface_v6/webinterface.cpp
================================================
#include "webinterface.h"

Config config;
extern WebServer server;

/***************************************************************************/

static String getContentType(const String& path) {
  if (path.endsWith(".html"))       return "text/html";
  else if (path.endsWith(".htm"))   return "text/html";
  else if (path.endsWith(".css"))   return "text/css";
  else if (path.endsWith(".txt"))   return "text/plain";
  else if (path.endsWith(".js"))    return "application/javascript";
  else if (path.endsWith(".png"))   return "image/png";
  else if (path.endsWith(".gif"))   return "image/gif";
  else if (path.endsWith(".jpg"))   return "image/jpeg";
  else if (path.endsWith(".jpeg"))  return "image/jpeg";
  else if (path.endsWith(".ico"))   return "image/x-icon";
  else if (path.endsWith(".svg"))   return "image/svg+xml";
  else if (path.endsWith(".xml"))   return "text/xml";
  else if (path.endsWith(".pdf"))   return "application/pdf";
  else if (path.endsWith(".zip"))   return "application/zip";
  else if (path.endsWith(".gz"))    return "application/x-gzip";
  else if (path.endsWith(".json"))  return "application/json";
  return "application/octet-stream";
}

/***************************************************************************/

bool defaultConfig() {
  Serial.println("defaultConfig");
  
  config.var1 = 11;
  config.var2 = 22;
  config.var3 = 33;
  return true;
}

bool loadConfig() {
  Serial.println("loadConfig");

  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  std::unique_ptr<char[]> buf(new char[size]);
  configFile.readBytes(buf.get(), size);
  configFile.close();

  StaticJsonDocument<300> jsonBuffer;
  DynamicJsonDocument root(1024);
  DeserializationError error = deserializeJson(root, buf.get());
  if (error) {
    Serial.println("Failed to parse config file");
    return false;
  }

  N_JSON_TO_CONFIG(var1, "var1");
  N_JSON_TO_CONFIG(var2, "var2");
  N_JSON_TO_CONFIG(var3, "var3");

  return true;
}

bool saveConfig() {
  Serial.println("saveConfig");
  DynamicJsonDocument root(300);

  N_CONFIG_TO_JSON(var1, "var1");
  N_CONFIG_TO_JSON(var2, "var2");
  N_CONFIG_TO_JSON(var3, "var3");

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  }
  else {
    Serial.println("Writing to config file");
    serializeJson(root, configFile);
    configFile.close();
    return true;
  }
}

void printConfig() {
  Serial.print("var1 = ");
  Serial.println(config.var1);
  Serial.print("var2 = ");
  Serial.println(config.var2);
  Serial.print("var3 = ");
  Serial.println(config.var3);
}

void printRequest() {
  String message = "HTTP Request\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nHeaders: ";
  message += server.headers();
  message += "\n";
  for (uint8_t i = 0; i < server.headers(); i++ ) {
    message += " " + server.headerName(i) + ": " + server.header(i) + "\n";
  }
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  Serial.println(message);
}

void handleNotFound() {
  Serial.print("handleNotFound: ");
  Serial.println(server.uri());
  if (SPIFFS.exists(server.uri())) {
    handleStaticFile(server.uri());
  }
  else {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    for (uint8_t i = 0; i < server.args(); i++) {
      message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }
    server.setContentLength(message.length());
    server.send(404, "text/plain", message);
  }
}

void handleRedirect(const char * filename) {
  handleRedirect((String)filename);
}

void handleRedirect(String filename) {
  Serial.println("handleRedirect: " + filename);
  server.sendHeader("Location", filename, true);
  server.setContentLength(0);
  server.send(302, "text/plain", "");
}

bool handleStaticFile(const char * path) {
  return handleStaticFile((String)path);
}

bool handleStaticFile(String path) {
  Serial.println("handleStaticFile: " + path);
  String contentType = getContentType(path);            // Get the MIME type
  if (SPIFFS.exists(path)) {                            // If the file exists
    File file = SPIFFS.open(path, "r");                 // Open it
    server.setContentLength(file.size());
    server.streamFile(file, contentType);               // And send it to the client
    file.close();                                       // Then close the file again
    return true;
  }
  Serial.println("\tFile Not Found");
  return false;                                         // If the file doesn't exist, return false
}

void handleJSON() {
  // this gets called in response to either a PUT or a POST
  Serial.println("handleJSON");
  printRequest();

  if (server.hasArg("var1") || server.hasArg("var2") || server.hasArg("var3")) {
    // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it
    N_KEYVAL_TO_CONFIG(var1, "var1");
    N_KEYVAL_TO_CONFIG(var2, "var2");
    N_KEYVAL_TO_CONFIG(var3, "var3");
    handleStaticFile("/reload_success.html");
  }
  else if (server.hasArg("plain")) {
    // parse the body as JSON object
    DynamicJsonDocument root(300);
    DeserializationError error = deserializeJson(root, server.arg("plain"));
    if (error) {
      handleStaticFile("/reload_failure.html");
      return;
    }
    N_JSON_TO_CONFIG(var1, "var1");
    N_JSON_TO_CONFIG(var2, "var2");
    N_JSON_TO_CONFIG(var3, "var3");
    handleStaticFile("/reload_success.html");
  }
  else {
    handleStaticFile("/reload_failure.html");
    return; // do not save the configuration
  }
  
  saveConfig();
}


================================================
FILE: esp32_config_webinterface_v6/webinterface.h
================================================
#ifndef _WEBINTERFACE_H_
#define _WEBINTERFACE_H_

#include <Arduino.h>
#include <WebServer.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <FS.h>
#include <SPIFFS.h>

#ifndef ARDUINOJSON_VERSION
#error ArduinoJson version 6 not found, please include ArduinoJson.h in your .ino file
#endif

#if ARDUINOJSON_VERSION_MAJOR != 6
#error ArduinoJson version 6 is required
#endif

/* these are for numbers */
#define N_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { config.x = root[y]; } }
#define N_CONFIG_TO_JSON(x, y)   { root[y] = config.x; }
#define N_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); config.x = str.toFloat(); } }

/* these are for strings */
#define S_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { strcpy(config.x, root[y]); } }
#define S_CONFIG_TO_JSON(x, y)   { root.set(y, config.x); }
#define S_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); strcpy(config.x, str.c_str()); } }

struct Config {
  int var1;
  int var2;
  int var3;
};

extern Config config;

bool defaultConfig(void);
bool loadConfig(void);
bool saveConfig(void);
void printConfig(void);

void handleNotFound(void);
void handleRedirect(String);
void handleRedirect(const char *);
bool handleStaticFile(String);
bool handleStaticFile(const char *);
void handleJSON();

#endif // _WEBINTERFACE_H_


================================================
FILE: esp32_config_webinterface_v7/README.md
================================================
# Overview

This is a demonstration and test sketch for configuring persistent options via the web interface. This strategy is used in a number of my functional ESP32 and ESP8266 sketches. It consists of a `settings.json` file in the SPIFFS filesystem and a `settings.html` file with some javascript.

**This specific version is for ArduinoJson 7, there are other examples for version 5 and 6.**

The settings can be changed in the webbrowser at http://192.168.1.xxx/settings and clicking "Send". This results in `Webserver` parsing the arguments and placing the variables in the header, but also places the query string in the body.

The following also results in `Webserver` parsing the arguments and placing the variables in the header.

    curl -X POST 'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'
    curl -X PUT  'http://192.168.1.xxx/json?var1=11&var2=22&var3=33'

The following is not parsed, but only places the JSON data in the body.

    curl -X POST http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'
    curl -X PUT  http://192.168.1.xxx/json -d '{"var1":11,"var2":22,"var3":33}'

## SPIFFS for static files

You should not only write the firmware to the ESP32 module, but also the static content for the web interface. The html, css and javascript files located in the data directory should be written to the SPIFS filesystem on the ESP32. See https://github.com/me-no-dev/arduino-esp32fs-plugin.

You will get a "file not found" error if the firmware cannot access the data files.

## Arduino ESP32 filesystem uploader

This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP32 using the [SPIFFS filesystem uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin) tool. At the moment (Feb 2024) the Arduino 2.x IDE does *not* support the SPIFFS filesystem uploader plugin. You have to use the Arduino 1.8.x IDE (recommended), or the command line utilities for uploading the data.


================================================
FILE: esp32_config_webinterface_v7/data/config.json
================================================
{
"var1":10,
"var2":20,
"var3":30
}


================================================
FILE: esp32_config_webinterface_v7/data/hello.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Hello</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Hello panda!</p>
<img src="panda.jpg">
</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Index</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<a href="/hello.html">Hello</a></br>
<a href="/monitor.html">Monitor</a></br>
<a href="/settings.html">Change settings</a></br>
<a href="/reconnect">Reconnect WiFi</a></br>
<a href="/defaults">Default settings</a></br>
<a href="/restart">Restart hardware</a></br>
</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/monitor.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Monitor</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>

<h1>Monitor</h1>

Firmware version:
<div id="version" name="version">?</div>

Uptime:
<div id="uptime" name"uptime">?</div>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("version").innerHTML = data["version"];
    document.getElementById("uptime").innerHTML = data["uptime"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/reload_failure.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Failure</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Failure</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/reload_success.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Success</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<p>Success</p>

<script type="text/javascript">
  setTimeout(function(){location="/index.html"},1000);
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/settings.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Settings</title>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
<h1>Settings</h1>

<form id="settings-form" method="post" action="json">
    <div class="field">
        <label for="var1">var1:</label>
        <input type="text" id="var1" name="var1" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var2:</label>
        <input type="text" id="var2" name="var2" value="?" required>
    </div>

    <div class="field">
        <label for="var2">var3:</label>
        <input type="text" id="var3" name="var3" value="?" required>
    </div>

    <div class="field">
        <button type="submit">Save</button>
    </div>
</form>

<script>
  async function updateContent() {
    const response = await fetch("json");
    const data = await response.json();
    console.log(data);
    document.getElementById("var1").value = data["var1"];
    document.getElementById("var2").value = data["var2"];
    document.getElementById("var3").value = data["var3"];
  }
  updateContent();
</script>

</body>
</html>


================================================
FILE: esp32_config_webinterface_v7/data/style.css
================================================
.c{
    text-align: center;
}
div,input{
    padding:5px;font-size:1em;
}
input{
    width:95%;
}
body{
    text-align: center;font-family:verdana;
}
button{
    border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;
}
.q{
    float: right;width: 64px;text-align: right;
}
.l{
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==") no-repeat left center;background-size: 1em;
}


================================================
FILE: esp32_config_webinterface_v7/esp32_config_webinterface_v7.ino
================================================
#if not defined(ESP32)
#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32
#endif

#include <Arduino.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <WiFi.h>
#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <FS.h>
#include <SPIFFS.h>

#include "webinterface.h"

/*********************************************************************************/

const char* host = "ESP32";
const char* version = __DATE__ " / " __TIME__;

WebServer server(80);
WiFiManager wifiManager;

/*********************************************************************************/

void setup() {

  Serial.begin(115200);
  while (!Serial) {
    ;
  }
  Serial.println("Setup starting");

  // The SPIFFS file system contains the config.json, and the html and javascript code for the web interface
  SPIFFS.begin();
  loadConfig();
  printConfig();

  WiFiManager wifiManager;
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
  wifiManager.autoConnect(host);
  Serial.println("connected");

  // this serves all URIs that can be resolved to a file on the SPIFFS filesystem
  server.onNotFound(handleNotFound);

  server.on("/", HTTP_GET, []() {
    handleRedirect("/index.html");
  });

  server.on("/defaults", HTTP_GET, []() {
    Serial.println("handleDefaults");
    handleStaticFile("/reload_success.html");
    defaultConfig();
    printConfig();
    saveConfig();
    server.close();
    server.stop();
    ESP.restart();
  });

  server.on("/reconnect", HTTP_GET, []() {
    Serial.println("handleReconnect");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    delay(5000);
    WiFiManager wifiManager;
    wifiManager.resetSettings();
    wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
    wifiManager.startConfigPortal(host);
    server.begin();
    if (WiFi.status() == WL_CONNECTED)
      Serial.println("WiFi status ok");
  });

  server.on("/restart", HTTP_GET, []() {
    Serial.println("handleRestart");
    handleStaticFile("/reload_success.html");
    server.close();
    server.stop();
    SPIFFS.end();
    delay(5000);
    ESP.restart();
  });

  server.on("/json", HTTP_PUT, handleJSON);

  server.on("/json", HTTP_POST, handleJSON);

  server.on("/json", HTTP_GET, [] {
    JsonDocument root;
    N_CONFIG_TO_JSON(var1, "var1");
    N_CONFIG_TO_JSON(var2, "var2");
    N_CONFIG_TO_JSON(var3, "var3");
    root["version"] = version;
    root["uptime"] = long(millis() / 1000);
    String str;
    serializeJson(root, str);
    server.setContentLength(str.length());
    server.send(200, "application/json", str);
  });

  // start the web server
  server.begin();

  // announce the hostname and web server through zeroconf
  MDNS.begin(host);
  MDNS.addService("http", "tcp", 80);

  Serial.println("Setup done");
}

/*********************************************************************************/

void loop() {
  server.handleClient();
  delay(10);  // in milliseconds
}


================================================
FILE: esp32_config_webinterface_v7/webinterface.cpp
================================================
#include "webinterface.h"

Config config;
extern WebServer server;

/***************************************************************************/

static String getContentType(const String& path) {
  if (path.endsWith(".html"))       return "text/html";
  else if (path.endsWith(".htm"))   return "text/html";
  else if (path.endsWith(".css"))   return "text/css";
  else if (path.endsWith(".txt"))   return "text/plain";
  else if (path.endsWith(".js"))    return "application/javascript";
  else if (path.endsWith(".png"))   return "image/png";
  else if (path.endsWith(".gif"))   return "image/gif";
  else if (path.endsWith(".jpg"))   return "image/jpeg";
  else if (path.endsWith(".jpeg"))  return "image/jpeg";
  else if (path.endsWith(".ico"))   return "image/x-icon";
  else if (path.endsWith(".svg"))   return "image/svg+xml";
  else if (path.endsWith(".xml"))   return "text/xml";
  else if (path.endsWith(".pdf"))   return "application/pdf";
  else if (path.endsWith(".zip"))   return "application/zip";
  else if (path.endsWith(".gz"))    return "application/x-gzip";
  else if (path.endsWith(".json"))  return "application/json";
  return "application/octet-stream";
}

/***************************************************************************/

bool defaultConfig() {
  Serial.println("defaultConfig");
  
  config.var1 = 11;
  config.var2 = 22;
  config.var3 = 33;
  return true;
}

bool loadConfig() {
  Serial.println("loadConfig");

  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  std::unique_ptr<char[]> buf(new char[size]);
  configFile.readBytes(buf.get(), size);
  configFile.close();

  JsonDocument root;
  DeserializationError error = deserializeJson(root, buf.get());
  if (error) {
    Serial.println("Failed to parse config file");
    return false;
  }

  N_JSON_TO_CONFIG(var1, "var1");
  N_JSON_TO_CONFIG(var2, "var2");
  N_JSON_TO_CONFIG(var3, "var3");

  return true;
}

bool saveConfig() {
  Serial.println("saveConfig");
  JsonDocument root;

  N_CONFIG_TO_JSON(var1, "var1");
  N_CONFIG_TO_JSON(var2, "var2");
  N_CONFIG_TO_JSON(var3, "var3");

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  }
  else {
    Serial.println("Writing to config file");
    serializeJson(root, configFile);
    configFile.close();
    return true;
  }
}

void printConfig() {
  Serial.print("var1 = ");
  Serial.println(config.var1);
  Serial.print("var2 = ");
  Serial.println(config.var2);
  Serial.print("var3 = ");
  Serial.println(config.var3);
}

void printRequest() {
  String message = "HTTP Request\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nHeaders: ";
  message += server.headers();
  message += "\n";
  for (uint8_t i = 0; i < server.headers(); i++ ) {
    message += " " + server.headerName(i) + ": " + server.header(i) + "\n";
  }
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  Serial.println(message);
}

void handleNotFound() {
  Serial.print("handleNotFound: ");
  Serial.println(server.uri());
  if (SPIFFS.exists(server.uri())) {
    handleStaticFile(server.uri());
  }
  else {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    for (uint8_t i = 0; i < server.args(); i++) {
      message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }
    server.setContentLength(message.length());
    server.send(404, "text/plain", message);
  }
}

void handleRedirect(const char * filename) {
  handleRedirect((String)filename);
}

void handleRedirect(String filename) {
  Serial.println("handleRedirect: " + filename);
  server.sendHeader("Location", filename, true);
  server.setContentLength(0);
  server.send(302, "text/plain", "");
}

bool handleStaticFile(const char * path) {
  return handleStaticFile((String)path);
}

bool handleStaticFile(String path) {
  Serial.println("handleStaticFile: " + path);
  String contentType = getContentType(path);            // Get the MIME type
  if (SPIFFS.exists(path)) {                            // If the file exists
    File file = SPIFFS.open(path, "r");                 // Open it
    server.setContentLength(file.size());
    server.streamFile(file, contentType);               // And send it to the client
    file.close();                                       // Then close the file again
    return true;
  }
  Serial.println("\tFile Not Found");
  return false;                                         // If the file doesn't exist, return false
}

void handleJSON() {
  // this gets called in response to either a PUT or a POST
  Serial.println("handleJSON");
  printRequest();

  if (server.hasArg("var1") || server.hasArg("var2") || server.hasArg("var3")) {
    // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it
    N_KEYVAL_TO_CONFIG(var1, "var1");
    N_KEYVAL_TO_CONFIG(var2, "var2");
    N_KEYVAL_TO_CONFIG(var3, "var3");
    handleStaticFile("/reload_success.html");
  }
  else if (server.hasArg("plain")) {
    // parse the body as JSON object
    JsonDocument root;
    DeserializationError error = deserializeJson(root, server.arg("plain"));
    if (error) {
      handleStaticFile("/reload_failure.html");
      return;
    }
    N_JSON_TO_CONFIG(var1, "var1");
    N_JSON_TO_CONFIG(var2, "var2");
    N_JSON_TO_CONFIG(var3, "var3");
    handleStaticFile("/reload_success.html");
  }
  else {
    handleStaticFile("/reload_failure.html");
    return; // do not save the configuration
  }
  
  saveConfig();
}


================================================
FILE: esp32_config_webinterface_v7/webinterface.h
================================================
#ifndef _WEBINTERFACE_H_
#define _WEBINTERFACE_H_

#include <Arduino.h>
#include <WebServer.h>
#include <ArduinoJson.h>  // https://arduinojson.org
#include <FS.h>
#include <SPIFFS.h>

#ifndef ARDUINOJSON_VERSION
#error ArduinoJson version 7 not found, please include ArduinoJson.h in your .ino file
#endif

#if ARDUINOJSON_VERSION_MAJOR != 7
#error ArduinoJson version 7 is required
#endif

/* these are for numbers */
#define N_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { config.x = root[y]; } }
#define N_CONFIG_TO_JSON(x, y)   { root[y] = config.x; }
#define N_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); config.x = str.toFloat(); } }

/* these are for strings */
#define S_JSON_TO_CONFIG(x, y)   { if (root.containsKey(y)) { strcpy(config.x, root[y]); } }
#define S_CONFIG_TO_JSON(x, y)   { root.set(y, config.x); }
#define S_KEYVAL_TO_CONFIG(x, y) { if (server.hasArg(y))    { String str = server.arg(y); strcpy(config.x, str.c_str()); } }

struct Config {
  int var1;
  int var2;
  int var3;
};

extern Config config;

bool defaultConfig(void);
bool loadConfig(void);
bool saveConfig(void);
void printConfig(void);

void handleNotFound(vo
Download .txt
gitextract_yp2dqrl9/

├── .gitignore
├── .gitmodules
├── PulseSensor_v0/
│   ├── AllSerialHandling.cpp
│   ├── Interrupt.cpp
│   ├── PulseSensor_v0.ino
│   ├── README.md
│   └── Timer_Interrupt_Notes.cpp
├── PulseSensor_v1/
│   ├── PulseSensor_v1.ino
│   └── README.md
├── PulseSensor_v2/
│   ├── Interrupt.cpp
│   ├── PulseSensor_v2.ino
│   └── README.md
├── README.md
├── bitsi/
│   └── bitsi.ino
├── blenderdefender/
│   └── blenderdefender.ino
├── digispark_skateboard/
│   ├── README.md
│   ├── colorspace.cpp
│   ├── colorspace.h
│   ├── digispark_skateboard.ino
│   ├── neopixel_mode.cpp
│   └── neopixel_mode.h
├── eegsynth_cvgate_mcp4725/
│   ├── README.md
│   └── eegsynth_cvgate_mcp4725.ino
├── eegsynth_cvgate_mcp4822/
│   └── eegsynth_cvgate_mcp4822.ino
├── eegsynth_devirtualizer/
│   └── eegsynth_devirtualizer.ino
├── eegsynth_usbdmxpro/
│   ├── COPYING.txt
│   └── eegsynth_usbdmxpro.ino
├── esp32_3wd_servo/
│   ├── README.md
│   └── esp32_3wd_servo.ino
├── esp32_3wd_stepper/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   ├── waypoints.html
│   │   ├── waypoints1.csv
│   │   ├── waypoints2.csv
│   │   ├── waypoints3.csv
│   │   ├── waypoints4.csv
│   │   ├── waypoints5.csv
│   │   ├── waypoints6.csv
│   │   ├── waypoints7.csv
│   │   └── waypoints8.csv
│   ├── esp32_3wd_stepper.ino
│   ├── parseosc.cpp
│   ├── parseosc.h
│   ├── stepper.cpp
│   ├── stepper.h
│   ├── util.cpp
│   ├── util.h
│   ├── waypoints.cpp
│   ├── waypoints.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_config_webinterface_v5/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   └── esp32_config_webinterface_v5.ino
├── esp32_config_webinterface_v6/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_config_webinterface_v6.ino
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_config_webinterface_v7/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_config_webinterface_v7.ino
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_exgpill/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   └── style.css
│   ├── esp32_exgpill.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp32_inmp441/
│   ├── RunningStat.h
│   ├── esp32_inmp441.ino
│   └── util.h
├── esp32_sph0645/
│   ├── RunningStat.h
│   ├── compress.cpp
│   └── esp32_sph0645.ino
├── esp32c6_feeder/
│   ├── README.md
│   └── esp32c6_feeder.ino
├── esp8266_12v_trigger/
│   └── esp8266_12v_trigger.ino
├── esp8266_ad8232_ecg/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_ad8232_ecg.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_artnet_bci/
│   └── esp8266_artnet_bci.ino
├── esp8266_artnet_neopixel/
│   ├── README.md
│   ├── colorspace.cpp
│   ├── colorspace.h
│   ├── data/
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_artnet_neopixel.ino
│   ├── font8x8_basic.h
│   ├── neopixel_mode.cpp
│   ├── neopixel_mode.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_config_spiffs/
│   ├── data/
│   │   └── config.json
│   └── esp8266_config_spiffs.ino
├── esp8266_config_webinterface/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   └── esp8266_config_webinterface.ino
├── esp8266_fan_control/
│   └── esp8266_fan_control.ino
├── esp8266_fieldtrip_buffer/
│   ├── esp8266_fieldtrip_buffer.ino
│   ├── fieldtrip_buffer.cpp
│   └── fieldtrip_buffer.h
├── esp8266_imu_osc/
│   ├── I2Cscan.cpp
│   ├── I2Cscan.h
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_imu_osc.ino
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── tca9548a.cpp
│   ├── tca9548a.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_inmp411/
│   ├── RunningStat.h
│   └── esp8266_inmp411.ino
├── esp8266_p1_thingspeak/
│   └── esp8266_p1_thingspeak.ino
├── esp8266_polar_wearlink/
│   ├── README.md
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_polar_wearlink.ino
│   ├── rgb_led.cpp
│   ├── rgb_led.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_pulse_sensor/
│   ├── README.md
│   ├── blink_led.cpp
│   ├── blink_led.h
│   ├── data/
│   │   ├── config.json
│   │   ├── hello.html
│   │   ├── index.html
│   │   ├── monitor.html
│   │   ├── reload_failure.html
│   │   ├── reload_success.html
│   │   ├── settings.html
│   │   ├── style.css
│   │   └── update.html
│   ├── esp8266_pulse_sensor.ino
│   ├── fieldtrip_buffer.cpp
│   ├── fieldtrip_buffer.h
│   ├── webinterface.cpp
│   └── webinterface.h
├── esp8266_thingspeak/
│   └── esp8266_thingspeak.ino
├── m5dial_midi/
│   ├── colormap.h
│   └── m5dial_midi.ino
├── m5nanoc6_angle8_midi/
│   ├── colormap.h
│   └── m5nanoc6_angle8_midi.ino
├── m5nanoc6_encoder8_midi/
│   ├── colormap.h
│   └── m5nanoc6_encoder8_midi.ino
├── rfm12b_recv_xxxx/
│   ├── README.md
│   └── rfm12b_recv_xxxx.ino
├── rfm12b_send_am2301/
│   ├── README.md
│   └── rfm12b_send_am2301.ino
├── rfm12b_send_bmp085/
│   ├── README.md
│   └── rfm12b_send_bmp085.ino
├── rfm12b_send_cny70/
│   ├── README.md
│   └── rfm12b_send_cny70.ino
├── rfm12b_send_ds18b20/
│   ├── README.md
│   └── rfm12b_send_ds18b20.ino
├── rfm12b_send_lm35/
│   ├── README.md
│   └── rfm12b_send_lm35.ino
├── rfm12b_send_random/
│   ├── README.md
│   └── rfm12b_send_random.ino
├── rfm12b_thingspeak/
│   ├── README.md
│   └── rfm12b_thingspeak.ino
├── rp2040_dac7578/
│   ├── blink_led.cpp
│   ├── blink_led.h
│   └── rp2040_dac7578.ino
├── teensy_cvgate_mcp4725_neopixel/
│   ├── colormap.h
│   └── teensy_cvgate_mcp4725_neopixel.ino
├── teensy_gps_temp_ttn/
│   ├── payload.h
│   ├── sensor.cpp
│   └── teensy_gps_temp_ttn.ino
├── teensy_midifilter/
│   ├── midiname.c
│   └── teensy_midifilter.ino
├── teensy_ttn1/
│   └── teensy_ttn1.ino
├── teensy_ttn2/
│   └── teensy_ttn2.ino
└── uno_dac7578/
    ├── blink_led.cpp
    ├── blink_led.h
    └── uno_dac7578.ino
Download .txt
SYMBOL INDEX (334 symbols across 57 files)

FILE: PulseSensor_v0/AllSerialHandling.cpp
  function serialOutput (line 8) | void serialOutput(){   // Decide How To Output Serial.
  function serialOutputWhenBeatHappens (line 18) | void serialOutputWhenBeatHappens(){
  function sendDataToSerial (line 33) | void sendDataToSerial(char symbol, int data ){
  function arduinoSerialMonitorVisual (line 41) | void arduinoSerialMonitorVisual(char symbol, int data ){

FILE: PulseSensor_v0/Interrupt.cpp
  function interruptSetup (line 15) | void interruptSetup(){
  function ISR (line 27) | ISR(TIMER2_COMPA_vect){                         // triggered when Timer2...

FILE: PulseSensor_v2/Interrupt.cpp
  function interruptSetup (line 12) | void interruptSetup() {
  function ISR (line 24) | ISR(TIMER2_COMPA_vect) {                      // triggered when Timer2 c...

FILE: digispark_skateboard/colorspace.cpp
  function hsv (line 3) | hsv rgb2hsv(rgb in)
  function rgb (line 47) | rgb hsv2rgb(hsv in)

FILE: digispark_skateboard/colorspace.h
  type rgb (line 4) | typedef struct {
  type hsv (line 10) | typedef struct {

FILE: digispark_skateboard/neopixel_mode.cpp
  function mode1 (line 21) | void mode1(uint8_t * data) {
  function mode4 (line 37) | void mode4(uint8_t * data) {
  function mode10 (line 65) | void mode10(uint8_t * data) {
  function mode12 (line 95) | void mode12(uint8_t * data) {
  function map_hsv_to_rgb (line 122) | void map_hsv_to_rgb(int *r, int *g, int *b) {

FILE: esp32_3wd_stepper/blink_led.cpp
  function changeState (line 13) | void changeState() {
  function ledInit (line 17) | void ledInit() {
  function ledOn (line 21) | void ledOn() {
  function ledOff (line 29) | void ledOff() {
  function ledSlow (line 37) | void ledSlow() {
  function ledMedium (line 45) | void ledMedium() {
  function ledFast (line 53) | void ledFast() {

FILE: esp32_3wd_stepper/parseosc.cpp
  function parseOSC (line 5) | void parseOSC() {
  function printCallback (line 84) | void printCallback(OSCMessage &msg) {
  function route1Callback (line 114) | void route1Callback(OSCMessage &msg) {
  function route2Callback (line 119) | void route2Callback(OSCMessage &msg) {
  function route3Callback (line 124) | void route3Callback(OSCMessage &msg) {
  function route4Callback (line 129) | void route4Callback(OSCMessage &msg) {
  function route5Callback (line 134) | void route5Callback(OSCMessage &msg) {
  function route6Callback (line 139) | void route6Callback(OSCMessage &msg) {
  function route7Callback (line 144) | void route7Callback(OSCMessage &msg) {
  function route8Callback (line 149) | void route8Callback(OSCMessage &msg) {
  function pauseCallback (line 154) | void pauseCallback(OSCMessage &msg) {
  function resumeCallback (line 159) | void resumeCallback(OSCMessage &msg) {
  function stopCallback (line 164) | void stopCallback(OSCMessage &msg) {
  function resetCallback (line 169) | void resetCallback(OSCMessage & msg) {
  function xCallback (line 174) | void xCallback(OSCMessage & msg) {
  function yCallback (line 178) | void yCallback(OSCMessage & msg) {
  function aCallback (line 182) | void aCallback(OSCMessage & msg) {
  function dxCallback (line 186) | void dxCallback(OSCMessage & msg) {
  function dyCallback (line 192) | void dyCallback(OSCMessage & msg) {
  function daCallback (line 198) | void daCallback(OSCMessage & msg) {
  function xyCallback (line 204) | void xyCallback(OSCMessage & msg) {
  function accxyzCallback (line 209) | void accxyzCallback(OSCMessage & msg) {

FILE: esp32_3wd_stepper/stepper.h
  function class (line 6) | class Stepper {

FILE: esp32_3wd_stepper/util.cpp
  function printMacAddress (line 5) | void printMacAddress() {
  function String (line 20) | String getMacAddress() {
  function maxOfThree (line 41) | float maxOfThree(float a, float b, float c) {

FILE: esp32_3wd_stepper/waypoints.cpp
  function printWaypoints (line 18) | void printWaypoints(int route) {
  function saveWaypoints (line 37) | size_t saveWaypoints(int route, String s) {
  function String (line 60) | String loadWaypoints(int route) {
  function parseWaypoints (line 89) | void parseWaypoints(int route) {

FILE: esp32_3wd_stepper/webinterface.cpp
  function String (line 9) | static String getContentType(const String& path) {
  function defaultConfig (line 31) | bool defaultConfig() {
  function loadConfig (line 41) | bool loadConfig() {
  function saveConfig (line 75) | bool saveConfig() {
  function printConfig (line 96) | void printConfig() {
  function printRequest (line 107) | void printRequest() {
  function handleNotFound (line 128) | void handleNotFound() {
  function handleRedirect (line 150) | void handleRedirect(const char* filename) {
  function handleRedirect (line 154) | void handleRedirect(String filename) {
  function handleStaticFile (line 161) | bool handleStaticFile(const char* path) {
  function handleStaticFile (line 165) | bool handleStaticFile(String path) {
  function handleJSON (line 179) | void handleJSON() {

FILE: esp32_3wd_stepper/webinterface.h
  type Config (line 28) | struct Config {

FILE: esp32_config_webinterface_v6/webinterface.cpp
  function String (line 8) | static String getContentType(const String& path) {
  function defaultConfig (line 30) | bool defaultConfig() {
  function loadConfig (line 39) | bool loadConfig() {
  function saveConfig (line 73) | bool saveConfig() {
  function printConfig (line 94) | void printConfig() {
  function printRequest (line 103) | void printRequest() {
  function handleNotFound (line 124) | void handleNotFound() {
  function handleRedirect (line 147) | void handleRedirect(const char * filename) {
  function handleRedirect (line 151) | void handleRedirect(String filename) {
  function handleStaticFile (line 158) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 162) | bool handleStaticFile(String path) {
  function handleJSON (line 176) | void handleJSON() {

FILE: esp32_config_webinterface_v6/webinterface.h
  type Config (line 28) | struct Config {

FILE: esp32_config_webinterface_v7/webinterface.cpp
  function String (line 8) | static String getContentType(const String& path) {
  function defaultConfig (line 30) | bool defaultConfig() {
  function loadConfig (line 39) | bool loadConfig() {
  function saveConfig (line 72) | bool saveConfig() {
  function printConfig (line 93) | void printConfig() {
  function printRequest (line 102) | void printRequest() {
  function handleNotFound (line 123) | void handleNotFound() {
  function handleRedirect (line 146) | void handleRedirect(const char * filename) {
  function handleRedirect (line 150) | void handleRedirect(String filename) {
  function handleStaticFile (line 157) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 161) | bool handleStaticFile(String path) {
  function handleJSON (line 175) | void handleJSON() {

FILE: esp32_config_webinterface_v7/webinterface.h
  type Config (line 28) | struct Config {

FILE: esp32_exgpill/fieldtrip_buffer.cpp
  function fieldtrip_open_connection (line 11) | int fieldtrip_open_connection(const char *address, int port) {
  function fieldtrip_close_connection (line 24) | int fieldtrip_close_connection(int s) {
  function fieldtrip_write_header (line 33) | int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchan...
  function fieldtrip_write_data (line 85) | int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans,...
  function wordsize_from_type (line 133) | int wordsize_from_type(uint32_t datatype) {

FILE: esp32_exgpill/fieldtrip_buffer.h
  type messagedef_t (line 61) | typedef struct {
  type headerdef_t (line 69) | typedef struct {
  type datadef_t (line 80) | typedef struct {
  type eventdef_t (line 89) | typedef struct {

FILE: esp32_exgpill/rgb_led.cpp
  type LedColor (line 13) | enum LedColor {
  type LedState (line 20) | enum LedState {
  function ledInit (line 28) | void ledInit() {
  function ledSet (line 121) | void ledSet(LedColor color, LedState state){
  function toggleLed (line 151) | void toggleLed() {

FILE: esp32_exgpill/webinterface.cpp
  function String (line 13) | static String getContentType(const String& path) {
  function defaultConfig (line 33) | bool defaultConfig() {
  function loadConfig (line 40) | bool loadConfig() {
  function saveConfig (line 74) | bool saveConfig() {
  function printConfig (line 93) | void printConfig() {
  function printRequest (line 100) | void printRequest() {
  function handleNotFound (line 121) | void handleNotFound() {
  function handleRedirect (line 144) | void handleRedirect(const char * filename) {
  function handleRedirect (line 148) | void handleRedirect(String filename) {
  function handleStaticFile (line 155) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 159) | bool handleStaticFile(String path) {
  function handleJSON (line 175) | void handleJSON() {

FILE: esp32_exgpill/webinterface.h
  type Config (line 4) | struct Config {

FILE: esp32_inmp441/RunningStat.h
  function class (line 8) | class RunningStat

FILE: esp32_inmp441/util.h
  function print_binary (line 4) | void print_binary(uint32_t val) {

FILE: esp32_sph0645/RunningStat.h
  function class (line 5) | class RunningStat

FILE: esp32_sph0645/compress.cpp
  function compress (line 3) | uint32_t compress(uint32_t *data, uint32_t nsamples, uint8_t *dest) {
  function decompress (line 14) | uint32_t decompress(uint32_t *dest, uint32_t nsamples, uint8_t *data) {

FILE: esp8266_ad8232_ecg/blink_led.cpp
  function changeState (line 13) | void changeState() {
  function ledInit (line 17) | void ledInit() {
  function ledOn (line 21) | void ledOn() {
  function ledOff (line 29) | void ledOff() {
  function ledSlow (line 37) | void ledSlow() {
  function ledMedium (line 45) | void ledMedium() {
  function ledFast (line 53) | void ledFast() {

FILE: esp8266_ad8232_ecg/fieldtrip_buffer.cpp
  function fieldtrip_open_connection (line 11) | int fieldtrip_open_connection(const char *address, int port) {
  function fieldtrip_close_connection (line 25) | int fieldtrip_close_connection(int s) {
  function fieldtrip_write_header (line 37) | int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchan...
  function fieldtrip_write_data (line 97) | int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans,...
  function wordsize_from_type (line 153) | int wordsize_from_type(uint32_t datatype) {

FILE: esp8266_ad8232_ecg/fieldtrip_buffer.h
  type messagedef_t (line 61) | typedef struct {
  type headerdef_t (line 69) | typedef struct {
  type datadef_t (line 80) | typedef struct {
  type eventdef_t (line 89) | typedef struct {

FILE: esp8266_ad8232_ecg/webinterface.cpp
  function String (line 9) | static String getContentType(const String& path) {
  function defaultConfig (line 31) | bool defaultConfig() {
  function loadConfig (line 39) | bool loadConfig() {
  function saveConfig (line 72) | bool saveConfig() {
  function printRequest (line 94) | void printRequest() {
  function handleUpdate1 (line 115) | void handleUpdate1() {
  function handleUpdate2 (line 122) | void handleUpdate2() {
  function handleDirList (line 147) | void handleDirList() {
  function handleNotFound (line 160) | void handleNotFound() {
  function handleRedirect (line 183) | void handleRedirect(const char * filename) {
  function handleRedirect (line 187) | void handleRedirect(String filename) {
  function handleStaticFile (line 194) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 198) | bool handleStaticFile(String path) {
  function handleJSON (line 212) | void handleJSON() {

FILE: esp8266_ad8232_ecg/webinterface.h
  type Config (line 29) | struct Config {

FILE: esp8266_artnet_neopixel/colorspace.cpp
  function hsv (line 3) | hsv rgb2hsv(rgb in)
  function rgb (line 47) | rgb hsv2rgb(hsv in)

FILE: esp8266_artnet_neopixel/colorspace.h
  type rgb (line 4) | typedef struct {
  type hsv (line 10) | typedef struct {

FILE: esp8266_artnet_neopixel/neopixel_mode.cpp
  function mode0 (line 42) | void mode0(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode1 (line 79) | void mode1(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode2 (line 128) | void mode2(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode3 (line 189) | void mode3(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode4 (line 281) | void mode4(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode5 (line 368) | void mode5(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode6 (line 443) | void mode6(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode7 (line 516) | void mode7(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode8 (line 591) | void mode8(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode9 (line 663) | void mode9(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t...
  function mode10 (line 747) | void mode10(uint16_t universe, uint16_t length, uint8_t sequence, uint8_...
  function mode11 (line 823) | void mode11(uint16_t universe, uint16_t length, uint8_t sequence, uint8_...
  function mode12 (line 858) | void mode12(uint16_t universe, uint16_t length, uint8_t sequence, uint8_...
  function mode13 (line 909) | void mode13(uint16_t universe, uint16_t length, uint8_t sequence, uint8_...
  function singleLed (line 982) | void singleLed(byte r, byte g, byte b, byte w) {
  function singleRed (line 988) | void singleRed() {
  function singleGreen (line 991) | void singleGreen() {
  function singleBlue (line 994) | void singleBlue() {
  function singleYellow (line 997) | void singleYellow() {
  function singleCyan (line 1000) | void singleCyan() {
  function singleMagenta (line 1003) | void singleMagenta() {
  function red (line 1008) | uint8_t red(uint32_t c) {
  function green (line 1011) | uint8_t green(uint32_t c) {
  function blue (line 1014) | uint8_t blue(uint32_t c) {
  function Wheel (line 1019) | uint32_t Wheel(byte WheelPos) {
  function fullRed (line 1033) | void fullRed() {
  function fullGreen (line 1041) | void fullGreen() {
  function fullBlue (line 1048) | void fullBlue() {
  function fullWhite (line 1055) | void fullWhite() {
  function fullBlack (line 1062) | void fullBlack() {
  function colorWipe (line 1070) | void colorWipe(uint8_t wait, uint32_t c) {
  function pulseWhite (line 1078) | void pulseWhite(uint8_t wait) {
  function rainbowFade2White (line 1096) | void rainbowFade2White(uint8_t wait, int rainbowLoops, int whiteLoops) {
  function whiteOverRainbow (line 1144) | void whiteOverRainbow(uint8_t wait, uint8_t whiteSpeed, uint8_t whiteLen...
  function rainbowCycle (line 1180) | void rainbowCycle(uint8_t wait) {
  function rainbow (line 1191) | void rainbow(uint8_t wait) {
  function map_hsv_to_rgb (line 1202) | void map_hsv_to_rgb(int *r, int *g, int *b) {

FILE: esp8266_artnet_neopixel/webinterface.cpp
  function String (line 9) | static String getContentType(const String& path) {
  function defaultConfig (line 31) | bool defaultConfig() {
  function loadConfig (line 48) | bool loadConfig() {
  function saveConfig (line 90) | bool saveConfig() {
  function printRequest (line 120) | void printRequest() {
  function handleUpdate1 (line 141) | void handleUpdate1() {
  function handleUpdate2 (line 148) | void handleUpdate2() {
  function handleDirList (line 173) | void handleDirList() {
  function handleNotFound (line 186) | void handleNotFound() {
  function handleRedirect (line 209) | void handleRedirect(const char * filename) {
  function handleRedirect (line 213) | void handleRedirect(String filename) {
  function handleStaticFile (line 220) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 224) | bool handleStaticFile(String path) {
  function handleJSON (line 238) | void handleJSON() {

FILE: esp8266_artnet_neopixel/webinterface.h
  type Config (line 29) | struct Config {

FILE: esp8266_fieldtrip_buffer/fieldtrip_buffer.cpp
  function fieldtrip_open_connection (line 11) | int fieldtrip_open_connection(const char *address, int port) {
  function fieldtrip_close_connection (line 24) | int fieldtrip_close_connection(int s) {
  function fieldtrip_write_header (line 33) | int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchan...
  function fieldtrip_write_data (line 85) | int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans,...
  function wordsize_from_type (line 133) | int wordsize_from_type(uint32_t datatype) {

FILE: esp8266_fieldtrip_buffer/fieldtrip_buffer.h
  type messagedef_t (line 61) | typedef struct {
  type headerdef_t (line 69) | typedef struct {
  type datadef_t (line 80) | typedef struct {
  type eventdef_t (line 89) | typedef struct {

FILE: esp8266_imu_osc/I2Cscan.cpp
  function I2Cscan (line 6) | void I2Cscan()

FILE: esp8266_imu_osc/rgb_led.cpp
  function ledInit (line 3) | void ledInit() {
  function ledRed (line 17) | void ledRed() {
  function ledGreen (line 23) | void ledGreen() {
  function ledBlue (line 29) | void ledBlue() {
  function ledYellow (line 35) | void ledYellow() {
  function ledMagenta (line 41) | void ledMagenta() {
  function ledCyan (line 47) | void ledCyan() {
  function ledBlack (line 53) | void ledBlack() {
  function ledWhite (line 59) | void ledWhite() {

FILE: esp8266_imu_osc/tca9548a.h
  function class (line 9) | class tca9548a {

FILE: esp8266_imu_osc/webinterface.cpp
  function String (line 10) | static String getContentType(const String& path) {
  function defaultConfig (line 32) | bool defaultConfig() {
  function loadConfig (line 45) | bool loadConfig() {
  function saveConfig (line 85) | bool saveConfig() {
  function printRequest (line 113) | void printRequest() {
  function handleUpdate1 (line 134) | void handleUpdate1() {
  function handleUpdate2 (line 141) | void handleUpdate2() {
  function handleDirList (line 166) | void handleDirList() {
  function handleNotFound (line 179) | void handleNotFound() {
  function handleRedirect (line 202) | void handleRedirect(const char * filename) {
  function handleRedirect (line 206) | void handleRedirect(String filename) {
  function handleStaticFile (line 213) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 217) | bool handleStaticFile(String path) {
  function handleJSON (line 231) | void handleJSON() {
  function handleFileUpload (line 287) | void handleFileUpload() { // upload a new file to the SPIFFS

FILE: esp8266_imu_osc/webinterface.h
  type Config (line 29) | struct Config {

FILE: esp8266_inmp411/RunningStat.h
  function class (line 5) | class RunningStat

FILE: esp8266_polar_wearlink/rgb_led.cpp
  function ledInit (line 3) | void ledInit() {
  function ledRed (line 11) | void ledRed() {
  function ledGreen (line 17) | void ledGreen() {
  function ledBlue (line 23) | void ledBlue() {
  function ledYellow (line 29) | void ledYellow() {
  function ledMagenta (line 35) | void ledMagenta() {
  function ledCyan (line 41) | void ledCyan() {
  function ledBlack (line 47) | void ledBlack() {
  function ledWhite (line 53) | void ledWhite() {
  function ledRed (line 61) | void ledRed() {
  function ledGreen (line 67) | void ledGreen() {
  function ledBlue (line 73) | void ledBlue() {
  function ledYellow (line 79) | void ledYellow() {
  function ledMagenta (line 85) | void ledMagenta() {
  function ledCyan (line 91) | void ledCyan() {
  function ledBlack (line 97) | void ledBlack() {
  function ledWhite (line 103) | void ledWhite() {

FILE: esp8266_polar_wearlink/webinterface.cpp
  function String (line 10) | static String getContentType(const String& path) {
  function defaultConfig (line 32) | bool defaultConfig() {
  function loadConfig (line 41) | bool loadConfig() {
  function saveConfig (line 75) | bool saveConfig() {
  function printRequest (line 97) | void printRequest() {
  function handleUpdate1 (line 118) | void handleUpdate1() {
  function handleUpdate2 (line 125) | void handleUpdate2() {
  function handleDirList (line 150) | void handleDirList() {
  function handleNotFound (line 163) | void handleNotFound() {
  function handleRedirect (line 186) | void handleRedirect(const char * filename) {
  function handleRedirect (line 190) | void handleRedirect(String filename) {
  function handleStaticFile (line 197) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 201) | bool handleStaticFile(String path) {
  function handleJSON (line 215) | void handleJSON() {

FILE: esp8266_polar_wearlink/webinterface.h
  type Config (line 29) | struct Config {

FILE: esp8266_pulse_sensor/blink_led.cpp
  function changeState (line 13) | void changeState() {
  function ledInit (line 17) | void ledInit() {
  function ledOn (line 21) | void ledOn() {
  function ledOff (line 29) | void ledOff() {
  function ledSlow (line 37) | void ledSlow() {
  function ledMedium (line 45) | void ledMedium() {
  function ledFast (line 53) | void ledFast() {

FILE: esp8266_pulse_sensor/fieldtrip_buffer.cpp
  function fieldtrip_open_connection (line 11) | int fieldtrip_open_connection(const char *address, int port) {
  function fieldtrip_close_connection (line 25) | int fieldtrip_close_connection(int s) {
  function fieldtrip_write_header (line 37) | int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchan...
  function fieldtrip_write_data (line 97) | int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans,...
  function wordsize_from_type (line 153) | int wordsize_from_type(uint32_t datatype) {

FILE: esp8266_pulse_sensor/fieldtrip_buffer.h
  type messagedef_t (line 61) | typedef struct {
  type headerdef_t (line 69) | typedef struct {
  type datadef_t (line 80) | typedef struct {
  type eventdef_t (line 89) | typedef struct {

FILE: esp8266_pulse_sensor/webinterface.cpp
  function String (line 9) | static String getContentType(const String& path) {
  function defaultConfig (line 31) | bool defaultConfig() {
  function loadConfig (line 39) | bool loadConfig() {
  function saveConfig (line 72) | bool saveConfig() {
  function printRequest (line 94) | void printRequest() {
  function handleUpdate1 (line 115) | void handleUpdate1() {
  function handleUpdate2 (line 122) | void handleUpdate2() {
  function handleDirList (line 147) | void handleDirList() {
  function handleNotFound (line 160) | void handleNotFound() {
  function handleRedirect (line 183) | void handleRedirect(const char * filename) {
  function handleRedirect (line 187) | void handleRedirect(String filename) {
  function handleStaticFile (line 194) | bool handleStaticFile(const char * path) {
  function handleStaticFile (line 198) | bool handleStaticFile(String path) {
  function handleJSON (line 212) | void handleJSON() {

FILE: esp8266_pulse_sensor/webinterface.h
  type Config (line 29) | struct Config {

FILE: rp2040_dac7578/blink_led.cpp
  function changeState (line 13) | void changeState() {
  function ledInit (line 17) | void ledInit() {
  function ledOn (line 21) | void ledOn() {
  function ledOff (line 29) | void ledOff() {
  function ledSlow (line 37) | void ledSlow() {
  function ledMedium (line 45) | void ledMedium() {
  function ledFast (line 53) | void ledFast() {

FILE: teensy_gps_temp_ttn/payload.h
  type payload_t (line 3) | struct payload_t {

FILE: teensy_gps_temp_ttn/sensor.cpp
  function init_sensor (line 39) | void init_sensor() {
  function read_sensor (line 56) | void read_sensor() {
  function readVcc (line 118) | uint32_t readVcc() {
  function crc_update (line 134) | unsigned long crc_update(unsigned long crc, byte data)
  function crc_buf (line 146) | unsigned long crc_buf(char *b, unsigned long l)
  function crc_string (line 157) | unsigned long crc_string(char *s)

FILE: teensy_midifilter/midiname.c
  type usb_string_descriptor_struct (line 15) | struct usb_string_descriptor_struct

FILE: uno_dac7578/blink_led.cpp
  function ledInit (line 13) | void ledInit() {
  function ledOn (line 18) | void ledOn() {
  function ledOff (line 26) | void ledOff() {
  function ledSlow (line 34) | void ledSlow() {
  function ledMedium (line 42) | void ledMedium() {
  function ledFast (line 50) | void ledFast() {
  function ledFlip (line 58) | void ledFlip() {
Condensed preview — 263 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (694K chars).
[
  {
    "path": ".gitignore",
    "chars": 33,
    "preview": ".DS_Store\n*.swp\nsecret.h\n.vscode\n"
  },
  {
    "path": ".gitmodules",
    "chars": 131,
    "preview": "[submodule \"esp8266_artnet_dmx512\"]\n\tpath = esp8266_artnet_dmx512\n\turl = git@github.com:robertoostenveld/esp8266_artnet_"
  },
  {
    "path": "PulseSensor_v0/AllSerialHandling.cpp",
    "chars": 2541,
    "preview": "\n//////////\n/////////  All Serial Handling Code, \n/////////  It's Changeable with the 'serialVisual' variable\n///////// "
  },
  {
    "path": "PulseSensor_v0/Interrupt.cpp",
    "chars": 5744,
    "preview": "\n\n\nvolatile int rate[10];                    // array to hold last ten IBI values\nvolatile unsigned long sampleCounter ="
  },
  {
    "path": "PulseSensor_v0/PulseSensor_v0.ino",
    "chars": 3086,
    "preview": "\n/*  Pulse Sensor Amped 1.4    by Joel Murphy and Yury Gitman   http://www.pulsesensor.com\n\n----------------------  Note"
  },
  {
    "path": "PulseSensor_v0/README.md",
    "chars": 236,
    "preview": "# Arduino sketch for PulseSensor, see http://pulsesensor.com\n\nThis is the original code from https://github.com/WorldFam"
  },
  {
    "path": "PulseSensor_v0/Timer_Interrupt_Notes.cpp",
    "chars": 7678,
    "preview": "/*\n  These notes put together by Joel Murphy for Pulse Sensor Amped, 2015\n\n  The code that this section is attached to u"
  },
  {
    "path": "PulseSensor_v1/PulseSensor_v1.ino",
    "chars": 1847,
    "preview": "// PulseSensor application that streams continuous data at 500Hz using the OpenEEG data format\n// over the serial port o"
  },
  {
    "path": "PulseSensor_v1/README.md",
    "chars": 676,
    "preview": "# Arduino sketch for PulseSensor, see http://pulsesensor.com\n\nThis reads the analog value of the sensor and sends it ove"
  },
  {
    "path": "PulseSensor_v2/Interrupt.cpp",
    "chars": 5588,
    "preview": "\nvolatile int rate[10];                      // array to hold last ten IBI values\nvolatile unsigned long lastBeatTime = "
  },
  {
    "path": "PulseSensor_v2/PulseSensor_v2.ino",
    "chars": 3014,
    "preview": "#define LSB(x) (byte)(x    & 0xff);\n#define MSB(x) (byte)(x>>8 & 0xff);\n\n#define pulsePin  A0                // Pulse Se"
  },
  {
    "path": "PulseSensor_v2/README.md",
    "chars": 670,
    "preview": "# Arduino sketch for PulseSensor, see http://pulsesensor.com\n\nThis code is derived from the [original code](https://gith"
  },
  {
    "path": "README.md",
    "chars": 1277,
    "preview": "# Arduino electronics hacking\n\nMost of the code here corresponds to electronics hardware that is documented on my [perso"
  },
  {
    "path": "bitsi/bitsi.ino",
    "chars": 5959,
    "preview": "/*\n * BITSI - Bits to Serial Interface\n *\n * This is an Arduino Uno based serial/usb interface that can be used to monit"
  },
  {
    "path": "blenderdefender/blenderdefender.ino",
    "chars": 5680,
    "preview": "#include <JeeLib.h>\n#include <Ports.h>\n\n#define pirPin      9     // the number of the pushbutton pin\n#define redLed    "
  },
  {
    "path": "digispark_skateboard/README.md",
    "chars": 1361,
    "preview": "# Digispark Skateboard\n\nThis is an Arduino sketch to implement a strip of WS2812b Neopixels\nunder a skateboard. It featu"
  },
  {
    "path": "digispark_skateboard/colorspace.cpp",
    "chars": 2108,
    "preview": "#include \"colorspace.h\"\n\nhsv rgb2hsv(rgb in)\n{\n  hsv         out;\n  double      min, max, delta;\n\n  min = in.r < in.g ? "
  },
  {
    "path": "digispark_skateboard/colorspace.h",
    "chars": 324,
    "preview": "#ifndef _COLORSPACE_H_\n#define _COLORSPACE_H_\n\ntypedef struct {\n  double r;       // percent\n  double g;       // percen"
  },
  {
    "path": "digispark_skateboard/digispark_skateboard.ino",
    "chars": 2198,
    "preview": "#include <Arduino.h>\n#include \"neopixel_mode.h\"\n\n// the neopixel strip is connected to pin 0\n#define BUILTIN_LED 1\n#defi"
  },
  {
    "path": "digispark_skateboard/neopixel_mode.cpp",
    "chars": 3235,
    "preview": "/*\n    This is a subset of the color modes supported by my esp8266_artnet_neopixel sketch, see\n    https://github.com/ro"
  },
  {
    "path": "digispark_skateboard/neopixel_mode.h",
    "chars": 925,
    "preview": "#ifndef _NEOPIXEL_MODE_H_\n#define _NEOPIXEL_MODE_H_\n\n#include <Adafruit_NeoPixel.h>\n\n#define PIN            0\n#define NU"
  },
  {
    "path": "eegsynth_cvgate_mcp4725/README.md",
    "chars": 304,
    "preview": "# Arduino sketch for an MCP4725 digital-to-analog converter\n\nThis is a sketch to control Control Voltages (CV) and Gates"
  },
  {
    "path": "eegsynth_cvgate_mcp4725/eegsynth_cvgate_mcp4725.ino",
    "chars": 6025,
    "preview": "/*\n* EEGSynth Arduino based CV/Gate controller. This sketch allows\n* one to use control voltages and gates to interface "
  },
  {
    "path": "eegsynth_cvgate_mcp4822/eegsynth_cvgate_mcp4822.ino",
    "chars": 5927,
    "preview": "/*\n* EEGSynth Arduino based CV/Gate controller. This sketch allows\n* one to use control voltages and gates to interface "
  },
  {
    "path": "eegsynth_devirtualizer/eegsynth_devirtualizer.ino",
    "chars": 8426,
    "preview": "/*\n* EEGSynth Arduino based CV/Gate controller. This sketch allows\n* one to use control voltages and gates to interface "
  },
  {
    "path": "eegsynth_usbdmxpro/COPYING.txt",
    "chars": 1258,
    "preview": "/*==============================================================================\n  Copyright (c) 2013 Soixante circuits\n"
  },
  {
    "path": "eegsynth_usbdmxpro/eegsynth_usbdmxpro.ino",
    "chars": 3102,
    "preview": "/*\n   The purpose of this sketch is to implement a module that converts from USB to DMX512.\n   This allows to use comput"
  },
  {
    "path": "esp32_3wd_servo/README.md",
    "chars": 1303,
    "preview": "# Three-wheel-drive omni-wheel robot platform\n\nThis is an Arduino sketch for an ESP32 (LOLIN32 Lite) controlled\nthree-wh"
  },
  {
    "path": "esp32_3wd_servo/esp32_3wd_servo.ino",
    "chars": 4751,
    "preview": "#include <Arduino.h>\n#include <WiFi.h>\n#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager\n#include <WiFiU"
  },
  {
    "path": "esp32_3wd_stepper/README.md",
    "chars": 3861,
    "preview": "# Three-wheel-drive omni-wheel robot platform\n\nThis is an Arduino sketch for an ESP32 (LOLIN32 Lite) controlled three-wh"
  },
  {
    "path": "esp32_3wd_stepper/blink_led.cpp",
    "chars": 952,
    "preview": "#include \"blink_led.h\"\n\nTicker blinker;\n\nenum {\n  LED_ON,\n  LED_OFF,\n  LED_SLOW,\n  LED_MEDIUM,\n  LED_FAST,\n} ledState;\n\n"
  },
  {
    "path": "esp32_3wd_stepper/blink_led.h",
    "chars": 292,
    "preview": "#ifndef _BLINK_LED_H_\n#define _BLINK_LED_H_\n\n#include <Arduino.h>\n#include <Ticker.h>\n\n#define LED 22 // GPIO22 is conne"
  },
  {
    "path": "esp32_3wd_stepper/data/config.json",
    "chars": 51,
    "preview": "{\n\"repeat\":0,\n\"absolute\":0,\n\"warp\": 1,\n\"debug\":0\n}\n"
  },
  {
    "path": "esp32_3wd_stepper/data/hello.html",
    "chars": 221,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_3wd_stepper/data/index.html",
    "chars": 487,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_3wd_stepper/data/monitor.html",
    "chars": 752,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_3wd_stepper/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_3wd_stepper/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_3wd_stepper/data/settings.html",
    "chars": 1378,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp32_3wd_stepper/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints.html",
    "chars": 2701,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Edit waypoints</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel="
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints1.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints2.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints3.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints4.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints5.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints6.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints7.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/data/waypoints8.csv",
    "chars": 8,
    "preview": "0,0,0,0\n"
  },
  {
    "path": "esp32_3wd_stepper/esp32_3wd_stepper.ino",
    "chars": 13832,
    "preview": "#include <Arduino.h>\n#include <WiFi.h>\n#include <WiFiManager.h>        // https://github.com/tzapu/WiFiManager\n#include "
  },
  {
    "path": "esp32_3wd_stepper/parseosc.cpp",
    "chars": 6391,
    "preview": "#include \"parseosc.h\"\n\n/********************************************************************************/\n\nvoid parseOSC"
  },
  {
    "path": "esp32_3wd_stepper/parseosc.h",
    "chars": 1133,
    "preview": "#ifndef _PARSEOSC_H_\n#define _PARSEOSC_H_\n\n#include <WiFiUdp.h>\n#include <OSCMessage.h>         // https://github.com/CN"
  },
  {
    "path": "esp32_3wd_stepper/stepper.cpp",
    "chars": 2962,
    "preview": "#include \"stepper.h\"\n\n// This implements the ESP32 control for the 28BYJ-48 motor and the ULN2003 driver board.\n//\n// It"
  },
  {
    "path": "esp32_3wd_stepper/stepper.h",
    "chars": 879,
    "preview": "#ifndef _STEPPER_H_\n#define _STEPPER_H_\n\n#include <Arduino.h>\n\nclass Stepper {\n  public:\n    Stepper();\n    void begin(u"
  },
  {
    "path": "esp32_3wd_stepper/util.cpp",
    "chars": 1134,
    "preview": "#include \"util.h\"\n\n/********************************************************************************/\n\nvoid printMacAddr"
  },
  {
    "path": "esp32_3wd_stepper/util.h",
    "chars": 212,
    "preview": "#ifndef _UTIL_H_\n#define _UTIL_H_\n\n#include <Arduino.h>\n#include <WiFi.h>\n#include <esp_wifi.h>\n\nvoid printMacAddress(vo"
  },
  {
    "path": "esp32_3wd_stepper/waypoints.cpp",
    "chars": 3907,
    "preview": "#include \"waypoints.h\"\n\n// read the sequence of waypoints from the CSV file on SPIFFS\n//\n// the file should have no head"
  },
  {
    "path": "esp32_3wd_stepper/waypoints.h",
    "chars": 814,
    "preview": "#ifndef _WAYPOINTS_H_\n#define _WAYPOINTS_H_\n\n#include <FS.h>\n#include <SPIFFS.h>\n#include <vector>\n\nusing namespace std;"
  },
  {
    "path": "esp32_3wd_stepper/webinterface.cpp",
    "chars": 8131,
    "preview": "#include \"webinterface.h\"\n#include \"waypoints.h\"\n\nConfig config;\nextern WebServer server;\n\n/****************************"
  },
  {
    "path": "esp32_3wd_stepper/webinterface.h",
    "chars": 1394,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <WebServer.h>\n#include <ArduinoJson.h> "
  },
  {
    "path": "esp32_config_webinterface_v5/README.md",
    "chars": 1966,
    "preview": "# Overview\n\nThis is a demonstration and test sketch for configuring persistent options via the web interface. This strat"
  },
  {
    "path": "esp32_config_webinterface_v5/data/config.json",
    "chars": 47,
    "preview": "{\n\"var1\":10,\n\"var2\":20,\n\"var3\":30,\n\"var4\":40\n}\n"
  },
  {
    "path": "esp32_config_webinterface_v5/data/hello.html",
    "chars": 215,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v5/data/index.html",
    "chars": 437,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v5/data/monitor.html",
    "chars": 618,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v5/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v5/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v5/data/settings.html",
    "chars": 1129,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp32_config_webinterface_v5/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp32_config_webinterface_v5/esp32_config_webinterface_v5.ino",
    "chars": 9565,
    "preview": "#if not defined(ESP32)\n#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit H"
  },
  {
    "path": "esp32_config_webinterface_v6/README.md",
    "chars": 1971,
    "preview": "# Overview\n\nThis is a demonstration and test sketch for configuring persistent options via the web interface. This strat"
  },
  {
    "path": "esp32_config_webinterface_v6/data/config.json",
    "chars": 36,
    "preview": "{\n\"var1\":10,\n\"var2\":20,\n\"var3\":30\n}\n"
  },
  {
    "path": "esp32_config_webinterface_v6/data/hello.html",
    "chars": 215,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v6/data/index.html",
    "chars": 437,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v6/data/monitor.html",
    "chars": 618,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v6/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v6/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v6/data/settings.html",
    "chars": 1129,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp32_config_webinterface_v6/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp32_config_webinterface_v6/esp32_config_webinterface_v6.ino",
    "chars": 3225,
    "preview": "#if not defined(ESP32)\n#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit H"
  },
  {
    "path": "esp32_config_webinterface_v6/webinterface.cpp",
    "chars": 6275,
    "preview": "#include \"webinterface.h\"\n\nConfig config;\nextern WebServer server;\n\n/***************************************************"
  },
  {
    "path": "esp32_config_webinterface_v6/webinterface.h",
    "chars": 1373,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <WebServer.h>\n#include <ArduinoJson.h> "
  },
  {
    "path": "esp32_config_webinterface_v7/README.md",
    "chars": 1971,
    "preview": "# Overview\n\nThis is a demonstration and test sketch for configuring persistent options via the web interface. This strat"
  },
  {
    "path": "esp32_config_webinterface_v7/data/config.json",
    "chars": 36,
    "preview": "{\n\"var1\":10,\n\"var2\":20,\n\"var3\":30\n}\n"
  },
  {
    "path": "esp32_config_webinterface_v7/data/hello.html",
    "chars": 215,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v7/data/index.html",
    "chars": 437,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_config_webinterface_v7/data/monitor.html",
    "chars": 618,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v7/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v7/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_config_webinterface_v7/data/settings.html",
    "chars": 1129,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp32_config_webinterface_v7/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp32_config_webinterface_v7/esp32_config_webinterface_v7.ino",
    "chars": 3213,
    "preview": "#if not defined(ESP32)\n#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit H"
  },
  {
    "path": "esp32_config_webinterface_v7/webinterface.cpp",
    "chars": 6200,
    "preview": "#include \"webinterface.h\"\n\nConfig config;\nextern WebServer server;\n\n/***************************************************"
  },
  {
    "path": "esp32_config_webinterface_v7/webinterface.h",
    "chars": 1373,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <WebServer.h>\n#include <ArduinoJson.h> "
  },
  {
    "path": "esp32_exgpill/README.md",
    "chars": 1200,
    "preview": "# Overview\n\nThis is an Arduino sketch for an ExGpill connected to a LOLIN32 Lite development board. It features a webint"
  },
  {
    "path": "esp32_exgpill/data/config.json",
    "chars": 42,
    "preview": "{\n\"address\":\"192.168.1.34\",\n\"port\":1972\n}\n"
  },
  {
    "path": "esp32_exgpill/data/hello.html",
    "chars": 225,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_exgpill/data/index.html",
    "chars": 437,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp32_exgpill/data/monitor.html",
    "chars": 740,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_exgpill/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_exgpill/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp32_exgpill/data/settings.html",
    "chars": 944,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp32_exgpill/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp32_exgpill/esp32_exgpill.ino",
    "chars": 6113,
    "preview": "#if not defined(ESP32)\n#error This is a sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit H"
  },
  {
    "path": "esp32_exgpill/fieldtrip_buffer.cpp",
    "chars": 4505,
    "preview": "#include \"fieldtrip_buffer.h\"\n\nWiFiClient client;\n\n#undef DEBUG\n\n/******************************************************"
  },
  {
    "path": "esp32_exgpill/fieldtrip_buffer.h",
    "chars": 3845,
    "preview": "#ifndef __BUFFER_H_\n#define __BUFFER_H_\n\n#include <WiFiClient.h>\n\n// definition of simplified interface functions, not a"
  },
  {
    "path": "esp32_exgpill/rgb_led.cpp",
    "chars": 3002,
    "preview": "Yellow#include \"rgb_led.h\"\n\nTicker blinker;\n\n#ifdef COMMON_ANODE\n  #define LED_ON  LOW\n  #define LED_OFF HIGH\n#else\n  #d"
  },
  {
    "path": "esp32_exgpill/rgb_led.h",
    "chars": 824,
    "preview": "#ifndef _RGB_LED_H_\n#define _RGB_LED_H_\n\n#include <Arduino.h>\n#include <Ticker.h>\n\n// #define COMMON_ANODE\n#define LED_R"
  },
  {
    "path": "esp32_exgpill/webinterface.cpp",
    "chars": 6403,
    "preview": "#include <WebServer.h>\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <FS.h>\n#include <SPIFFS.h>\n\n#include \"webi"
  },
  {
    "path": "esp32_exgpill/webinterface.h",
    "chars": 434,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\nstruct Config {\n  char address[32];\n  int port;\n};\n\nextern Config con"
  },
  {
    "path": "esp32_inmp441/RunningStat.h",
    "chars": 1692,
    "preview": "/*\n   See https://www.johndcook.com/blog/standard_deviation/ and https://www.johndcook.com/blog/skewness_kurtosis/\n*/\n\n#"
  },
  {
    "path": "esp32_inmp441/esp32_inmp441.ino",
    "chars": 9254,
    "preview": "/*\n    Sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32\n    connected to a INMP41"
  },
  {
    "path": "esp32_inmp441/util.h",
    "chars": 539,
    "preview": "#ifndef _UTIL_H_\n#define _UTIL_H_\n\nvoid print_binary(uint32_t val) {\n  for (int l = 31; l >= 24; l--) {\n    uint8_t b = "
  },
  {
    "path": "esp32_sph0645/RunningStat.h",
    "chars": 1263,
    "preview": "/*\n   See https://www.johndcook.com/blog/standard_deviation/\n*/\n\nclass RunningStat\n{\n  public:\n    RunningStat() : m_n(0"
  },
  {
    "path": "esp32_sph0645/compress.cpp",
    "chars": 504,
    "preview": "#include <Arduino.h>\n\nuint32_t compress(uint32_t *data, uint32_t nsamples, uint8_t *dest) {\n  uint32_t nbytes = 0;\n  uin"
  },
  {
    "path": "esp32_sph0645/esp32_sph0645.ino",
    "chars": 8250,
    "preview": "/*\n   Sketch for ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32\n   connected to a SPH0645 I2S"
  },
  {
    "path": "esp32c6_feeder/README.md",
    "chars": 681,
    "preview": "# Pet feeder\n\nThis Arduino sketch is for a battery operated pet feeder based on a XIAO ESP32c6. It uses deep \nsleep to c"
  },
  {
    "path": "esp32c6_feeder/esp32c6_feeder.ino",
    "chars": 4345,
    "preview": "/* \nThis Arduino sketch is for a battery operated pet feeder based on a XIAO ESP32c6. It uses deep \nsleep to conserve po"
  },
  {
    "path": "esp8266_12v_trigger/esp8266_12v_trigger.ino",
    "chars": 3991,
    "preview": "/*\n *  This sketch serves as a 12 volt trigger to switch a NAD-D3020 audio amplifier on and off.\n *  The on and off stat"
  },
  {
    "path": "esp8266_ad8232_ecg/README.md",
    "chars": 1281,
    "preview": "# Overview\n\nThis is an Arduino sketch for an ESP8266 module connected to an AD8232 ECG sensor module. It uses the ADC to"
  },
  {
    "path": "esp8266_ad8232_ecg/blink_led.cpp",
    "chars": 952,
    "preview": "#include \"blink_led.h\"\n\nTicker blinker;\n\nenum {\n  LED_ON,\n  LED_OFF,\n  LED_SLOW,\n  LED_MEDIUM,\n  LED_FAST,\n} ledState;\n\n"
  },
  {
    "path": "esp8266_ad8232_ecg/blink_led.h",
    "chars": 270,
    "preview": "#ifndef _BLINK_LED_H_\n#define _BLINK_LED_H_\n\n#include <Arduino.h>\n#include <Ticker.h>\n\n#define LED 2 // GPIO2 is connect"
  },
  {
    "path": "esp8266_ad8232_ecg/data/config.json",
    "chars": 48,
    "preview": "{\n  \"address\": \"192.168.1.34\",\n  \"port\": 1972\n}\n"
  },
  {
    "path": "esp8266_ad8232_ecg/data/hello.html",
    "chars": 215,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_ad8232_ecg/data/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_ad8232_ecg/data/monitor.html",
    "chars": 618,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_ad8232_ecg/data/reload_failure.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_ad8232_ecg/data/reload_success.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_ad8232_ecg/data/settings.html",
    "chars": 945,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp8266_ad8232_ecg/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:85%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp8266_ad8232_ecg/data/update.html",
    "chars": 417,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Update</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"stylesh"
  },
  {
    "path": "esp8266_ad8232_ecg/esp8266_ad8232_ecg.ino",
    "chars": 7562,
    "preview": "/*\n  This sketch is for an ESP8266 connected to an AD8232 module to record the ECG\n  and to send the continuous signal w"
  },
  {
    "path": "esp8266_ad8232_ecg/fieldtrip_buffer.cpp",
    "chars": 4894,
    "preview": "#include \"fieldtrip_buffer.h\"\n\nWiFiClient client;\n\n#undef DEBUG\n\n/******************************************************"
  },
  {
    "path": "esp8266_ad8232_ecg/fieldtrip_buffer.h",
    "chars": 3845,
    "preview": "#ifndef __BUFFER_H_\n#define __BUFFER_H_\n\n#include <WiFiClient.h>\n\n// definition of simplified interface functions, not a"
  },
  {
    "path": "esp8266_ad8232_ecg/webinterface.cpp",
    "chars": 7557,
    "preview": "#include \"webinterface.h\"\n#include \"blink_led.h\"\n\nConfig config;\nextern ESP8266WebServer server;\n\n/*********************"
  },
  {
    "path": "esp8266_ad8232_ecg/webinterface.h",
    "chars": 1406,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <string.h>\n#in"
  },
  {
    "path": "esp8266_artnet_bci/esp8266_artnet_bci.ino",
    "chars": 8684,
    "preview": "#include <ESP8266WiFi.h>         // https://github.com/esp8266/Arduino\n#include <ESP8266WebServer.h>\n#include <ESP8266mD"
  },
  {
    "path": "esp8266_artnet_neopixel/README.md",
    "chars": 6412,
    "preview": "# Overview\n\nThis is an Arduino sketch for an ESP8266 module connected to a neopixel LED strip. It uses ArtNet to control"
  },
  {
    "path": "esp8266_artnet_neopixel/colorspace.cpp",
    "chars": 2108,
    "preview": "#include \"colorspace.h\"\n\nhsv rgb2hsv(rgb in)\n{\n  hsv         out;\n  double      min, max, delta;\n\n  min = in.r < in.g ? "
  },
  {
    "path": "esp8266_artnet_neopixel/colorspace.h",
    "chars": 337,
    "preview": "#ifndef _COLORSPACE_H_\n#define _COLORSPACE_H_\n\ntypedef struct {\n    double r;       // percent\n    double g;       // pe"
  },
  {
    "path": "esp8266_artnet_neopixel/data/hello.html",
    "chars": 213,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_artnet_neopixel/data/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_artnet_neopixel/data/monitor.html",
    "chars": 859,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_artnet_neopixel/data/reload_failure.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_artnet_neopixel/data/reload_success.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_artnet_neopixel/data/settings.html",
    "chars": 2366,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp8266_artnet_neopixel/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:85%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp8266_artnet_neopixel/data/update.html",
    "chars": 417,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Update</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"stylesh"
  },
  {
    "path": "esp8266_artnet_neopixel/esp8266_artnet_neopixel.ino",
    "chars": 7357,
    "preview": "/*\n  This sketch receive a DMX universes via Artnet to control a\n  strip of ws2811 leds via Adafruit's NeoPixel library:"
  },
  {
    "path": "esp8266_artnet_neopixel/font8x8_basic.h",
    "chars": 9808,
    "preview": "/** \n * 8x8 monochrome bitmap fonts for rendering\n * Author: Daniel Hepper <daniel@hepper.net>\n * \n * Modified from http"
  },
  {
    "path": "esp8266_artnet_neopixel/neopixel_mode.cpp",
    "chars": 35877,
    "preview": "#include \"neopixel_mode.h\"\n#include \"webinterface.h\"\n#include \"colorspace.h\"\n#include \"font8x8_basic.h\"\n\nextern Adafruit"
  },
  {
    "path": "esp8266_artnet_neopixel/neopixel_mode.h",
    "chars": 4259,
    "preview": "#ifndef _MODE_H_\n#define _MODE_H_\n\n#include <Arduino.h>\n#include <ESP8266WiFi.h>\n#include <WiFiUdp.h>\n#include <ArtnetWi"
  },
  {
    "path": "esp8266_artnet_neopixel/webinterface.cpp",
    "chars": 9186,
    "preview": "#include \"webinterface.h\"\n\nConfig config;\nextern ESP8266WebServer server;\nextern int packetCounter;\n\n/******************"
  },
  {
    "path": "esp8266_artnet_neopixel/webinterface.h",
    "chars": 1545,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <string.h>\n#in"
  },
  {
    "path": "esp8266_config_spiffs/data/config.json",
    "chars": 36,
    "preview": "{\n\"var1\":10,\n\"var2\":20,\n\"var3\":30\n}\n"
  },
  {
    "path": "esp8266_config_spiffs/esp8266_config_spiffs.ino",
    "chars": 2403,
    "preview": "#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <FS.h>\n\n#if defined(ESP32)\n#include <SPIFFS.h>\n#endif\n\n#ifndef AR"
  },
  {
    "path": "esp8266_config_webinterface/README.md",
    "chars": 1986,
    "preview": "# Overview\n\nThis is a demonstration and test sketch for configuring persistent options via the web interface. This strat"
  },
  {
    "path": "esp8266_config_webinterface/data/config.json",
    "chars": 47,
    "preview": "{\n\"var1\":10,\n\"var2\":20,\n\"var3\":30,\n\"var4\":40\n}\n"
  },
  {
    "path": "esp8266_config_webinterface/data/hello.html",
    "chars": 213,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_config_webinterface/data/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_config_webinterface/data/monitor.html",
    "chars": 618,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_config_webinterface/data/reload_failure.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_config_webinterface/data/reload_success.html",
    "chars": 289,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_config_webinterface/data/settings.html",
    "chars": 1129,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp8266_config_webinterface/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:95%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp8266_config_webinterface/data/update.html",
    "chars": 416,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Update</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"stylesh"
  },
  {
    "path": "esp8266_config_webinterface/esp8266_config_webinterface.ino",
    "chars": 10489,
    "preview": "\n#include <Arduino.h>\n#include <ArduinoJson.h>          // https://arduinojson.org\n#include <ESP8266WiFi.h>          // "
  },
  {
    "path": "esp8266_fan_control/esp8266_fan_control.ino",
    "chars": 1391,
    "preview": "/*\n  This sketch is for an ESP8266 connected to an ARCTIC BioniX F140\n  140 mm diameter fan with PWM control.\n\n  See htt"
  },
  {
    "path": "esp8266_fieldtrip_buffer/esp8266_fieldtrip_buffer.ino",
    "chars": 2083,
    "preview": "#include <ESP8266WiFi.h>\n\n#include \"secret.h\"\n#include \"fieldtrip_buffer.h\"\n\nint ftserver = 0, status = 0;\n\n#define NCHA"
  },
  {
    "path": "esp8266_fieldtrip_buffer/fieldtrip_buffer.cpp",
    "chars": 4505,
    "preview": "#include \"fieldtrip_buffer.h\"\n\nWiFiClient client;\n\n#undef DEBUG\n\n/******************************************************"
  },
  {
    "path": "esp8266_fieldtrip_buffer/fieldtrip_buffer.h",
    "chars": 3845,
    "preview": "#ifndef __BUFFER_H_\n#define __BUFFER_H_\n\n#include <WiFiClient.h>\n\n// definition of simplified interface functions, not a"
  },
  {
    "path": "esp8266_imu_osc/I2Cscan.cpp",
    "chars": 964,
    "preview": "#include <Arduino.h>\n#include <Wire.h>\n\n#include \"I2Cscan.h\"\n\nvoid I2Cscan()\n{\n  // scan for i2c devices\n  byte error, a"
  },
  {
    "path": "esp8266_imu_osc/I2Cscan.h",
    "chars": 69,
    "preview": "#ifndef __I2CSCAN_H__\n#define __I2CSCAN_H__\n\nvoid I2Cscan();\n\n#endif\n"
  },
  {
    "path": "esp8266_imu_osc/README.md",
    "chars": 1273,
    "preview": "# Overview\n\nThis is an Arduino sketch for an ESP8266 module that is connected to a number of inertial motion units (IMUs"
  },
  {
    "path": "esp8266_imu_osc/data/config.json",
    "chars": 159,
    "preview": "{\n\t\"sensors\": 8,\n\t\"decimate\": 1,\n\t\"calibrate\": 0,\n\t\"raw\": 1,\n\t\"ahrs\": 0,\n\t\"quaternion\": 0,\n\t\"termperature\": 0,\n\t\"destina"
  },
  {
    "path": "esp8266_imu_osc/data/hello.html",
    "chars": 217,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_imu_osc/data/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_imu_osc/data/monitor.html",
    "chars": 729,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_imu_osc/data/reload_failure.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_imu_osc/data/reload_success.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_imu_osc/data/settings.html",
    "chars": 2124,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Settings</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"style"
  },
  {
    "path": "esp8266_imu_osc/data/style.css",
    "chars": 716,
    "preview": ".c{\n    text-align: center;\n}\ndiv,input{\n    padding:5px;font-size:1em;\n}\ninput{\n    width:85%;\n}\nbody{\n    text-align: "
  },
  {
    "path": "esp8266_imu_osc/data/update.html",
    "chars": 417,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Update</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"stylesh"
  },
  {
    "path": "esp8266_imu_osc/esp8266_imu_osc.ino",
    "chars": 20755,
    "preview": "/*\n  This sketch is for an ESP8266 connected to an TCA9548A I2C multiplexer,\n  which in turn connects to multiple MPU925"
  },
  {
    "path": "esp8266_imu_osc/rgb_led.cpp",
    "chars": 1166,
    "preview": "#include \"rgb_led.h\"\n\nvoid ledInit() {\n  pinMode(LED_R, OUTPUT);\n  pinMode(LED_G, OUTPUT);\n  pinMode(LED_B, OUTPUT);\n}\n\n"
  },
  {
    "path": "esp8266_imu_osc/rgb_led.h",
    "chars": 299,
    "preview": "#ifndef _RGB_LED_H_\n#define _RGB_LED_H_\n\n#include <Arduino.h>\n\n#define LED_R D5\n#define LED_G D6\n#define LED_B D7\n\n// #d"
  },
  {
    "path": "esp8266_imu_osc/tca9548a.cpp",
    "chars": 398,
    "preview": "#include \"tca9548a.h\"\n\ntca9548a::tca9548a(void) {\n}\n\ntca9548a::~tca9548a(void) {\n}\n\nvoid tca9548a::select(uint8_t channe"
  },
  {
    "path": "esp8266_imu_osc/tca9548a.h",
    "chars": 287,
    "preview": "#ifndef __TCA9548A_H_\n#define __TCA9548A_H_\n\n#include <Arduino.h>\n#include <Wire.h>\n\n#define TCA9548A_ADDRESS    0x70\n\nc"
  },
  {
    "path": "esp8266_imu_osc/webinterface.cpp",
    "chars": 10412,
    "preview": "#include \"webinterface.h\"\n#include \"rgb_led.h\"\n\nConfig config;\nextern ESP8266WebServer server;\nextern unsigned long pack"
  },
  {
    "path": "esp8266_imu_osc/webinterface.h",
    "chars": 1624,
    "preview": "#ifndef _WEBINTERFACE_H_\n#define _WEBINTERFACE_H_\n\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <string.h>\n#in"
  },
  {
    "path": "esp8266_inmp411/RunningStat.h",
    "chars": 1263,
    "preview": "/*\n   See https://www.johndcook.com/blog/standard_deviation/\n*/\n\nclass RunningStat\n{\n  public:\n    RunningStat() : m_n(0"
  },
  {
    "path": "esp8266_inmp411/esp8266_inmp411.ino",
    "chars": 4586,
    "preview": "/*\n   Sketch for ESP8266 board, like the WEMOS D1 mini pro\n   connected to a INMP411 I2S microphone\n\n   The *internal GP"
  },
  {
    "path": "esp8266_p1_thingspeak/esp8266_p1_thingspeak.ino",
    "chars": 13214,
    "preview": "/*\n   This sketch is designed for the P1 port of an ISKRA AM500 smart energy meter.\n   It runs on a Wemos D1 mini PRO th"
  },
  {
    "path": "esp8266_polar_wearlink/README.md",
    "chars": 1071,
    "preview": "# Overview\n\nThis is designed to link https://www.adafruit.com/product/1077 to http://www.eegsynth.org\n\n## SPIFFS for sta"
  },
  {
    "path": "esp8266_polar_wearlink/data/config.json",
    "chars": 62,
    "preview": "{\n\t\"redis\": \"192.168.1.34\",\n\t\"port\": 6379,\n\t\"duration\": 100\n}\n"
  },
  {
    "path": "esp8266_polar_wearlink/data/hello.html",
    "chars": 221,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Hello</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_polar_wearlink/data/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Index</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styleshe"
  },
  {
    "path": "esp8266_polar_wearlink/data/monitor.html",
    "chars": 748,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Monitor</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_polar_wearlink/data/reload_failure.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Failure</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  },
  {
    "path": "esp8266_polar_wearlink/data/reload_success.html",
    "chars": 290,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Success</title>\n<meta charset=\"utf-8\">\n<link href=\"style.css\" rel=\"styles"
  }
]

// ... and 63 more files (download for full content)

About this extraction

This page contains the full source code of the robertoostenveld/arduino GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 263 files (631.7 KB), approximately 198.0k tokens, and a symbol index with 334 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!