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 #include #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 #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 #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 //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 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 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 #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 #include #include // https://github.com/tzapu/WiFiManager #include #include // https://github.com/CNMAT/OSC #include #include 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 #include #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 ================================================ Hello

Hello tortoise!

================================================ FILE: esp32_3wd_stepper/data/index.html ================================================ Index Hello
Monitor
Change settings
Edit waypoints
Reconnect WiFi
Default settings
Restart hardware
================================================ FILE: esp32_3wd_stepper/data/monitor.html ================================================ Monitor

Monitor

Uptime:
?
Firmware version:
?
MAC address:
?
================================================ FILE: esp32_3wd_stepper/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp32_3wd_stepper/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp32_3wd_stepper/data/settings.html ================================================ Settings

Settings





================================================ 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 ================================================ Edit waypoints

Edit waypoints









================================================ 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 #include #include // https://github.com/tzapu/WiFiManager #include #include #include #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 #include // https://github.com/CNMAT/OSC #include // 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 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 #include #include 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 waypoints_t; vector waypoints_x; vector waypoints_y; vector 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 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 #include #include using namespace std; extern vector waypoints_t; extern vector waypoints_x; extern vector waypoints_y; extern vector 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 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 #include #include // https://arduinojson.org #include #include #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 ================================================ Hello

Hello panda!

================================================ FILE: esp32_config_webinterface_v5/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Restart hardware
================================================ FILE: esp32_config_webinterface_v5/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp32_config_webinterface_v5/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp32_config_webinterface_v5/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp32_config_webinterface_v5/data/settings.html ================================================ Settings

Settings

================================================ 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 #include // https://arduinojson.org #include #include // https://github.com/tzapu/WiFiManager #include #include #include #include #include #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 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 ================================================ Hello

Hello panda!

================================================ FILE: esp32_config_webinterface_v6/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Restart hardware
================================================ FILE: esp32_config_webinterface_v6/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp32_config_webinterface_v6/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp32_config_webinterface_v6/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp32_config_webinterface_v6/data/settings.html ================================================ Settings

Settings

================================================ 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 #include // https://arduinojson.org #include #include // https://github.com/tzapu/WiFiManager #include #include #include #include #include #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 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 #include #include // https://arduinojson.org #include #include #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 ================================================ Hello

Hello panda!

================================================ FILE: esp32_config_webinterface_v7/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Restart hardware
================================================ FILE: esp32_config_webinterface_v7/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp32_config_webinterface_v7/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp32_config_webinterface_v7/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp32_config_webinterface_v7/data/settings.html ================================================ Settings

Settings

================================================ 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 #include // https://arduinojson.org #include #include // https://github.com/tzapu/WiFiManager #include #include #include #include #include #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 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 #include #include // https://arduinojson.org #include #include #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(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif // _WEBINTERFACE_H_ ================================================ FILE: esp32_exgpill/README.md ================================================ # Overview This is an Arduino sketch for an ExGpill connected to a LOLIN32 Lite development board. It features a webinterface that allows to change the configuration and it streams the EMG/ECG/EEG data as a FieldTrip buffer client to a server elsewhere on the wifi network. It is compatible with the [EEGsynth](https://www.eegsynth.org). ## 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 ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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_exgpill/data/config.json ================================================ { "address":"192.168.1.34", "port":1972 } ================================================ FILE: esp32_exgpill/data/hello.html ================================================ Hello

Hello arctic fox!

================================================ FILE: esp32_exgpill/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Restart hardware
================================================ FILE: esp32_exgpill/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
Samples transferred:
?
================================================ FILE: esp32_exgpill/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp32_exgpill/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp32_exgpill/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp32_exgpill/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_exgpill/esp32_exgpill.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 #include #include #include // https://github.com/tzapu/WiFiManager #include #include #include #include // https://github.com/sstaub/Ticker #include #include #include "fieldtrip_buffer.h" #include "rgb_led.h" #include "webinterface.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); Ticker sampler; // when SERIAL_PLOTTER is defined the ADC value is printed for each sample and some of the other debugging output is disabled #define SERIAL_PLOTTER #define ADC 33 // https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ #define NCHANS 1 #define FSAMPLE 250 #define BLOCKSIZE (FSAMPLE/10) const char* host = "EXGPILL"; const char* version = __DATE__ " / " __TIME__; unsigned int sample = 0, block = 0, total = 0; bool flush0 = false, flush1 = false; uint16_t block0[BLOCKSIZE], block1[BLOCKSIZE]; int ftserver = 0, status = 0; void getSample() { if (sample == BLOCKSIZE) { // switch to the start of the other block switch (block) { case 0: sample = 0; flush0 = true; block = 1; break; case 1: sample = 0; flush1 = true; block = 0; break; } } // get the current ExG value, it is a 12-bits ADC with a value between 0 and 4095 uint16_t value = analogRead(ADC); #ifdef SERIAL_PLOTTER Serial.println(value); #endif // store it in the active block switch (block) { case 0: block0[sample++] = value; break; case 1: block1[sample++] = value; break; } } void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.println(""); Serial.println("Setup start"); ledInit(); ledRed(); SPIFFS.begin(); loadConfig(); 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"); ledGreen(); // 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(); 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); Serial.println("connected"); server.begin(); }); 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["address"] = config.address; root["port"] = config.port; root["total"] = total; 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 ); server.begin(); MDNS.begin(host); MDNS.addService("http", "tcp", 80); Serial.print("Connecting to "); Serial.print(config.address); Serial.print(" on port "); Serial.println(config.port); ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) { Serial.println("Connection opened"); status = fieldtrip_write_header(ftserver, DATATYPE_UINT16, NCHANS, FSAMPLE); if (status == 0) Serial.println("Wrote header"); else Serial.println("Failed writing header"); } else { Serial.println("Failed opening connection"); } // start sampling the ExG sampler.attach_ms(1000 / FSAMPLE, getSample); Serial.println("Setup done"); } void loop() { server.handleClient(); byte *ptr = NULL; if (flush0) { // block0 is ready to be sent ptr = (byte *)block0; flush0 = false; } else if (flush1) { // block1 is ready to be sent ptr = (byte *)block1; flush1 = false; } if (ptr) { if (ftserver == 0) { ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) Serial.println("Connection opened"); else Serial.println("Failed opening connection"); } if (ftserver > 0) { status = fieldtrip_write_data(ftserver, DATATYPE_UINT16, NCHANS, BLOCKSIZE, ptr); if (status == 0) { total += BLOCKSIZE; #ifndef SERIAL_PLOTTER Serial.print("Wrote "); Serial.print(total); Serial.println(" samples"); #endif } else { Serial.println("Failed writing data"); status = fieldtrip_close_connection(ftserver); if (status == 0) Serial.println("Connection closed"); else Serial.println("Failed closing connection"); ftserver = 0; } } } delay(10); // in milliseconds } ================================================ FILE: esp32_exgpill/fieldtrip_buffer.cpp ================================================ #include "fieldtrip_buffer.h" WiFiClient client; #undef DEBUG /******************************************************************************* OPEN CONNECTION returns file descriptor that should be >0 on success *******************************************************************************/ int fieldtrip_open_connection(const char *address, int port) { int status; status = client.connect(address, port); if (status == 1) return 1; else return -1; }; /******************************************************************************* CLOSE CONNECTION returns 0 on success *******************************************************************************/ int fieldtrip_close_connection(int s) { client.stop(); return 0; }; /******************************************************************************* WRITE HEADER returns 0 on success *******************************************************************************/ int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; headerdef_t *header = NULL; byte msg[8+24]; for (int i = 0; i < 8+24; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_HDR_NORESPONSE; request->bufsize = sizeof(headerdef_t); header = (headerdef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize header->nchans = nchans; header->nsamples = 0; header->nevents = 0; header->fsample = fsample; header->data_type = datatype; header->bufsize = 0; #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(headerdef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(headerdef_t)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n == sizeof(messagedef_t) + sizeof(headerdef_t)) return 0; else return -1; }; /******************************************************************************* WRITE DATA returns 0 on success *******************************************************************************/ int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; datadef_t *data = NULL; byte msg[8+16]; for (int i = 0; i < 8+16; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_DAT_NORESPONSE; request->bufsize = sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype); data = (datadef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize data->nchans = nchans; data->nsamples = nsamples; data->data_type = datatype; data->bufsize = nchans * nsamples * wordsize_from_type(datatype); #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(datadef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(datadef_t)); client.flush(); n += client.write(buffer, nchans * nsamples * wordsize_from_type(datatype)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n == sizeof(messagedef_t) + sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype)) return 0; else return -1; }; int wordsize_from_type(uint32_t datatype) { int wordsize = 0; switch (datatype) { case DATATYPE_CHAR: wordsize = 1; break; case DATATYPE_UINT8: wordsize = 1; break; case DATATYPE_UINT16: wordsize = 2; break; case DATATYPE_UINT32: wordsize = 4; break; case DATATYPE_UINT64: wordsize = 8; break; case DATATYPE_INT8: wordsize = 1; break; case DATATYPE_INT16: wordsize = 2; break; case DATATYPE_INT32: wordsize = 4; break; case DATATYPE_INT64: wordsize = 8; break; case DATATYPE_FLOAT32: wordsize = 4; break; case DATATYPE_FLOAT64: wordsize = 8; break; } return wordsize; }; ================================================ FILE: esp32_exgpill/fieldtrip_buffer.h ================================================ #ifndef __BUFFER_H_ #define __BUFFER_H_ #include // definition of simplified interface functions, not all of them are implemented int fieldtrip_start_server(int port); int fieldtrip_open_connection(const char *hostname, int port); int fieldtrip_close_connection(int s); int fieldtrip_read_header(int server, uint32_t *datatype, uint32_t *nchans, float *fsample, uint32_t *nsamples, uint32_t *nevents); int fieldtrip_read_data(int server, uint32_t begsample, uint32_t endsample, byte *buffer); int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample); int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer); int fieldtrip_wait_data(int server, uint32_t nsamples, uint32_t nevents, uint32_t milliseconds); int wordsize_from_type(uint32_t datatype); // define the version of the message packet #define VERSION (uint16_t)0x0001 // these define the commands that can be used, which are split over the two available bytes #define PUT_HDR (uint16_t)0x0101 /* decimal 257 */ #define PUT_DAT (uint16_t)0x0102 /* decimal 258 */ #define PUT_EVT (uint16_t)0x0103 /* decimal 259 */ #define PUT_OK (uint16_t)0x0104 /* decimal 260 */ #define PUT_ERR (uint16_t)0x0105 /* decimal 261 */ #define GET_HDR (uint16_t)0x0201 /* decimal 513 */ #define GET_DAT (uint16_t)0x0202 /* decimal 514 */ #define GET_EVT (uint16_t)0x0203 /* decimal 515 */ #define GET_OK (uint16_t)0x0204 /* decimal 516 */ #define GET_ERR (uint16_t)0x0205 /* decimal 517 */ #define FLUSH_HDR (uint16_t)0x0301 /* decimal 769 */ #define FLUSH_DAT (uint16_t)0x0302 /* decimal 770 */ #define FLUSH_EVT (uint16_t)0x0303 /* decimal 771 */ #define FLUSH_OK (uint16_t)0x0304 /* decimal 772 */ #define FLUSH_ERR (uint16_t)0x0305 /* decimal 773 */ #define WAIT_DAT (uint16_t)0x0402 /* decimal 1026 */ #define WAIT_OK (uint16_t)0x0404 /* decimal 1027 */ #define WAIT_ERR (uint16_t)0x0405 /* decimal 1028 */ #define PUT_HDR_NORESPONSE (uint16_t)0x0501 /* decimal 1281 */ #define PUT_DAT_NORESPONSE (uint16_t)0x0502 /* decimal 1282 */ #define PUT_EVT_NORESPONSE (uint16_t)0x0503 /* decimal 1283 */ // these are used in the data_t and event_t structure #define DATATYPE_CHAR (uint32_t)0 #define DATATYPE_UINT8 (uint32_t)1 #define DATATYPE_UINT16 (uint32_t)2 #define DATATYPE_UINT32 (uint32_t)3 #define DATATYPE_UINT64 (uint32_t)4 #define DATATYPE_INT8 (uint32_t)5 #define DATATYPE_INT16 (uint32_t)6 #define DATATYPE_INT32 (uint32_t)7 #define DATATYPE_INT64 (uint32_t)8 #define DATATYPE_FLOAT32 (uint32_t)9 #define DATATYPE_FLOAT64 (uint32_t)10 // a packet that is sent over the network should contain the following typedef struct { uint16_t version; // see VERSION uint16_t command; // see PUT_xxx, GET_xxx and FLUSH_xxx uint32_t bufsize; // size of the buffer in bytes } messagedef_t; // 8 bytes // the header definition is fixed, it can be followed by additional header chunks typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t nevents; float fsample; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } headerdef_t; // 24 bytes // the data definition is fixed, it should be followed by additional data typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } datadef_t; // 16 bytes // the event definition is fixed, it should be followed by additional data typedef struct { uint32_t type_type; /* usual would be DATATYPE_CHAR */ uint32_t type_numel; /* length of the type string */ uint32_t value_type; uint32_t value_numel; int32_t sample; int32_t offset; int32_t duration; uint32_t bufsize; /* size of the buffer in bytes */ } eventdef_t; // 64 bytes #endif ================================================ FILE: esp32_exgpill/rgb_led.cpp ================================================ Yellow#include "rgb_led.h" Ticker blinker; #ifdef COMMON_ANODE #define LED_ON LOW #define LED_OFF HIGH #else #define LED_ON HIGH #define LED_OFF LOW #endif enum LedColor { RED, GREEN, BLUE, WHITE } ledColor; enum LedState { OFF, ON, SLOW, MEDIUM, FAST } ledState; void ledInit() { pinMode(LED_R, OUTPUT); pinMode(LED_G, OUTPUT); pinMode(LED_B, OUTPUT); } void ledBlack() ledSet(WHITE, OFF); void ledRed() ledSet(RED, ON); void ledRedSlow() ledSet(RED, SLOW); void ledRedMedium() ledSet(RED, MEDIUM); void ledRedFast() ledSet(RED, FAST); void ledGreen() ledSet(GREEN, ON); void ledGreenSlow() ledSet(GREEN, SLOW); void ledGreenMedium() ledSet(GREEN, MEDIUM); void ledGreenFast() ledSet(GREEN, FAST); void ledBlue() ledSet(BLUE, ON); void ledBlueSlow() ledSet(BLUE, SLOW); void ledBlueMedium() ledSet(BLUE, MEDIUM); void ledBlueFast() ledSet(BLUE, FAST); void ledYellow() ledSet(YELLOW, ON); void ledYellowSlow() ledSet(YELLOW, SLOW); void ledYellowMedium() ledSet(YELLOW, MEDIUM); void ledYellowFast() ledSet(YELLOW, FAST); void ledMagenta() ledSet(MAGENTA, ON); void ledMagentaSlow() ledSet(MAGENTA, SLOW); void ledMagentaMedium() ledSet(MAGENTA, MEDIUM); void ledMagentaFast() ledSet(MAGENTA, FAST); void ledCyan() ledSet(CYAN, ON); void ledCyanSlow() ledSet(CYAN, SLOW); void ledCyanMedium() ledSet(CYAN, MEDIUM); void ledCyanFast() ledSet(CYAN, FAST); void ledWhite() ledSet(WHITE, ON); void ledWhiteSlow() ledSet(WHITE, SLOW); void ledWhiteMedium() ledSet(WHITE, MEDIUM); void ledWhiteFast() ledSet(WHITE, FAST); void ledSet(LedColor color, LedState state){ if (ledColor!=color || ledState!=state) { // remember the color and state, required to toggle ledColor = color; ledState = state; // switch all off digitalWrite(LED_R, LED_OFF); digitalWrite(LED_G, LED_OFF); digitalWrite(LED_B, LED_OFF); // set the initial color if (ledColor==RED || ledColor==YELLOW || ledColor==MAGENTA || ledColor=WHITE) digitalWrite(LED_R, LED_ON); if (ledColor==GREEN || ledColor==YELLOW || ledColor==CYAN || ledColor=WHITE) digitalWrite(LED_G, LED_ON); if (ledColor==BLUE || ledColor==CYAN || ledColor==MAGENTA || ledColor=WHITE) digitalWrite(LED_B, LED_ON); // attach the blinker blinker.detach(); if (ledState==SLOW) blinker.attach_ms(1000, toggleLed); else if (ledState==MEDIUM) blinker.attach_ms(250, toggleLed); else if (ledState==FAST) blinker.attach_ms(100, toggleLed); } } void toggleLed() { if (ledColor==RED || ledColor==YELLOW || ledColor==MAGENTA || ledColor=WHITE) digitalWrite(LED_R, !(digitalRead(LED))); if (ledColor==GREEN || ledColor==YELLOW || ledColor==CYAN || ledColor=WHITE) digitalWrite(LED_G, !(digitalRead(LED_G))); if (ledColor==BLUE || ledColor==CYAN || ledColor==MAGENTA || ledColor=WHITE) digitalWrite(LED_B, !(digitalRead(LED_B))); } ================================================ FILE: esp32_exgpill/rgb_led.h ================================================ #ifndef _RGB_LED_H_ #define _RGB_LED_H_ #include #include // #define COMMON_ANODE #define LED_R 12 #define LED_G 14 #define LED_B 27 void ledInit(); void ledBlack(); // constant color void ledRed(); void ledGreen(); void ledBlue(); void ledYellow(); void ledMagenta(); void ledCyan(); void ledWhite(); // blink every 1000, 250 or 100 ms void ledRedSlow(); void ledRedMedium(); void ledRedFast(); void ledGreenSlow(); void ledGreenMedium(); void ledGreenFast(); void ledBlueSlow(); void ledBleMedium(); void ledBlueFast(); void ledYellowSlow(); void ledYellowMedium(); void ledYellowFast(); void ledMagentaSlow(); void ledMagentaMedium(); void ledMagentaFast(); void ledCyanSlow(); void ledCyanMedium(); void ledCyanFast(); void ledWhiteSlow(); void ledWhiteMedium(); void ledWhiteFast(); #endif ================================================ FILE: esp32_exgpill/webinterface.cpp ================================================ #include #include #include #include #include #include "webinterface.h" Config config; extern WebServer server; extern unsigned int total; 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"); strncpy(config.address, "192.168.1.34", 32); config.port = 1972; 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 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("address")) strncpy(config.address, root["address"], 32); if (root.containsKey("port")) config.port = root["port"]; printConfig(); return true; } bool saveConfig() { Serial.println("saveConfig"); printConfig(); StaticJsonBuffer<200> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["address"] = config.address; root["port"] = config.port; 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("address = "); Serial.println(config.address); Serial.print("port = "); Serial.println(config.port); } 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("address") || server.hasArg("port") || 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("address")) { str = server.arg("address"); strncpy(config.address, str.c_str(), 32); } if (server.hasArg("port")) { str = server.arg("port"); config.port = 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("address")) strncpy(config.address, root["address"], 32); if (root.containsKey("port")) config.port = root["port"]; handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; } saveConfig(); } ================================================ FILE: esp32_exgpill/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ struct Config { char address[32]; int port; }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void printConfig(void); void printRequest(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif ================================================ FILE: esp32_inmp441/RunningStat.h ================================================ /* See https://www.johndcook.com/blog/standard_deviation/ and https://www.johndcook.com/blog/skewness_kurtosis/ */ #ifndef _RUNNINGSTAT_H_ #define _RUNNINGSTAT_H_ class RunningStat { public: RunningStat() : m_n(0) {} void Clear() { m_n = 0; m_prev = 0; m_crossing = 0; } void Push(double x) { m_n++; // see https://en.wikipedia.org/wiki/Zero-crossing_rate m_crossing += ((x > 0) ^ (m_prev > 0)); // bitwise xor m_prev = x; // See Knuth TAOCP vol 2, 3rd edition, page 232 if (m_n == 1) { m_oldM = m_newM = x; m_oldS = 0.0; } else { m_newM = m_oldM + (x - m_oldM) / m_n; m_newS = m_oldS + (x - m_oldM) * (x - m_newM); // set up for next iteration m_oldM = m_newM; m_oldS = m_newS; } if (m_n == 1) { m_min = x; m_max = x; } else { m_min = (x < m_min ? x : m_min); m_max = (x > m_max ? x : m_max); } } int NumDataValues() const { return m_n; } double Mean() const { return (m_n > 0) ? m_newM : 0.0; } double Variance() const { return ( (m_n > 1) ? m_newS / (m_n - 1) : 0.0 ); } double StandardDeviation() const { return sqrt( Variance() ); } double Min() const { return m_min; } double Max() const { return m_max; } double ZeroCrossingRate() const { return double(m_crossing) / double(m_n) / 2; } private: int m_n, m_crossing; double m_oldM, m_newM, m_oldS, m_newS, m_min, m_max, m_prev; }; #endif _RUNNINGSTAT_H_ ================================================ FILE: esp32_inmp441/esp32_inmp441.ino ================================================ /* Sketch for an ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32 connected to a INMP411 I2S microphone See https://diyi0t.com/i2s-sound-tutorial-for-esp32/ and https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html This is how I connected the LOLIN32 Lite to the INMP411 module: 3V3 - VDD (red) GND - GND (black) 26 - SD (orange) 32 - L/R (blue) 33 - WS (green) 25 - SCK (purple) */ #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 #include #include #include #include #include #include "secret.h" #include "RunningStat.h" #define USE_DHCP #define CONNECT_LOLIN32 //#define DO_RECONNECT //#define SEND_TCP //#define PRINT_VALUE //#define PRINT_RANGE //#define PRINT_FREQUENCY #define PRINT_VOLUME //#define PRINT_HEADER #ifndef USE_DHCP IPAddress localAddress(192, 168, 1, 100); IPAddress gateway(192, 168, 1, 1); IPAddress subnet(255, 255, 0, 0); IPAddress primaryDNS(8, 8, 8, 8); //optional IPAddress secondaryDNS(8, 8, 4, 4); //optional #endif // IPAddress serverAddress(192, 168, 1, 21); IPAddress serverAddress(192, 168, 1, 34); const unsigned int serverPort = 4000; const unsigned int recvPort = 4001; WiFiUDP Udp; // this is for the local UDP server WiFiClient Tcp; unsigned long blinkTime = 250; unsigned long lastBlink = 0; unsigned int reconnectInterval = 30000; unsigned long lastConnect = 0; bool connected = false; const unsigned int sampleRate = 22050; const unsigned int nMessage = 512; // it can be up to 720 const unsigned int nBuffer = 64; bool meanInitialized = 0; const double alpha = 10. / sampleRate; // if the sampling time dT is much smaller than the time constant T, then alpha=1/(T*sampleRate) and T=1/(alpha*sampleRate) double signalMean = sqrt(-1); // initialize as not-a-number double signalScale = 1000; struct message_t { uint32_t version = 1; uint32_t id = 0; uint32_t counter; uint32_t samples; int16_t data[nMessage]; } message __attribute__((packed)); esp_err_t err; uint32_t bytes_read; int32_t buffer[nBuffer]; const i2s_port_t I2S_PORT = I2S_NUM_0; RunningStat shortstat; RunningStat longstat; void WiFiEvent(WiFiEvent_t event) { switch (event) { case SYSTEM_EVENT_STA_GOT_IP: Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); break; case SYSTEM_EVENT_STA_DISCONNECTED: Serial.println("WiFi lost connection."); connected = false; break; default: break; } } void setup() { esp_err_t err; Serial.begin(115200); #ifdef CONNECT_LOLIN32 #define LED_BUILTIN 22 pinMode(32, OUTPUT); digitalWrite(32, LOW); // L/R const i2s_pin_config_t pin_config = { .bck_io_num = 25, // Serial Clock (SCK on the INMP441) .ws_io_num = 33, // Word Select (WS on the INMP441) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 26 // Serial Data (SD on the INMP441) }; #endif #ifdef CONNECT_NODEMCU32 const i2s_pin_config_t pin_config = { .bck_io_num = 13, // Serial Clock (SCK on the INMP441) .ws_io_num = 15, // Word Select (WS on the INMP441) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 21 // Serial Data (SD on the INMP441) }; #endif #ifdef CONNECT_HUZZAH32 const i2s_pin_config_t pin_config = { .bck_io_num = 32, // Serial Clock (SCK on the INMP441) .ws_io_num = 22, // Word Select (WS on the INMP441) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 14 // Serial Data (SD on the INMP441) }; #endif // initialize status LED pinMode(LED_BUILTIN, OUTPUT); lastBlink = millis(); // delete old config WiFi.disconnect(true); //register event handler WiFi.onEvent(WiFiEvent); #ifndef USE_DHCP if (!WiFi.config(localAddress, gateway, subnet, primaryDNS, secondaryDNS)) { Serial.println("STA Failed to configure, halt!"); while (true); } #endif //Initiate connection Serial.println("Connecting to WiFi network: " + String(ssid)); WiFi.begin(ssid, password); Serial.println("Waiting for WIFI connection..."); while (WiFi.status() != WL_CONNECTED) { digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); } // The I2S config as per the example const i2s_config_t i2s_config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // receive, not transfer .sample_rate = sampleRate, // sampling rate .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // channel to use .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // interrupt level 1 .dma_buf_count = 16, // number of buffers, 128 max. .dma_buf_len = nBuffer // samples per buffer (minimum is 8) }; // Configuring the I2S driver and pins. // This function must be called before any I2S driver read/write operations. err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if (err != ESP_OK) { Serial.printf("Failed installing driver: %d, halt!\n", err); while (true); } Serial.println("I2S driver installed."); err = i2s_set_pin(I2S_PORT, &pin_config); if (err != ESP_OK) { Serial.printf("Failed setting pin: %d, halt!\n", err); while (true); } Serial.println("I2S pins set."); // use the last number of the IP address as identifier message.id = WiFi.localIP()[3]; for (int i=0; i < sampleRate; i++) { err = i2s_read(I2S_PORT, buffer, 4, &bytes_read, 0); } } // setup void loop() { if ((millis() - lastBlink) > 2000) { digitalWrite(LED_BUILTIN, LOW); lastBlink = millis(); } else if ((millis() - lastBlink) > 1000) { digitalWrite(LED_BUILTIN, HIGH); } size_t samples = nMessage - message.samples; if (samples > nBuffer) { // do not read more than nBuffer at a time samples = nBuffer; } err = i2s_read(I2S_PORT, buffer, samples * 4, &bytes_read, 0); if (err == ESP_OK) { for (unsigned int sample=0; sample < bytes_read / 4; sample++) { double value = buffer[sample]; // compute a smooth running mean with https://en.wikipedia.org/wiki/Exponential_smoothing if (isnan(signalMean)) { signalMean = value; } else { signalMean = alpha * value + (1 - alpha) * signalMean; } value -= signalMean; value /= signalScale; // https://en.wikipedia.org/wiki/Sigmoid_function value /= 32767; value /= (1 + fabs(value)); value *= 32767; #ifdef PRINT_VALUE Serial.println(value); #endif message.data[message.samples] = value; // this casts the value to an int16 message.samples++; shortstat.Push(value); longstat.Push(value); } if (message.samples == nMessage) { #ifdef PRINT_RANGE Serial.print(shortstat.Min()); Serial.print(", "); Serial.print(shortstat.Mean()); Serial.print(", "); Serial.println(shortstat.Max()); #endif #ifdef PRINT_FREQUENCY Serial.println(shortstat.ZeroCrossingRate() * sampleRate); #endif #ifdef PRINT_VOLUME Serial.println(10*log10(shortstat.Variance())); #endif #ifdef PRINT_HEADER Serial.print(message.version); Serial.print(", "); Serial.print(message.counter); Serial.print(", "); Serial.print(message.samples); Serial.print(", "); Serial.print(message.data[0]); Serial.println(); #endif #ifdef DO_RECONNECT if ((millis() - lastConnect) > reconnectInterval) { // reconnect, but don't try to reconnect too often lastConnect = millis(); // turn the status LED on digitalWrite(LED_BUILTIN, LOW); lastBlink = millis(); if (!connected) { connected = Tcp.connect(serverAddress, serverPort); if (connected) { Serial.print("Connected to "); Serial.println(serverAddress); } else { Serial.print("Failed to connect to "); Serial.println(serverAddress); } } } #endif #ifdef SEND_TCP if (connected) { blinkTime = 1000; int count = Tcp.write((uint8_t *)(&message), sizeof(message)); connected = (count == sizeof(message)); } else { blinkTime = 250; } #endif shortstat.Clear(); message.counter++; message.samples = 0; } } else { Serial.println("i2s_read error, halt!"); while (true); } } // loop ================================================ FILE: esp32_inmp441/util.h ================================================ #ifndef _UTIL_H_ #define _UTIL_H_ void print_binary(uint32_t val) { for (int l = 31; l >= 24; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 23; l >= 16; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 15; l >= 8; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 7; l >= 0; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.println(); } #endif ================================================ FILE: esp32_sph0645/RunningStat.h ================================================ /* See https://www.johndcook.com/blog/standard_deviation/ */ class RunningStat { public: RunningStat() : m_n(0) {} void Clear() { m_n = 0; } void Push(double x) { m_n++; // See Knuth TAOCP vol 2, 3rd edition, page 232 if (m_n == 1) { m_oldM = m_newM = x; m_oldS = 0.0; } else { m_newM = m_oldM + (x - m_oldM) / m_n; m_newS = m_oldS + (x - m_oldM) * (x - m_newM); // set up for next iteration m_oldM = m_newM; m_oldS = m_newS; } if (m_n == 1) { m_min = x; m_max = x; } else { m_min = (x < m_min ? x : m_min); m_max = (x > m_max ? x : m_max); } } int NumDataValues() const { return m_n; } double Mean() const { return (m_n > 0) ? m_newM : 0.0; } double Variance() const { return ( (m_n > 1) ? m_newS / (m_n - 1) : 0.0 ); } double StandardDeviation() const { return sqrt( Variance() ); } double Min() const { return m_min; } double Max() const { return m_max; } private: int m_n; double m_oldM, m_newM, m_oldS, m_newS, m_min, m_max; }; ================================================ FILE: esp32_sph0645/compress.cpp ================================================ #include uint32_t compress(uint32_t *data, uint32_t nsamples, uint8_t *dest) { uint32_t nbytes = 0; uint32_t *ptr = (uint32_t *)dest; for (int i; i < nsamples; i++) { ptr[i] = data[i]; nbytes += 4; } return nbytes; } // compress uint32_t decompress(uint32_t *dest, uint32_t nsamples, uint8_t *data) { uint32_t nbytes = 0; uint32_t *ptr = (uint32_t *)data; for (int i; i < nsamples; i++) { dest[i] = ptr[i]; nbytes += 4; } return nbytes; } // decompress ================================================ FILE: esp32_sph0645/esp32_sph0645.ino ================================================ /* Sketch for ESP32 board, like the NodeMCU 32S, LOLIN32 Lite, or the Adafruit Huzzah32 connected to a SPH0645 I2S microphone See https://diyi0t.com/i2s-sound-tutorial-for-esp32/ and https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html */ #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 #include #include #include #include #include "secret.h" #include "RunningStat.h" #ifndef htonl #define htonl htobe32 #endif #ifndef htons #define htons htobe16 #endif #ifndef ntohl #define htons be32toh #endif #ifndef ntohs #define htons be16toh #endif #define USE_DHCP #ifdef USE_DHCP IPAddress localAddress(192, 168, 1, 100); IPAddress gateway(192, 168, 1, 1); IPAddress subnet(255, 255, 0, 0); IPAddress primaryDNS(8, 8, 8, 8); //optional IPAddress secondaryDNS(8, 8, 4, 4); //optional #endif // this is for the remote UDP server IPAddress sendAddress(192, 168, 1, 21); const unsigned int sendPort = 4000; // this is for the local UDP server WiFiUDP Udp; const unsigned int recvPort = 4001; const unsigned int nMessage = 720; const unsigned int nBuffer = 64; const float decay = 0.1; unsigned long lastBlink = 0; unsigned long failedPackets = 0; unsigned long previous = 0; bool connected = false; const unsigned int sampleRate = 22050; float runningMean = -1; struct message_t { uint32_t version = 1; uint32_t id = 0; uint32_t counter; uint32_t samples; int16_t data[nMessage]; } message __attribute__((packed)); struct response_t { uint32_t version = 1; uint32_t id = 0; uint32_t counter; } response __attribute__((packed)); const i2s_port_t I2S_PORT = I2S_NUM_0; RunningStat shortstat; RunningStat longstat; void WiFiEvent(WiFiEvent_t event) { switch (event) { case SYSTEM_EVENT_STA_GOT_IP: Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); Udp.begin(WiFi.localIP(), recvPort); connected = true; break; case SYSTEM_EVENT_STA_DISCONNECTED: Serial.println("WiFi lost connection."); connected = false; break; default: break; } } void print_binary(uint32_t val) { for (int l = 31; l >= 24; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 23; l >= 16; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 15; l >= 8; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.print(' '); for (int l = 7; l >= 0; l--) { uint8_t b = (val >> l) & 0x01; Serial.print(b); } Serial.println(); } void setup() { esp_err_t err; Serial.begin(115200); // initialize status LED pinMode(LED_BUILTIN, OUTPUT); lastBlink = millis(); // delete old config WiFi.disconnect(true); //register event handler WiFi.onEvent(WiFiEvent); #ifndef USE_DHCP if (!WiFi.config(localAddress, gateway, subnet, primaryDNS, secondaryDNS)) { Serial.println("STA Failed to configure"); while (true); } #endif //Initiate connection Serial.println("Connecting to WiFi network: " + String(ssid)); WiFi.begin(ssid, password); Serial.println("Waiting for WIFI connection..."); while (WiFi.status() != WL_CONNECTED) { digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); } // The I2S config as per the example const i2s_config_t i2s_config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // receive, not transfer .sample_rate = sampleRate, // sampling rate .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // channel to use .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // interrupt level 1 .dma_buf_count = 16, // number of buffers, 128 max. .dma_buf_len = nBuffer // samples per buffer (minimum is 8) }; // setup for the NODEMCU32 const i2s_pin_config_t NODEMCU32_pin_config = { .bck_io_num = 26, // Serial Clock (BCLK on the SPH0645) .ws_io_num = 25, // Word Select (LRCL on the SPH0645) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 33 // Serial Data (DOUT on the SPH0645) }; // setup for the HUZZAH32 const i2s_pin_config_t HUZZAH32_pin_config = { .bck_io_num = 32, // Serial Clock (BCLK on the SPH0645) .ws_io_num = 22, // Word Select (LRCL on the SPH0645) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 14 // Serial Data (DOUT on the SPH0645) }; // Configuring the I2S driver and pins. // This function must be called before any I2S driver read/write operations. err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if (err != ESP_OK) { Serial.printf("Failed installing driver: %d\n", err); while (true); } Serial.println("I2S driver installed."); err = i2s_set_pin(I2S_PORT, &NODEMCU32_pin_config); if (err != ESP_OK) { Serial.printf("Failed setting pin: %d\n", err); while (true); } Serial.println("I2S pins set."); // use the last digit of the IP address as identifier message.id = WiFi.localIP()[3]; } // setup void loop() { esp_err_t err; uint32_t bytes_read; uint32_t buffer[nBuffer]; if ((millis() - lastBlink) > 2000) { digitalWrite(LED_BUILTIN, LOW); lastBlink = millis(); } else if ((millis() - lastBlink) > 1000) { digitalWrite(LED_BUILTIN, HIGH); } size_t samples = nMessage - message.samples; if (samples > nBuffer) { // do not read more than nBuffer at a time samples = nBuffer; } err = i2s_read(I2S_PORT, buffer, samples * 4, &bytes_read, 0); if (err == ESP_OK) { for (unsigned int sample; sample < bytes_read / 4; sample++) { uint32_t value = buffer[sample]; value = value >> 14; // convert to 18 bit value = value >> 3; // convert to 15 bit if (runningMean < 0) { runningMean = value; } else { runningMean = decay * value + (1 - decay) * runningMean; } int16_t demeaned = ((float)value - (float)runningMean); message.data[message.samples] = htons(demeaned); message.samples++; shortstat.Push(demeaned); longstat.Push(demeaned); } if (message.samples == nMessage) { /* Serial.print(message.version); Serial.print(", "); Serial.print(message.counter); Serial.print(", "); Serial.print(message.samples); Serial.print(", "); Serial.print(ntohs(message.data[0])); Serial.println(); Serial.print(shortstat.Min()); Serial.print(", "); Serial.print(shortstat.Mean()); Serial.print(", "); Serial.println(shortstat.Max()); */ if (connected) { Udp.beginPacket(sendAddress, sendPort); Udp.write((uint8_t *)(&message), sizeof(message)); Udp.endPacket(); } shortstat.Clear(); message.counter++; message.samples = 0; } } // receive incoming UDP packets int packetSize = Udp.parsePacket(); if (packetSize) { // Serial.printf("Received %d bytes from %s, port %d\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort()); int len = Udp.read((char *) &response, sizeof(response)); if (len == sizeof(response)) { if (response.counter != previous+1) { failedPackets++; Serial.print("missing response: "); Serial.print(previous); Serial.print(", "); Serial.print(response.counter); Serial.println(); } previous = response.counter; } } } // loop ================================================ FILE: esp32c6_feeder/README.md ================================================ # Pet feeder This Arduino sketch is for a battery operated pet feeder based on a XIAO ESP32c6. It uses deep sleep to conserve power. When it wakes up, it gets a JSON document from a URL that specifies whether and how much to feed. It then moves a servo to provide the required number of portions and goes back to sleep. The JSON document is dynamically hosted by Node-Red running on a Raspberri Pi, which allows for reprogramming the feeding schedule. To prevent the servo motor from jerking to the default 0 angle upon every wake up and also to preserve power, it is connected over a bs170 transistor. This alows the ESP to switch the servo motor power on only when needed. ================================================ FILE: esp32c6_feeder/esp32c6_feeder.ino ================================================ /* This Arduino sketch is for a battery operated pet feeder based on a XIAO ESP32c6. It uses deep sleep to conserve power. When it wakes up, it gets a JSON document from a URL that specifies whether and how much to feed. It then moves a servo to provide the required number of portions and goes back to sleep. The JSON document is dynamically hosted by Node-Red running on a Raspberri Pi, which allows for reprogramming the feeding schedule. To prevent the servo motor from jerking to the default 0 angle upon every wake up and also to preserve power, it is connected over a bs170 transistor. This alows the ESP to switch the servo motor power on only when needed. */ #include #include #include #include #include "secret.h" #define SERVO_CONTROL 20 // D9 is GPIO20, this is the control line for the servo #define SERVO_ENABLE 18 // D10 is GPIO18, this goes over a 1 kOhm resistor to a BS170 transistor #define uS_TO_S_FACTOR 1000000ULL // conversion factor for micro seconds to seconds WiFiClient wifi_client; HTTPClient http_client; Servo myservo; // defaults following a hard reset RTC_DATA_ATTR int interval = 60; RTC_DATA_ATTR int amount = 0; const long wifiTimeout = 10000; const char* version = __DATE__ " / " __TIME__; void deepsleep() { Serial.print("Going asleep for "); Serial.print(interval); Serial.println(" seconds."); Serial.flush(); wifi_client.flush(); wifi_client.stop(); esp_sleep_enable_timer_wakeup(interval * uS_TO_S_FACTOR); esp_deep_sleep_start(); } void moveServo() { // these have been experimentally calibrated const int left = 10; const int right = 74; const int middle = (left + right) / 2; Serial.println("Moving servo."); // start in the middle position myservo.write(middle); // switch on the power to the servo motor digitalWrite(SERVO_ENABLE, HIGH); // move to the right to pick up the food for (int posDegrees = middle; posDegrees <= right; posDegrees++) { myservo.write(posDegrees); delay(50); } // wait for it to drop into the slider delay(1000); // move to the left to drop the food for (int posDegrees = right; posDegrees >= left; posDegrees--) { myservo.write(posDegrees); delay(50); } // wait for it to drop out of the slider delay(1000); // move back to the middle position for (int posDegrees = left; posDegrees <= middle; posDegrees++) { myservo.write(posDegrees); delay(50); } // switch off the power to the servo motor digitalWrite(SERVO_ENABLE, LOW); } void setup() { Serial.begin(115200); Serial.print("\n[esp32c6_feeder / "); Serial.print(version); Serial.println("]"); // switch on the LED pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // it is inverted, this switches it on // set up the servo, but initially with the power disabled pinMode(SERVO_ENABLE, OUTPUT); digitalWrite(SERVO_ENABLE, LOW); myservo.setPeriodHertz(50); myservo.attach(SERVO_CONTROL, 500, 2500); Serial.println("Connecting to WiFi..."); WiFi.begin(ssid, password); long now = millis(); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); if ((millis() - now) > wifiTimeout) { Serial.println(""); Serial.println("failed!"); deepsleep(); // try again later } delay(500); } Serial.println(""); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.print("RRSI: "); Serial.println(WiFi.RSSI()); // get the instructions from a Raspberry Pi that is running Node-Red http_client.useHTTP10(true); http_client.begin("http://192.168.1.16:1880/feeder"); if (http_client.GET()) { String payload = http_client.getString(); Serial.println(payload); StaticJsonDocument<256> doc; deserializeJson(doc, payload); // these default to 0 when they are not present in the JSON document interval = doc["interval"].as(); amount = doc["amount"].as(); Serial.print("interval = "); Serial.println(interval); Serial.print("amount = "); Serial.println(amount); // provide food by moving the servo for (int i = 0; i < amount; i++) moveServo(); } http_client.end(); deepsleep(); } void loop() { // it never gets here } ================================================ FILE: esp8266_12v_trigger/esp8266_12v_trigger.ino ================================================ /* * This sketch serves as a 12 volt trigger to switch a NAD-D3020 audio amplifier on and off. * The on and off state are controlled over a http call. The current status can also be probed. * * The GPOI output of pin D6 is used to switch a PC900v optocoupler. Its output voltage can be * toggled between 0 and 5 volt, which is is enough to trigger the amplifier. * * See https://robertoostenveld.nl/12-volt-trigger-for-nad-d3020-amplifier/ * * Valid HTTP calls are * http://nad-d3020.local/on * http://nad-d3020.local/off * http://nad-d3020.local/status this returns on or off * http://nad-d3020.local/version * http://nad-d3020.local/reset * http://nad-d3020.local/ this returns an identifier * * (C) 2017-2019 Robert Oostenveld * */ #include #include #include #include #include #include //https://github.com/tzapu/WiFiManager #define PINOUT D6 const char* version = __DATE__ " / " __TIME__; const char* host = "NAD-D3020"; ESP8266WebServer server(80); void handleRoot() { Serial.println("/"); server.send(200, "text/plain", host); } 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); } } long checkpoint = millis(); void setup(void) { pinMode(PINOUT, OUTPUT); digitalWrite(PINOUT, HIGH); // note that the logic is inverted Serial.begin(115200); while (!Serial) { ; } Serial.print("\n[esp8266_12v_trigger / "); Serial.print(version); Serial.println("]"); 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"); Serial.println(""); Serial.println("Connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); if (MDNS.begin(host)) { Serial.println("MDNS responder started"); } server.on("/", handleRoot); server.on("/on", []() { Serial.println("/on"); digitalWrite(PINOUT, LOW); // note that the logic is inverted server.send(200, "text/plain", "on"); }); server.on("/off", []() { Serial.println("/off"); digitalWrite(PINOUT, HIGH); // note that the logic is inverted server.send(200, "text/plain", "off"); }); server.on("/status", []() { Serial.println("/status"); if (digitalRead(PINOUT)) // note that the logic is inverted server.send(200, "text/plain", "off"); else server.send(200, "text/plain", "on"); }); server.on("/version", []() { Serial.println("/version"); server.send(200, "text/plain", version); }); server.on("/restart", []() { Serial.println("/restart"); server.send(200, "text/plain", "restart"); ESP.restart(); }); server.on("/reset", []() { Serial.println("/reset"); server.send(200, "text/plain", "reset"); ESP.reset(); }); server.onNotFound(handleNotFound); server.begin(); Serial.println("HTTP server started"); } // setup void loop(void) { // check the connection status every 10 seconds if ((millis()-checkpoint) > 10000) { if (WiFi.status() != WL_CONNECTED) { Serial.println("restart"); ESP.restart(); } else checkpoint = millis(); } // process incoming http requests server.handleClient(); } // loop ================================================ FILE: esp8266_ad8232_ecg/README.md ================================================ # Overview This is an Arduino sketch for an ESP8266 module connected to an AD8232 ECG sensor module. It uses the ADC to sample the data and writes it as a continuous stream to a computer that is running a FieldTrip buffer. You can use the webinterface to set the IP address and port of the receiving computer. ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## Arduino ESP8266 filesystem uploader This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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: esp8266_ad8232_ecg/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: esp8266_ad8232_ecg/blink_led.h ================================================ #ifndef _BLINK_LED_H_ #define _BLINK_LED_H_ #include #include #define LED 2 // GPIO2 is connected to the builtin LED void ledInit(void); void ledOn(void); void ledOff(void); void ledSlow(void); void ledMedium(void); void ledFast(void); #endif ================================================ FILE: esp8266_ad8232_ecg/data/config.json ================================================ { "address": "192.168.1.34", "port": 1972 } ================================================ FILE: esp8266_ad8232_ecg/data/hello.html ================================================ Hello

Hello llama!

================================================ FILE: esp8266_ad8232_ecg/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_ad8232_ecg/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp8266_ad8232_ecg/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_ad8232_ecg/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_ad8232_ecg/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_ad8232_ecg/data/style.css ================================================ .c{ text-align: center; } div,input{ padding:5px;font-size:1em; } input{ width:85%; } 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: esp8266_ad8232_ecg/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_ad8232_ecg/esp8266_ad8232_ecg.ino ================================================ /* This sketch is for an ESP8266 connected to an AD8232 module to record the ECG and to send the continuous signal with the Fieldtrip buffer protocol to a server. It uses WIFiManager for initial configuration and includes a web-interface that allows to monitor and change parameters. The status of the wifi connection, http server and received data is indicated by the builtin LED. */ #include #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include // https://github.com/sstaub/Ticker #include "webinterface.h" #include "blink_led.h" #include "fieldtrip_buffer.h" // this allows some sections of the code to be disabled for debugging purposes #define ENABLE_WEBINTERFACE #define ENABLE_MDNS #define ENABLE_BUFFER const char* host = "AD8232-ECG"; const char* version = __DATE__ " / " __TIME__; ESP8266WebServer server(80); Ticker sampler; #define ADC A0 #define NCHANS 1 #define FSAMPLE 200 #define BLOCKSIZE (FSAMPLE/10) #define LO1 D6 // Lead-off detection #define LO2 D7 // Lead-off detection long tic_web = 0; int sample = 0, block = 0; bool flush0 = false, flush1 = false; uint16_t block0[BLOCKSIZE], block1[BLOCKSIZE]; int ftserver = 0, status = 0; int lo1 = 0, lo2 = 0; /************************************************************************************************/ void getSample() { if (sample == BLOCKSIZE) { // switch to the start of the other block switch (block) { case 0: sample = 0; flush0 = true; block = 1; break; case 1: sample = 0; flush1 = true; block = 0; break; } // sample the lead-off detection every block // they don't behave as they should lo1 = digitalRead(LO1); lo2 = digitalRead(LO2); } // get the current ECG value, it is from a 10-bits ADC, value between 0 and 1023 uint16_t value = analogRead(ADC); // store it in the active block switch (block) { case 0: block0[sample++] = value; break; case 1: block1[sample++] = value; break; } } /************************************************************************************************/ void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.print("\n[esp8266_ad8232 / "); Serial.print(__DATE__ " / " __TIME__); Serial.println("]"); pinMode(LO1, INPUT); // Setup for leads off detection LO + pinMode(LO2, INPUT); // Setup for leads off detection LO - ledInit(); WiFi.hostname(host); WiFi.begin(); SPIFFS.begin(); if (loadConfig()) { ledSlow(); delay(1000); } else { ledFast(); delay(1000); } Serial.println(config.address); Serial.println(config.port); if (WiFi.status() != WL_CONNECTED) ledFast(); 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"); if (WiFi.status() == WL_CONNECTED) ledSlow(); #ifdef ENABLE_WEBINTERFACE // this serves all URIs that cannot be resolved to a file on the SPIFFS filesystem server.onNotFound(handleNotFound); server.on("/", HTTP_GET, []() { tic_web = millis(); handleRedirect("/index.html"); }); server.on("/defaults", HTTP_GET, []() { tic_web = millis(); Serial.println("handleDefaults"); handleStaticFile("/reload_success.html"); defaultConfig(); saveConfig(); server.close(); server.stop(); delay(5000); ESP.restart(); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); handleStaticFile("/reload_success.html"); ledFast(); 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); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) ledSlow(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); ledFast(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, [] { tic_web = millis(); handleDirList(); }); server.on("/json", HTTP_PUT, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_POST, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_GET, [] { tic_web = millis(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(address, "address"); N_CONFIG_TO_JSON(port, "port"); root["version"] = version; root["uptime"] = long(millis() / 1000); String str; root.printTo(str); server.setContentLength(str.length()); server.send(200, "application/json", str); }); server.on("/update", HTTP_GET, [] { tic_web = millis(); handleStaticFile("/update.html"); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // start the web server server.begin(); #endif #ifdef ENABLE_MDNS // announce the hostname and web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); #endif #ifdef ENABLE_BUFFER Serial.print("Connecting to "); Serial.print(config.address); Serial.print(" on port "); Serial.println(config.port); ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) { Serial.println("Connection opened"); status = fieldtrip_write_header(ftserver, DATATYPE_UINT16, NCHANS, FSAMPLE); if (status == 0) Serial.println("Wrote header"); else Serial.println("Failed writing header"); } else { Serial.println("Failed opening connection"); } #endif // start sampling the ECG sampler.attach_ms(1000 / FSAMPLE, getSample); Serial.println("===================================================="); Serial.println("Setup done"); Serial.println("===================================================="); return; } // setup /************************************************************************************************/ void loop() { #ifdef ENABLE_WEBINTERFACE server.handleClient(); #endif #ifdef ENABLE_BUFFER byte *ptr = NULL; if (flush0) { ptr = (byte *)block0; flush0 = false; } else if (flush1) { ptr = (byte *)block1; flush1 = false; } if (ptr) { if (ftserver == 0) { ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) Serial.println("Connection opened"); else Serial.println("Failed opening connection"); } if (ftserver > 0) { status = fieldtrip_write_data(ftserver, DATATYPE_UINT16, NCHANS, BLOCKSIZE, ptr); if (status == 0) Serial.println("Wrote data"); else { Serial.println("Failed writing data"); status = fieldtrip_close_connection(ftserver); if (status == 0) Serial.println("Connection closed"); else Serial.println("Failed closing connection"); ftserver = 0; } } } #endif delay(10); return; } // loop ================================================ FILE: esp8266_ad8232_ecg/fieldtrip_buffer.cpp ================================================ #include "fieldtrip_buffer.h" WiFiClient client; #undef DEBUG /******************************************************************************* OPEN CONNECTION returns file descriptor that should be >0 on success *******************************************************************************/ int fieldtrip_open_connection(const char *address, int port) { #ifdef DEBUG Serial.println("fieldtrip_open_connection"); #endif if (client.connect(address, port)) return 1; else return -1; }; /******************************************************************************* CLOSE CONNECTION returns 0 on success *******************************************************************************/ int fieldtrip_close_connection(int s) { #ifdef DEBUG Serial.println("fieldtrip_close_connection"); #endif client.stop(); return 0; }; /******************************************************************************* WRITE HEADER returns 0 on success *******************************************************************************/ int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; headerdef_t *header = NULL; #ifdef DEBUG Serial.println("fieldtrip_write_header"); #endif byte msg[8+24]; for (int i = 0; i < 8+24; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_HDR; request->bufsize = sizeof(headerdef_t); header = (headerdef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize header->nchans = nchans; header->nsamples = 0; header->nevents = 0; header->fsample = fsample; header->data_type = datatype; header->bufsize = 0; #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(headerdef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(headerdef_t)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n != sizeof(messagedef_t) + sizeof(headerdef_t)) status++; // read and ignore whatever response we get while (client.available()) client.read(); return status; }; /******************************************************************************* WRITE DATA returns 0 on success *******************************************************************************/ int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer) { int status = 0; messagedef_t *request = NULL; messagedef_t *response = NULL; datadef_t *data = NULL; #ifdef DEBUG Serial.println("fieldtrip_write_data"); #endif byte msg[8+16]; for (int i = 0; i < 8+16; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_DAT; request->bufsize = sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype); data = (datadef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize data->nchans = nchans; data->nsamples = nsamples; data->data_type = datatype; data->bufsize = nchans * nsamples * wordsize_from_type(datatype); #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(datadef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(datadef_t)); client.flush(); n += client.write(buffer, nchans * nsamples * wordsize_from_type(datatype)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n != sizeof(messagedef_t) + sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype)) status++; // read and ignore whatever response we get while (client.available()) client.read(); return status; }; int wordsize_from_type(uint32_t datatype) { int wordsize = 0; switch (datatype) { case DATATYPE_CHAR: wordsize = 1; break; case DATATYPE_UINT8: wordsize = 1; break; case DATATYPE_UINT16: wordsize = 2; break; case DATATYPE_UINT32: wordsize = 4; break; case DATATYPE_UINT64: wordsize = 8; break; case DATATYPE_INT8: wordsize = 1; break; case DATATYPE_INT16: wordsize = 2; break; case DATATYPE_INT32: wordsize = 4; break; case DATATYPE_INT64: wordsize = 8; break; case DATATYPE_FLOAT32: wordsize = 4; break; case DATATYPE_FLOAT64: wordsize = 8; break; } return wordsize; }; ================================================ FILE: esp8266_ad8232_ecg/fieldtrip_buffer.h ================================================ #ifndef __BUFFER_H_ #define __BUFFER_H_ #include // definition of simplified interface functions, not all of them are implemented int fieldtrip_start_server(int port); int fieldtrip_open_connection(const char *hostname, int port); int fieldtrip_close_connection(int s); int fieldtrip_read_header(int server, uint32_t *datatype, uint32_t *nchans, float *fsample, uint32_t *nsamples, uint32_t *nevents); int fieldtrip_read_data(int server, uint32_t begsample, uint32_t endsample, byte *buffer); int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample); int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer); int fieldtrip_wait_data(int server, uint32_t nsamples, uint32_t nevents, uint32_t milliseconds); int wordsize_from_type(uint32_t datatype); // define the version of the message packet #define VERSION (uint16_t)0x0001 // these define the commands that can be used, which are split over the two available bytes #define PUT_HDR (uint16_t)0x0101 /* decimal 257 */ #define PUT_DAT (uint16_t)0x0102 /* decimal 258 */ #define PUT_EVT (uint16_t)0x0103 /* decimal 259 */ #define PUT_OK (uint16_t)0x0104 /* decimal 260 */ #define PUT_ERR (uint16_t)0x0105 /* decimal 261 */ #define GET_HDR (uint16_t)0x0201 /* decimal 513 */ #define GET_DAT (uint16_t)0x0202 /* decimal 514 */ #define GET_EVT (uint16_t)0x0203 /* decimal 515 */ #define GET_OK (uint16_t)0x0204 /* decimal 516 */ #define GET_ERR (uint16_t)0x0205 /* decimal 517 */ #define FLUSH_HDR (uint16_t)0x0301 /* decimal 769 */ #define FLUSH_DAT (uint16_t)0x0302 /* decimal 770 */ #define FLUSH_EVT (uint16_t)0x0303 /* decimal 771 */ #define FLUSH_OK (uint16_t)0x0304 /* decimal 772 */ #define FLUSH_ERR (uint16_t)0x0305 /* decimal 773 */ #define WAIT_DAT (uint16_t)0x0402 /* decimal 1026 */ #define WAIT_OK (uint16_t)0x0404 /* decimal 1027 */ #define WAIT_ERR (uint16_t)0x0405 /* decimal 1028 */ #define PUT_HDR_NORESPONSE (uint16_t)0x0501 /* decimal 1281 */ #define PUT_DAT_NORESPONSE (uint16_t)0x0502 /* decimal 1282 */ #define PUT_EVT_NORESPONSE (uint16_t)0x0503 /* decimal 1283 */ // these are used in the data_t and event_t structure #define DATATYPE_CHAR (uint32_t)0 #define DATATYPE_UINT8 (uint32_t)1 #define DATATYPE_UINT16 (uint32_t)2 #define DATATYPE_UINT32 (uint32_t)3 #define DATATYPE_UINT64 (uint32_t)4 #define DATATYPE_INT8 (uint32_t)5 #define DATATYPE_INT16 (uint32_t)6 #define DATATYPE_INT32 (uint32_t)7 #define DATATYPE_INT64 (uint32_t)8 #define DATATYPE_FLOAT32 (uint32_t)9 #define DATATYPE_FLOAT64 (uint32_t)10 // a packet that is sent over the network should contain the following typedef struct { uint16_t version; // see VERSION uint16_t command; // see PUT_xxx, GET_xxx and FLUSH_xxx uint32_t bufsize; // size of the buffer in bytes } messagedef_t; // 8 bytes // the header definition is fixed, it can be followed by additional header chunks typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t nevents; float fsample; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } headerdef_t; // 24 bytes // the data definition is fixed, it should be followed by additional data typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } datadef_t; // 16 bytes // the event definition is fixed, it should be followed by additional data typedef struct { uint32_t type_type; /* usual would be DATATYPE_CHAR */ uint32_t type_numel; /* length of the type string */ uint32_t value_type; uint32_t value_numel; int32_t sample; int32_t offset; int32_t duration; uint32_t bufsize; /* size of the buffer in bytes */ } eventdef_t; // 64 bytes #endif ================================================ FILE: esp8266_ad8232_ecg/webinterface.cpp ================================================ #include "webinterface.h" #include "blink_led.h" Config config; extern ESP8266WebServer 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"); strncpy(config.address, "192.168.1.34", 32); config.port = 1972; 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 buf(new char[size]); configFile.readBytes(buf.get(), size); configFile.close(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(buf.get()); if (!root.success()) { Serial.println("Failed to parse config file"); return false; } S_JSON_TO_CONFIG(address, "address"); N_JSON_TO_CONFIG(port, "port"); return true; } bool saveConfig() { Serial.println("saveConfig"); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(address, "address"); N_CONFIG_TO_JSON(port, "port"); 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"); root.printTo(configFile); configFile.close(); return true; } } 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 handleUpdate1() { server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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("address") || server.hasArg("port")) { // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it S_KEYVAL_TO_CONFIG(address, "address"); N_KEYVAL_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else if (server.hasArg("plain")) { // parse the body as JSON object StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg("plain")); if (!root.success()) { handleStaticFile("/reload_failure.html"); return; } S_JSON_TO_CONFIG(address, "address"); N_JSON_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; // do not save the configuration } saveConfig(); // blink five times for (int i = 0; i < 5; i++) { ledOn(); delay(200); ledOff(); delay(200); } // some of the settings require re-initialization ESP.restart(); } ================================================ FILE: esp8266_ad8232_ecg/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ #include #include #include #include #include #include #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 /* 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.set(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 { char address[32]; int port; }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void handleUpdate1(void); void handleUpdate2(void); void handleDirList(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif ================================================ FILE: esp8266_artnet_bci/esp8266_artnet_bci.ino ================================================ #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include #include #include #include // https://github.com/rstephan/ArtnetWifi #include // https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library extern "C" { #include "user_interface.h" // https://github.com/willemwouters/ESP8266/wiki/Timer-example } const char* host = "ARTNET-BCI"; const char* version = __DATE__ " / " __TIME__; #define UNIVERSE 1 #define OFFSET 0 #define NUMPIXELS 1 #define PIN 5 #define NUMEL(x) (sizeof(x) / sizeof((x)[0])) #define WRAP(x,y) (x>=y ? x-y : x) WiFiManager wifiManager; Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_RGB + NEO_KHZ800); ESP8266WebServer server(80); static ETSTimer sequenceTimer; // Artnet settings ArtnetWifi artnet; unsigned long packetCounter = 0; // keep track of the time just after a http request long tic_web = 0; // Blink sequence settings byte enable = 0, step = 0, current = 0; byte r = 255, g = 255, b = 255; unsigned int sequence0[] = {125, 125}; // default, rapid blinking at 4Hz unsigned int sequence1[] = {100, 0}; // constant on unsigned int sequence2[] = {0, 100}; // constant off unsigned int sequence3[] = {8, 8}; // approximately 60Hz unsigned int sequence4[] = {80, 16, 96, 96, 16, 48, 48, 80, 48, 64, 32, 128, 80, 112, 96, 80, 48, 48, 80, 80, 48, 64, 32, 80, 96, 96, 48, 48, 96, 64, 96, 32, 48, 112, 112, 80, 48, 32, 96, 112, 144, 16, 48, 128, 64, 80, 80, 48, 48, 64, 112, 64, 112, 80, 128, 32, 48, 96, 64, 80, 64, 16, 32, 64, 64, 112, 128, 0, 112, 112, 32, 80, 144, 128, 112, 64, 16, 112, 48, 64, 64, 96, 64, 96, 80, 48, 80, 80, 64, 80, 64, 16, 64, 80, 96, 96, 48, 80, 64, 96}; unsigned int sequence5[] = {80, 100, 60, 60, 100, 80, 100, 100, 80, 100, 140, 60, 120, 160, 120, 160, 40, 80, 100, 200, 200, 160, 60, 80, 120, 100, 80, 160, 80, 180, 80, 80, 160, 100, 100, 100, 40, 60, 140, 160, 100, 80, 100, 100, 20, 60, 60, 100, 160, 100, 180, 200, 60, 220, 120, 60, 120, 100, 160, 80, 100, 160, 80, 140, 60, 140, 20, 100, 120, 100, 80, 60, 100, 60, 140, 140, 40, 140, 80, 100, 140, 180, 60, 100, 200, 140, 140, 60, 60, 80, 80, 60, 80, 40, 80, 60, 80, 220, 160, 80}; unsigned int sequence6[] = {96, 96, 256, 224, 96, 96, 256, 224, 96, 192, 64, 96, 64, 128, 160, 224, 128, 352, 160, 64, 160, 160, 32, 128, 96, 288, 128, 128, 128, 224, 160, 192, 64, 128, 32, 96, 192, 96, 160, 96, 96, 128, 192, 192, 256, 192, 192, 192, 96, 32, 160, 96, 64, 64, 192, 96, 32, 224, 320, 32, 96, 256, 224, 128, 128, 224, 192, 192, 192, 96, 96, 160, 192, 224, 128, 128, 96, 128, 192, 160, 224, 128, 192, 96, 96, 352, 256, 96, 128, 160, 288, 256, 224, 224, 160, 256, 96, 192, 128, 224}; #define NUMSEQUENCE 7 unsigned int *sequence[NUMSEQUENCE] = {sequence0, sequence1, sequence2, sequence3, sequence4, sequence5, sequence6}; unsigned int length[NUMSEQUENCE] = {2, 2, 2, 2, 100, 100, 100}; /***********************************************************************************************/ //this is perpetually scheduled for the blink sequence updating void sequence_update() { os_timer_disarm(&sequenceTimer); step = WRAP(step, length[current]); unsigned int interval = sequence[current][step]; if (interval == 0 ) { // skip to the next step step++; step = WRAP(step, length[current]); interval = sequence[current][step]; } if (enable) { if (step % 2 == 0) singleBright(); else singleBlack(); } step++; os_timer_arm(&sequenceTimer, interval, 0); } // sequence_update /***********************************************************************************************/ //this will be called for each UDP packet received void packet_receive(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { Serial.print("received packet = "); Serial.println(packetCounter++); if (universe != UNIVERSE) return; // shift to the specified channels in the DMX packet data += OFFSET; length -= OFFSET; // 0 = current sequence if (length > 0) { if (data[0] >= 0 && data[0] < NUMSEQUENCE) { current = data[0]; step = 0; } } // 1, 2, 3 = color if (length > 3) { r = data[1]; g = data[2]; b = data[3]; } } // packet_receive /***********************************************************************************************/ void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.println("setup starting"); pixels.setBrightness(150); pixels.begin(); current = 0; if (WiFi.status() != WL_CONNECTED) singleRed(); WiFiManager wifiManager; wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0)); wifiManager.autoConnect(host); if (WiFi.status() == WL_CONNECTED) { singleGreen(); Serial.println("connected"); } // Port defaults to 8266 ArduinoOTA.setPort(8266); // Hostname defaults to esp8266-[ChipID] ArduinoOTA.setHostname(host); // No authentication by default // ArduinoOTA.setPassword((const char *)"123"); ArduinoOTA.onStart([]() { Serial.println("Start OTA"); server = ESP8266WebServer(); // See https://github.com/esp8266/Arduino/issues/686 artnet = ArtnetWifi(); os_timer_disarm(&sequenceTimer); singleBlue(); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd OTA"); ESP.restart(); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); server.onNotFound([]() { tic_web = millis(); Serial.println("handleNotFound"); server.send(404, "text/plain", "not found"); }); server.on("/", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRoot"); server.send(200, "text/plain", host); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); singleRed(); server.close(); server.stop(); delay(5000); wifiManager.resetSettings(); wifiManager.startConfigPortal(host); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) singleGreen(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); singleRed(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); // start the web server server.begin(); // start the OTA server ArduinoOTA.begin(); // announce the web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); // start the Artnet server artnet.begin(); artnet.setArtDmxCallback(packet_receive); // start the timer os_timer_disarm(&sequenceTimer); os_timer_setfn(&sequenceTimer, (os_timer_func_t *) sequence_update, NULL); os_timer_arm(&sequenceTimer, 1000, 0); Serial.println("setup done"); } // setup /***********************************************************************************************/ void loop() { if (WiFi.status() != WL_CONNECTED) { enable = 0; singleRed(); } else if ((millis() - tic_web) < 1000) { enable = 0; singleBlue(); } else { enable = 1; server.handleClient(); artnet.read(); ArduinoOTA.handle(); // the remainder of the work gets done by the timer and callback functions } } // loop /***********************************************************************************************/ void singleRed() { pixels.setPixelColor(0, 255, 0, 0); pixels.show(); } void singleGreen() { pixels.setPixelColor(0, 0, 255, 0); pixels.show(); } void singleBlue() { pixels.setPixelColor(0, 0, 0, 255); pixels.show(); } void singleYellow() { pixels.setPixelColor(0, 255, 255, 0); pixels.show(); } void singleBlack() { pixels.setPixelColor(0, 0, 0, 0); pixels.show(); } void singleWhite() { pixels.setPixelColor(0, 255, 255, 255); pixels.show(); } void singleBright() { pixels.setPixelColor(0, r, g, b); pixels.show(); } ================================================ FILE: esp8266_artnet_neopixel/README.md ================================================ # Overview This is an Arduino sketch for an ESP8266 module connected to a neopixel LED strip. It uses ArtNet to control features of the light pattern, such as the brightness and the speed. There are a number of modes implemented to make nice patterns, such as a slider that moves from one to the other side. ## Webinterface The Neopixel and Art-Net settings can be updated on the fly using the webinterface or like this curl -X PUT -d '{"universe":1,"offset":0,"pixels":24,"leds":4,"white":0,"brightness":100,"hsv":0,"mode":10,"speed":8,"split":1,"reverse":0}' artnet.local/json ## Operating modes mode 0: individual pixel control channel 1 = pixel 1 red channel 2 = pixel 1 green channel 3 = pixel 1 blue channel 4 = pixel 1 white channel 5 = pixel 2 red etc. mode 1: single uniform color channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity (this allows scaling a preset RGBW color with a single channel) mode 2: two color mixing channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity (this also allows to black out the colors) channel 10 = balance (between color 1 and color2) mode 3: single uniform color, blinking between the color and black channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = speed (number of flashes per unit of time) channel 7 = ramp (whether there is a abrubt or more smooth transition) channel 8 = duty cycle (the time ratio between the color and black) mode 4: uniform color, blinking between color 1 and color 2 channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = speed channel 11 = ramp channel 12 = duty cycle mode 5: single color slider, segment that can be moved along the array (between the edges) channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 7 = width (from 0-255 or 0-360 degrees, relative to the length of the array) mode 6: dual color slider, segment can be moved along the array (between the edges) channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 11 = width (from 0-255 or 0-360 degrees, relative to the length of the array) mode 7: single color smooth slider, segment can be moved along the array (continuous over the edge) channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 7 = width (from 0-255 or 0-360 degrees, relative to the length of the array) channel 8 = ramp (from 0-255 or 0-360 degrees, relative to the length of the array) mode 8: dual color smooth slider, segment can be moved along the array (continuous over the edge) channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 11 = width (from 0-255 or 0-360 degrees, relative to the length of the array) channel 12 = ramp (from 0-255 or 0-360 degrees, relative to the length of the array) mode 9: spinning color wheel channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = speed channel 7 = width channel 8 = ramp mode 10: spinning color wheel with color background channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = speed channel 11 = width channel 12 = ramp mode 11: rainbow slider channel 1 = saturation channel 2 = value channel 3 = position mode 12: rainbow spinner channel 1 = saturation channel 2 = value channel 3 = speed mode 13: dual color letter for 8x8 RGBW neopixel array channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = ASCII code ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## Arduino ESP8266 filesystem uploader This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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: esp8266_artnet_neopixel/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: esp8266_artnet_neopixel/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: esp8266_artnet_neopixel/data/hello.html ================================================ Hello

Hello kitty!

================================================ FILE: esp8266_artnet_neopixel/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_artnet_neopixel/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
Packets received:
?
Frames per second:
?
================================================ FILE: esp8266_artnet_neopixel/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_artnet_neopixel/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_artnet_neopixel/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_artnet_neopixel/data/style.css ================================================ .c{ text-align: center; } div,input{ padding:5px;font-size:1em; } input{ width:85%; } 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: esp8266_artnet_neopixel/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_artnet_neopixel/esp8266_artnet_neopixel.ino ================================================ /* This sketch receive a DMX universes via Artnet to control a strip of ws2811 leds via Adafruit's NeoPixel library: https://github.com/rstephan/ArtnetWifi https://github.com/adafruit/Adafruit_NeoPixel */ #include #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include // https://github.com/rstephan/ArtnetWifi #include #include "webinterface.h" #include "neopixel_mode.h" ESP8266WebServer server(80); const char* host = "ARTNET"; const char* version = __DATE__ " / " __TIME__; float fps = 0; // Neopixel settings const byte dataPin = D2; Adafruit_NeoPixel strip = Adafruit_NeoPixel(1, dataPin, NEO_GRBW + NEO_KHZ800); // start with one pixel // Artnet settings ArtnetWifi artnet; unsigned int packetCounter = 0; // Global universe buffer struct { uint16_t universe; uint16_t length; uint8_t sequence; uint8_t *data; } global; // use an array of function pointers to jump to the desired mode void (*mode[]) (uint16_t, uint16_t, uint8_t, uint8_t *) { mode0, mode1, mode2, mode3, mode4, mode5, mode6, mode7, mode8, mode9, mode10, mode11, mode12, mode13, mode14, mode15, mode16 }; // keep the timing of the function calls long tic_loop = 0, tic_fps = 0, tic_packet = 0, tic_web = 0; long frameCounter = 0; //this will be called for each UDP packet received void onDmxPacket(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { // print some feedback Serial.print("packetCounter = "); Serial.print(packetCounter++); if ((millis() - tic_fps) > 1000 && frameCounter > 100) { // don't estimate the FPS too quickly fps = 1000 * frameCounter / (millis() - tic_fps); tic_fps = millis(); frameCounter = 0; Serial.print(", FPS = "); Serial.print(fps); } Serial.println(); // copy the data from the UDP packet over to the global universe buffer global.universe = universe; global.sequence = sequence; if (length < 512) global.length = length; for (int i = 0; i < global.length; i++) global.data[i] = data[i]; } // onDmxpacket void updateNeopixelStrip(void) { // update the neopixel strip configuration strip.updateLength(config.pixels); strip.setBrightness(config.brightness); if (config.leds == 3) strip.updateType(NEO_GRB + NEO_KHZ800); else if (config.leds == 4 && config.white) strip.updateType(NEO_GRBW + NEO_KHZ800); else if (config.leds == 4 && !config.white) strip.updateType(NEO_GRBW + NEO_KHZ800); } void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.println("setup starting"); global.universe = 0; global.sequence = 0; global.length = 512; global.data = (uint8_t *)malloc(512); SPIFFS.begin(); strip.begin(); if (loadConfig()) { updateNeopixelStrip(); strip.setBrightness(255); singleYellow(); delay(1000); } else { updateNeopixelStrip(); strip.setBrightness(255); singleRed(); delay(1000); } 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"); if (WiFi.status() == WL_CONNECTED) singleGreen(); // this serves all URIs that can be resolved to a file on the SPIFFS filesystem server.onNotFound(handleNotFound); server.on("/", HTTP_GET, []() { tic_web = millis(); handleRedirect("/index.html"); }); server.on("/version", HTTP_GET, []() { Serial.println("version"); server.send(200, "text/plain", version); }); server.on("/defaults", HTTP_GET, []() { tic_web = millis(); Serial.println("handleDefaults"); handleStaticFile("/reload_success.html"); defaultConfig(); saveConfig(); server.close(); server.stop(); ESP.restart(); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); handleStaticFile("/reload_success.html"); singleYellow(); 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); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) singleGreen(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); singleRed(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, [] { tic_web = millis(); handleDirList(); }); server.on("/json", HTTP_PUT, [] { Serial.println("HTTP_PUT /json"); tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_POST, [] { Serial.println("HTTP_POST /json"); tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_GET, [] { Serial.println("HTTP_GET /json"); tic_web = millis(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); N_CONFIG_TO_JSON(universe, "universe"); N_CONFIG_TO_JSON(offset, "offset"); N_CONFIG_TO_JSON(pixels, "pixels"); N_CONFIG_TO_JSON(leds, "leds"); N_CONFIG_TO_JSON(white, "white"); N_CONFIG_TO_JSON(brightness, "brightness"); N_CONFIG_TO_JSON(hsv, "hsv"); N_CONFIG_TO_JSON(mode, "mode"); N_CONFIG_TO_JSON(reverse, "reverse"); N_CONFIG_TO_JSON(speed, "speed"); N_CONFIG_TO_JSON(split, "split"); root["version"] = version; root["uptime"] = long(millis() / 1000); root["packets"] = packetCounter; root["fps"] = fps; String str; root.printTo(str); server.setContentLength(str.length()); server.send(200, "application/json", str); }); server.on("/update", HTTP_GET, [] { tic_web = millis(); handleStaticFile("/update.html"); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // start the web server server.begin(); // announce the hostname and web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); artnet.begin(); artnet.setArtDmxCallback(onDmxPacket); // initialize all timers tic_loop = millis(); tic_packet = millis(); tic_fps = millis(); tic_web = 0; Serial.println("setup done"); } // setup void loop() { server.handleClient(); if (WiFi.status() != WL_CONNECTED) { singleRed(); } else if ((millis() - tic_web) < 5000) { singleBlue(); } else { artnet.read(); // this section gets executed at a maximum rate of around 1Hz if ((millis() - tic_loop) > 999) updateNeopixelStrip(); // this section gets executed at a maximum rate of around 100Hz if ((millis() - tic_loop) > 9) { if (config.mode >= 0 && config.mode < (sizeof(mode) / 4)) { // call the function corresponding to the current mode (*mode[config.mode]) (global.universe, global.length, global.sequence, global.data); tic_loop = millis(); frameCounter++; } } } delay(1); } // loop ================================================ FILE: esp8266_artnet_neopixel/font8x8_basic.h ================================================ /** * 8x8 monochrome bitmap fonts for rendering * Author: Daniel Hepper * * Modified from https://github.com/dhepper/font8x8 by Robert Oostenveld * * License: Public Domain * * Based on: * // Summary: font8x8.h * // 8x8 monochrome bitmap fonts for rendering * // * // Author: * // Marcel Sondaar * // International Business Machines (public domain VGA fonts) * // * // License: * // Public Domain * * Fetched from: http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm **/ // Constant: font8x8_basic // Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin) char font8x8_basic[128][8] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) { B00110110, B01111111, B01111111, B01111111, B00111110, B00011100, B00001000, B00000000}, // U+2764 (heart, see https://en.wikipedia.org/wiki/Dingbat) { B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000}, // U+0002 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (//) { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F }; ================================================ FILE: esp8266_artnet_neopixel/neopixel_mode.cpp ================================================ #include "neopixel_mode.h" #include "webinterface.h" #include "colorspace.h" #include "font8x8_basic.h" extern Adafruit_NeoPixel strip; extern long tic_frame; float prev; int gamma_l[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 }; #define RGB (config.leds==3 || (config.leds==4 && !config.white)) #define RGBW ( (config.leds==4 && config.white)) /* mode 0: individual pixel control channel 1 = pixel 1 red channel 2 = pixel 1 green channel 3 = pixel 1 blue channel 4 = pixel 1 white channel 5 = pixel 2 red etc. */ void mode0(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 * strip.numPixels() + 1) return; if (RGBW && (length - config.offset) < 4 * strip.numPixels() + 1) return; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); if (RGB) strip.setPixelColor(pixel, r, g, b); else if (RGBW) strip.setPixelColor(pixel, r, g, b, w); yield(); } strip.show(); } /* mode 1: single uniform color channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity (this allows scaling a preset RGBW color with a single channel) */ void mode1(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; float intensity; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 + 1) return; if (RGBW && (length - config.offset) < 4 + 1) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { if (RGB) strip.setPixelColor(pixel, r, g, b); else if (RGBW) strip.setPixelColor(pixel, r, g, b, w); yield(); } strip.show(); } /* mode 2: two color mixing channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity (this also allows to black out the colors) channel 10 = balance (between color 1 and color2) */ void mode2(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w, r2, g2, b2, w2; float balance, intensity; if (universe != config.universe) return; if (RGB && (length - config.offset) < 2 * 3 + 2) return; if (RGBW && (length - config.offset) < 2 * 4 + 2) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; balance = 1. * data[config.offset + i++] / 255.; if (config.hsv) { map_hsv_to_rgb(&r, &g, &b); map_hsv_to_rgb(&r2, &g2, &b2); } // apply the balance between the two colors r = BALANCE(balance, r, r2); g = BALANCE(balance, g, g2); b = BALANCE(balance, b, b2); w = BALANCE(balance, w, w2); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { if (RGB) strip.setPixelColor(pixel, r, g, b); else if (RGBW) strip.setPixelColor(pixel, r, g, b, w); yield(); } strip.show(); } /* mode 3: single uniform color, blinking between the color and black channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = speed (number of flashes per unit of time) channel 7 = ramp (whether there is a abrubt or more smooth transition) channel 8 = duty cycle (the time ratio between the color and black) */ void mode3(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; float intensity, speed, ramp, duty, phase, balance; if (universe != config.universe) return; if (RGB && (length - config.offset) < (3 + 4) * config.split) return; if (RGBW && (length - config.offset) < (4 + 4) * config.split) return; // the code that takes care of the blinking repeats for each of the segments for (int segment = 0; segment < config.split; segment++) { r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; intensity = data[config.offset + i++] / 255.; speed = 1. * data[config.offset + i++] / config.speed; ramp = 1. * data[config.offset + i++] * 360. / 255.; duty = 1. * data[config.offset + i++] * 360. / 255.; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); // the ramp cannot be too wide if (duty < 180) ramp = (ramp < duty ? ramp : duty); else ramp = (ramp < (360 - duty) ? ramp : (360 - duty)); // determine the current phase in the temporal cycle phase = (speed * millis()) * 360. / 1000.; // prevent rolling back // only feasible with a single segment if (config.split == 1 && WRAP180(phase - prev) < 0) phase = prev; else prev = phase; phase = WRAP180(phase); phase = ABS(phase); if (phase <= (duty / 2 - ramp / 4)) balance = 1; else if (phase >= (duty / 2 + ramp / 4)) balance = 0; else if (ramp > 0) balance = ((duty / 2 + ramp / 4) - phase) / ( ramp / 2 ); // scale with the intensity r *= intensity; g *= intensity; b *= intensity; w *= intensity; // scale with the balance r *= balance; g *= balance; b *= balance; w *= balance; int begpixel = MAX((segment + 0) * strip.numPixels() / config.split, 0); int endpixel = MIN((segment + 1) * strip.numPixels() / config.split, strip.numPixels()); for (int pixel = begpixel; pixel < endpixel; pixel++) { if (RGB) strip.setPixelColor(pixel, r, g, b); else if (RGBW) strip.setPixelColor(pixel, r, g, b, w); yield(); } } strip.show(); } /* mode 4: uniform color, blinking between color 1 and color 2 channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = speed channel 11 = ramp channel 12 = duty cycle */ void mode4(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w, r2, g2, b2, w2; float intensity, speed, ramp, duty, phase, balance; if (universe != config.universe) return; if (RGB && (length - config.offset) < 2 * 3 + 4) return; if (RGBW && (length - config.offset) < 2 * 4 + 4) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; speed = 1. * data[config.offset + i++] / config.speed; ramp = 1. * data[config.offset + i++] * 360. / 255.; duty = 1. * data[config.offset + i++] * 360. / 255.; if (config.hsv) { map_hsv_to_rgb(&r, &g, &b); map_hsv_to_rgb(&r2, &g2, &b2); } // the ramp cannot be too wide if (duty < 180) ramp = (ramp < duty ? ramp : duty); else ramp = (ramp < (360 - duty) ? ramp : (360 - duty)); // determine the current phase in the temporal cycle phase = (speed * millis()) * 360. / 1000.; // prevent rolling back if (WRAP180(phase - prev) < 0) phase = prev; else prev = phase; phase = WRAP180(phase); phase = ABS(phase); if (phase <= (duty / 2 - ramp / 4)) balance = 1; else if (phase >= (duty / 2 + ramp / 4)) balance = 0; else if (ramp > 0) balance = ((duty / 2 + ramp / 4) - phase) / ( ramp / 2 ); // apply the balance between the two colors r = BALANCE(balance, r, r2); g = BALANCE(balance, g, g2); b = BALANCE(balance, b, b2); w = BALANCE(balance, w, w2); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { if (RGB) strip.setPixelColor(pixel, r, g, b); else if (RGBW) strip.setPixelColor(pixel, r, g, b, w); yield(); } strip.show(); } /* mode 5: single color slider, segment that can be moved along the array (between the edges) channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 7 = width (from 0-255 or 0-360 degrees, relative to the length of the array) */ void mode5(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; float intensity, width, position; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 + 3) return; if (RGBW && (length - config.offset) < 4 + 3) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; intensity = data[config.offset + i++] / 255.; position = data[config.offset + i++] * (strip.numPixels() - 1) / 255.; width = data[config.offset + i++] * (strip.numPixels() - 0) / 255.; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; // the position needs to be corrected for the width position -= strip.numPixels() / 2; position /= strip.numPixels() / 2; position *= (strip.numPixels() - width) / 2; position += strip.numPixels() / 2; // express the position and with as phase along the strip position *= 360. / strip.numPixels(); width *= 360. / strip.numPixels(); for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float phase, balance; phase = WRAP180((360. * flip * pixel / strip.numPixels()) * config.split - position); phase = ABS(phase); if (width == 0) balance = 0; else if (phase <= width / 2) balance = 1; else balance = 0; if (RGB) strip.setPixelColor(pixel, balance * r, balance * g, balance * b); else if (RGBW) strip.setPixelColor(pixel, balance * r, balance * g, balance * b, balance * w); yield(); } strip.show(); } /* mode 6: dual color slider, segment can be moved along the array (between the edges) channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 11 = width (from 0-255 or 0-360 degrees, relative to the length of the array) */ void mode6(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w, r2, g2, b2, w2; float intensity, width, position; if (universe != config.universe) return; if (RGB && (length - config.offset) < 2 * 3 + 3) return; if (RGBW && (length - config.offset) < 2 * 4 + 3) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = data[config.offset + i++] / 255.; position = data[config.offset + i++] * (strip.numPixels() - 1) / 255.; width = data[config.offset + i++] * (strip.numPixels() - 0) / 255.; if (config.hsv) { map_hsv_to_rgb(&r, &g, &b); map_hsv_to_rgb(&r2, &g2, &b2); } // the position needs to be corrected for the width position -= strip.numPixels() / 2; position /= strip.numPixels() / 2; position *= (strip.numPixels() - width) / 2; position += strip.numPixels() / 2; // express the position and with as phase along the strip position *= 360. / strip.numPixels(); width *= 360. / strip.numPixels(); for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float phase, balance; phase = WRAP180((360. * flip * pixel / (strip.numPixels() - 1)) * config.split - position); phase = ABS(phase); if (width == 0) balance = 0; else if (phase <= width / 2) balance = 1; else balance = 0; if (RGB) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2)); else if (RGBW) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2), intensity * BALANCE(balance, w, w2)); yield(); } strip.show(); } /* mode 7: single color smooth slider, segment can be moved along the array (continuous over the edge) channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 7 = width (from 0-255 or 0-360 degrees, relative to the length of the array) channel 8 = ramp (from 0-255 or 0-360 degrees, relative to the length of the array) */ void mode7(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; float intensity, position, width, ramp; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 + 4) return; if (RGBW && (length - config.offset) < 4 + 4) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; intensity = data[config.offset + i++] / 255.; position = data[config.offset + i++] * 360. / 255.; width = data[config.offset + i++] * 360. / 255.; ramp = data[config.offset + i++] * 360. / 255.; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); // the ramp cannot be too wide if (width < 180) ramp = (ramp < width ? ramp : width); else ramp = (ramp < (360 - width) ? ramp : (360 - width)); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float phase, balance; phase = WRAP180(360. * flip * pixel / (strip.numPixels() - 1) * config.split - position); phase = ABS(phase); if (width == 0) balance = 0; else if (phase < (width / 2. - ramp / 2.)) balance = 1; else if (phase > (width / 2. + ramp / 2.)) balance = 0; else if (ramp > 0) balance = ((width / 2. + ramp / 2.) - phase) / ramp; if (RGB) strip.setPixelColor(pixel, balance * r, balance * g, balance * b); else if (RGBW) strip.setPixelColor(pixel, balance * r, balance * g, balance * b, balance * w); yield(); } strip.show(); } /* mode 8: dual color smooth slider, segment can be moved along the array (continuous over the edge) channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = position (from 0-255 or 0-360 degrees, relative to the length of the array) channel 11 = width (from 0-255 or 0-360 degrees, relative to the length of the array) channel 12 = ramp (from 0-255 or 0-360 degrees, relative to the length of the array) */ void mode8(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w, r2, g2, b2, w2; float intensity, position, width, ramp; if (universe != config.universe) return; if (RGB && (length - config.offset) < 2 * 3 + 4) return; if (RGBW && (length - config.offset) < 2 * 4 + 4) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = data[config.offset + i++] / 255.; position = data[config.offset + i++] * 360. / 255.; width = data[config.offset + i++] * 360. / 255.; ramp = data[config.offset + i++] * 360. / 255.; if (config.hsv) { map_hsv_to_rgb(&r, &g, &b); map_hsv_to_rgb(&r2, &g2, &b2); } // the ramp cannot be too wide if (width < 180) ramp = (ramp < width ? ramp : width); else ramp = (ramp < (360 - width) ? ramp : (360 - width)); for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float phase, balance; phase = WRAP180(360. * flip * pixel / (strip.numPixels() - 1) * config.split - position); phase = ABS(phase); if (width == 0) balance = 0; else if (phase < (width / 2. - ramp / 2.)) balance = 1; else if (phase > (width / 2. + ramp / 2.)) balance = 0; else if (ramp > 0) balance = ((width / 2. + ramp / 2.) - phase) / ramp; if (RGB) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2)); else if (RGBW) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2), intensity * BALANCE(balance, w, w2)); yield(); } strip.show(); } /* mode 9: spinning color wheel channel 1 = red channel 2 = green channel 3 = blue channel 4 = white channel 5 = intensity channel 6 = speed channel 7 = width channel 8 = ramp */ void mode9(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w; float intensity, speed, width, ramp, phase; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 + 4) return; if (RGBW && (length - config.offset) < 4 + 4) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; speed = 1. * data[config.offset + i++] / config.speed; width = 1. * data[config.offset + i++] * 360. / 255.; ramp = 1. * data[config.offset + i++] * 360. / 255.; if (config.hsv) map_hsv_to_rgb(&r, &g, &b); // the ramp cannot be too wide if (width < 180) ramp = (ramp < width ? ramp : width); else ramp = (ramp < (360 - width) ? ramp : (360 - width)); // scale with the intensity r = intensity * r; g = intensity * g; b = intensity * b; w = intensity * w; // determine the current phase in the temporal cycle phase = (speed * millis()) * 360. / 1000.; // prevent rolling back if (WRAP180(phase - prev) < 0) phase = prev; else prev = phase; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float position, balance; position = WRAP180(360. * flip * pixel / (strip.numPixels() - 1) * config.split - phase); position = ABS(position); if (width == 0) balance = 0; else if (position < (width / 2. - ramp / 2.)) balance = 1; else if (position > (width / 2. + ramp / 2.)) balance = 0; else if (position > 0) balance = ((width / 2. + ramp / 2.) - position) / ramp; if (RGB) strip.setPixelColor(pixel, balance * r, balance * g, balance * b); else if (RGBW) strip.setPixelColor(pixel, balance * r, balance * g, balance * b, balance * w); yield(); } strip.show(); }; /* mode 10: spinning color wheel with color background channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = speed channel 11 = width channel 12 = ramp */ void mode10(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r, g, b, w, r2, g2, b2, w2; float intensity, speed, width, ramp, phase; if (universe != config.universe) return; if (RGB && (length - config.offset) < 2 * 3 + 4) return; if (RGBW && (length - config.offset) < 2 * 4 + 4) return; r = data[config.offset + i++]; g = data[config.offset + i++]; b = data[config.offset + i++]; if (RGBW) w = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; speed = 1. * data[config.offset + i++] / config.speed; width = 1. * data[config.offset + i++] * 360. / 255.; ramp = 1. * data[config.offset + i++] * 360. / 255.; if (config.hsv) { map_hsv_to_rgb(&r, &g, &b); map_hsv_to_rgb(&r2, &g2, &b2); } // the ramp cannot be too wide if (width < 180) ramp = (ramp < width ? ramp : width); else ramp = (ramp < (360 - width) ? ramp : (360 - width)); // determine the current phase in the temporal cycle phase = (speed * millis()) * 360. / 1000.; // prevent rolling back if (WRAP180(phase - prev) < 0) phase = prev; else prev = phase; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float position, balance; position = WRAP180((360. * flip * pixel / (strip.numPixels() - 1)) * config.split - phase); position = ABS(position); if (width == 0) balance = 0; else if (position < (width / 2. - ramp / 2.)) balance = 1; else if (position > (width / 2. + ramp / 2.)) balance = 0; else if (position > 0) balance = ((width / 2. + ramp / 2.) - position) / ramp; if (RGB) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2)); else if (RGBW) strip.setPixelColor(pixel, intensity * BALANCE(balance, r, r2), intensity * BALANCE(balance, g, g2), intensity * BALANCE(balance, b, b2), intensity * BALANCE(balance, w, w2)); yield(); } strip.show(); }; /* mode 11: rainbow slider channel 1 = saturation channel 2 = value channel 3 = position */ void mode11(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0; float saturation, value, position; if (universe != config.universe) return; if ((length - config.offset) < 3) return; saturation = 1. * data[config.offset + i++]; value = 1. * data[config.offset + i++] ; position = 1. * data[config.offset + i++] * 360. / 255.; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float phase = WRAP360((360. * flip * pixel / strip.numPixels()) * config.split - position); int r, g, b; r = phase; // hue, between 0-360 g = saturation; // saturation, between 0-255 b = value; // value, between 0-255 map_hsv_to_rgb(&r, &g, &b); strip.setPixelColor(pixel, r, g, b); yield(); } strip.show(); }; /* mode 12: rainbow spinner channel 1 = saturation channel 2 = value channel 3 = speed */ void mode12(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0; float saturation, value, speed, phase; if (universe != config.universe) return; if ((length - config.offset) < 3) return; saturation = 1. * data[config.offset + i++]; value = 1. * data[config.offset + i++] ; speed = 1. * data[config.offset + i++] / config.speed; // determine the current phase in the temporal cycle phase = (speed * millis()) * 360. / 1000.; // prevent rolling back if (WRAP180(phase - prev) < 0) phase = prev; else prev = phase; for (int pixel = 0; pixel < strip.numPixels(); pixel++) { int flip = (config.reverse ? -1 : 1); float position = WRAP360((360. * flip * pixel / strip.numPixels()) * config.split - phase); int r, g, b; r = position; // hue, between 0-360 g = saturation; // saturation, between 0-255 b = value; // value, between 0-255 map_hsv_to_rgb(&r, &g, &b); strip.setPixelColor(pixel, r, g, b); yield(); } strip.show(); }; /* mode 13: dual color letter for 8x8 RGBW neopixel array channel 1 = color 1 red channel 2 = color 1 green channel 3 = color 1 blue channel 4 = color 1 white channel 5 = color 2 red channel 6 = color 2 green channel 7 = color 2 blue channel 8 = color 2 white channel 9 = intensity channel 10 = ASCII code */ void mode13(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) { int i = 0, r1, g1, b1, w1, r2, g2, b2, w2; byte glyph; float intensity; if (universe != config.universe) return; if (RGB && (length - config.offset) < 3 + 1) return; if (RGBW && (length - config.offset) < 4 + 1) return; r1 = data[config.offset + i++]; g1 = data[config.offset + i++]; b1 = data[config.offset + i++]; if (RGBW) w1 = data[config.offset + i++]; r2 = data[config.offset + i++]; g2 = data[config.offset + i++]; b2 = data[config.offset + i++]; if (RGBW) w2 = data[config.offset + i++]; intensity = 1. * data[config.offset + i++] / 255.; glyph = data[config.offset + i++]; if (glyph > 127) return; if (config.hsv) { map_hsv_to_rgb(&r1, &g1, &b1); map_hsv_to_rgb(&r2, &g2, &b2); } // scale with the intensity r1 = intensity * r1; g1 = intensity * g1; b1 = intensity * b1; w1 = intensity * w1; r2 = intensity * r2; g2 = intensity * g2; b2 = intensity * b2; w2 = intensity * w2; int pixel = 0; for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { bool toggle = font8x8_basic[glyph][row] & (0x01 << col); if (RGB && toggle) strip.setPixelColor(pixel, r1, g1, b1); else if (RGBW && toggle) strip.setPixelColor(pixel, r1, g1, b1, w1); else if (RGB && !toggle) strip.setPixelColor(pixel, r2, g2, b2); else if (RGBW && !toggle) strip.setPixelColor(pixel, r2, g2, b2, w2); pixel++; } yield(); } strip.show(); }; /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ void mode14(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) {}; void mode15(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) {}; void mode16(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t * data) {}; /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ void singleLed(byte r, byte g, byte b, byte w) { fullBlack(); strip.setPixelColor(0, strip.Color(r, g, b, w ) ); strip.show(); } void singleRed() { singleLed(128, 0, 0, 0); } void singleGreen() { singleLed(0, 128, 0, 0); } void singleBlue() { singleLed(0, 0, 128, 0); } void singleYellow() { singleLed(128, 128, 0, 0); } void singleCyan() { singleLed(0, 128, 128, 0); } void singleMagenta() { singleLed(128, 0, 128, 0); } // helper functions to convert an integer into individual RGBW values uint8_t red(uint32_t c) { return (c >> 8); } uint8_t green(uint32_t c) { return (c >> 16); } uint8_t blue(uint32_t c) { return (c); } // The colours transition from r - g - b - back to r. uint32_t Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if (WheelPos < 85) { return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3, 0); } if (WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3, 0); } WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0, 0); } void fullRed() { Serial.println("fullRed"); for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(255, 0, 0, 0 ) ); } strip.show(); } void fullGreen() { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 255, 0 ) ); } strip.show(); } void fullBlue() { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 255, 0 ) ); } strip.show(); } void fullWhite() { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, 255 ) ); } strip.show(); } void fullBlack() { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, 0 ) ); } strip.show(); } // Fill the dots one after the other with a specific color void colorWipe(uint8_t wait, uint32_t c) { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } void pulseWhite(uint8_t wait) { for (int j = 0; j < 256 ; j++) { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, gamma_l[j] ) ); } delay(wait); strip.show(); } for (int j = 255; j >= 0 ; j--) { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, gamma_l[j] ) ); } delay(wait); strip.show(); } } void rainbowFade2White(uint8_t wait, int rainbowLoops, int whiteLoops) { float fadeMax = 100.0; int fadeVal = 0; uint32_t wheelVal; int redVal, greenVal, blueVal; for (int k = 0 ; k < rainbowLoops ; k ++) { for (int j = 0; j < 256; j++) { // 5 cycles of all colors on wheel for (int i = 0; i < strip.numPixels(); i++) { wheelVal = Wheel(((i * 256 / strip.numPixels()) + j) & 255); redVal = red(wheelVal) * float(fadeVal / fadeMax); greenVal = green(wheelVal) * float(fadeVal / fadeMax); blueVal = blue(wheelVal) * float(fadeVal / fadeMax); strip.setPixelColor( i, strip.Color( redVal, greenVal, blueVal ) ); } //First loop, fade in! if (k == 0 && fadeVal < fadeMax - 1) { fadeVal++; } //Last loop, fade out! else if (k == rainbowLoops - 1 && j > 255 - fadeMax ) { fadeVal--; } strip.show(); delay(wait); } } /* delay(500); for (int k = 0 ; k < whiteLoops ; k ++) { for (int j = 0; j < 256 ; j++) { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, gamma_l[j] ) ); } strip.show(); } delay(2000); for (int j = 255; j >= 0 ; j--) { for (uint16_t i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0, gamma_l[j] ) ); } strip.show(); } } delay(500); */ } void whiteOverRainbow(uint8_t wait, uint8_t whiteSpeed, uint8_t whiteLength ) { if (whiteLength >= strip.numPixels()) whiteLength = strip.numPixels() - 1; int head = whiteLength - 1; int tail = 0; int loops = 3; int loopNum = 0; static unsigned long lastTime = 0; while (true) { for (int j = 0; j < 256; j++) { for (uint16_t i = 0; i < strip.numPixels(); i++) { if ((i >= tail && i <= head) || (tail > head && i >= tail) || (tail > head && i <= head) ) { strip.setPixelColor(i, strip.Color(0, 0, 0, 255 ) ); } else { strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255)); } } if (millis() - lastTime > whiteSpeed) { head++; tail++; if (head == strip.numPixels()) { loopNum++; } lastTime = millis(); } if (loopNum == loops) return; head %= strip.numPixels(); tail %= strip.numPixels(); strip.show(); delay(wait); } } } // Slightly different, this makes the rainbow equally distributed throughout void rainbowCycle(uint8_t wait) { uint16_t i, j; for (j = 0; j < 256 * 5; j++) { // 5 cycles of all colors on wheel for (i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255)); } strip.show(); delay(wait); } } void rainbow(uint8_t wait) { uint16_t i, j; for (j = 0; j < 256; j++) { for (i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel((i + j) & 255)); } strip.show(); delay(wait); } } 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: esp8266_artnet_neopixel/neopixel_mode.h ================================================ #ifndef _MODE_H_ #define _MODE_H_ #include #include #include #include #include #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) #ifdef __cplusplus extern "C" { #endif extern long tic_frame; void map_hsv_to_rgb(int *, int *, int *); void singleRed(); void singleGreen(); void singleBlue(); void singleYellow(); void singleMagenta(); void singleCyan(); void singleWhite(); void fullBlack(); void mode0(uint16_t, uint16_t, uint8_t, uint8_t *); void mode1(uint16_t, uint16_t, uint8_t, uint8_t *); void mode2(uint16_t, uint16_t, uint8_t, uint8_t *); void mode3(uint16_t, uint16_t, uint8_t, uint8_t *); void mode4(uint16_t, uint16_t, uint8_t, uint8_t *); void mode5(uint16_t, uint16_t, uint8_t, uint8_t *); void mode6(uint16_t, uint16_t, uint8_t, uint8_t *); void mode7(uint16_t, uint16_t, uint8_t, uint8_t *); void mode8(uint16_t, uint16_t, uint8_t, uint8_t *); void mode9(uint16_t, uint16_t, uint8_t, uint8_t *); void mode10(uint16_t, uint16_t, uint8_t, uint8_t *); void mode11(uint16_t, uint16_t, uint8_t, uint8_t *); void mode12(uint16_t, uint16_t, uint8_t, uint8_t *); void mode13(uint16_t, uint16_t, uint8_t, uint8_t *); void mode14(uint16_t, uint16_t, uint8_t, uint8_t *); void mode15(uint16_t, uint16_t, uint8_t, uint8_t *); void mode16(uint16_t, uint16_t, uint8_t, uint8_t *); void mode17(uint16_t, uint16_t, uint8_t, uint8_t *); void mode18(uint16_t, uint16_t, uint8_t, uint8_t *); void mode19(uint16_t, uint16_t, uint8_t, uint8_t *); void mode20(uint16_t, uint16_t, uint8_t, uint8_t *); void mode21(uint16_t, uint16_t, uint8_t, uint8_t *); void mode22(uint16_t, uint16_t, uint8_t, uint8_t *); void mode23(uint16_t, uint16_t, uint8_t, uint8_t *); void mode24(uint16_t, uint16_t, uint8_t, uint8_t *); void mode25(uint16_t, uint16_t, uint8_t, uint8_t *); void mode26(uint16_t, uint16_t, uint8_t, uint8_t *); void mode27(uint16_t, uint16_t, uint8_t, uint8_t *); void mode28(uint16_t, uint16_t, uint8_t, uint8_t *); void mode29(uint16_t, uint16_t, uint8_t, uint8_t *); void mode30(uint16_t, uint16_t, uint8_t, uint8_t *); void mode31(uint16_t, uint16_t, uint8_t, uint8_t *); void mode32(uint16_t, uint16_t, uint8_t, uint8_t *); void mode33(uint16_t, uint16_t, uint8_t, uint8_t *); void mode34(uint16_t, uint16_t, uint8_t, uint8_t *); void mode35(uint16_t, uint16_t, uint8_t, uint8_t *); void mode36(uint16_t, uint16_t, uint8_t, uint8_t *); void mode37(uint16_t, uint16_t, uint8_t, uint8_t *); void mode38(uint16_t, uint16_t, uint8_t, uint8_t *); void mode39(uint16_t, uint16_t, uint8_t, uint8_t *); void mode40(uint16_t, uint16_t, uint8_t, uint8_t *); void mode41(uint16_t, uint16_t, uint8_t, uint8_t *); void mode42(uint16_t, uint16_t, uint8_t, uint8_t *); void mode43(uint16_t, uint16_t, uint8_t, uint8_t *); void mode44(uint16_t, uint16_t, uint8_t, uint8_t *); void mode45(uint16_t, uint16_t, uint8_t, uint8_t *); void mode46(uint16_t, uint16_t, uint8_t, uint8_t *); void mode47(uint16_t, uint16_t, uint8_t, uint8_t *); void mode48(uint16_t, uint16_t, uint8_t, uint8_t *); void mode49(uint16_t, uint16_t, uint8_t, uint8_t *); void mode50(uint16_t, uint16_t, uint8_t, uint8_t *); void mode51(uint16_t, uint16_t, uint8_t, uint8_t *); void mode52(uint16_t, uint16_t, uint8_t, uint8_t *); void mode53(uint16_t, uint16_t, uint8_t, uint8_t *); void mode54(uint16_t, uint16_t, uint8_t, uint8_t *); void mode55(uint16_t, uint16_t, uint8_t, uint8_t *); void mode56(uint16_t, uint16_t, uint8_t, uint8_t *); void mode57(uint16_t, uint16_t, uint8_t, uint8_t *); void mode58(uint16_t, uint16_t, uint8_t, uint8_t *); void mode59(uint16_t, uint16_t, uint8_t, uint8_t *); void mode60(uint16_t, uint16_t, uint8_t, uint8_t *); void mode61(uint16_t, uint16_t, uint8_t, uint8_t *); void mode62(uint16_t, uint16_t, uint8_t, uint8_t *); void mode63(uint16_t, uint16_t, uint8_t, uint8_t *); #ifdef __cplusplus } #endif #endif ================================================ FILE: esp8266_artnet_neopixel/webinterface.cpp ================================================ #include "webinterface.h" Config config; extern ESP8266WebServer server; extern int packetCounter; /***************************************************************************/ 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.universe = 1; config.offset = 0; config.pixels = 12; config.leds = 4; config.white = 0; config.brightness = 255; config.hsv = 0; config.mode = 1; config.reverse = 0; config.speed = 8; config.split = 1; 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 buf(new char[size]); configFile.readBytes(buf.get(), size); configFile.close(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(buf.get()); if (!root.success()) { Serial.println("Failed to parse config file"); return false; } N_JSON_TO_CONFIG(universe, "universe"); N_JSON_TO_CONFIG(offset, "offset"); N_JSON_TO_CONFIG(pixels, "pixels"); N_JSON_TO_CONFIG(leds, "leds"); N_JSON_TO_CONFIG(white, "white"); N_JSON_TO_CONFIG(brightness, "brightness"); N_JSON_TO_CONFIG(hsv, "hsv"); N_JSON_TO_CONFIG(mode, "mode"); N_JSON_TO_CONFIG(reverse, "reverse"); N_JSON_TO_CONFIG(speed, "speed"); N_JSON_TO_CONFIG(split, "split"); return true; } bool saveConfig() { Serial.println("saveConfig"); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); N_CONFIG_TO_JSON(universe, "universe"); N_CONFIG_TO_JSON(offset, "offset"); N_CONFIG_TO_JSON(pixels, "pixels"); N_CONFIG_TO_JSON(leds, "leds"); N_CONFIG_TO_JSON(white, "white"); N_CONFIG_TO_JSON(brightness, "brightness"); N_CONFIG_TO_JSON(hsv, "hsv"); N_CONFIG_TO_JSON(mode, "mode"); N_CONFIG_TO_JSON(reverse, "reverse"); N_CONFIG_TO_JSON(speed, "speed"); N_CONFIG_TO_JSON(split, "split"); 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"); root.printTo(configFile); configFile.close(); return true; } } 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 handleUpdate1() { server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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("universe") || server.hasArg("offset") || server.hasArg("pixels") || server.hasArg("leds") || server.hasArg("white") || server.hasArg("brightness") || server.hasArg("hsv") || server.hasArg("mode") || server.hasArg("reverse") || server.hasArg("speed") || server.hasArg("split")) { // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it N_KEYVAL_TO_CONFIG(universe, "universe"); N_KEYVAL_TO_CONFIG(offset, "offset"); N_KEYVAL_TO_CONFIG(pixels, "pixels"); N_KEYVAL_TO_CONFIG(leds, "leds"); N_KEYVAL_TO_CONFIG(white, "white"); N_KEYVAL_TO_CONFIG(brightness, "brightness"); N_KEYVAL_TO_CONFIG(hsv, "hsv"); N_KEYVAL_TO_CONFIG(mode, "mode"); N_KEYVAL_TO_CONFIG(reverse, "reverse"); N_KEYVAL_TO_CONFIG(speed, "speed"); N_KEYVAL_TO_CONFIG(split, "split"); handleStaticFile("/reload_success.html"); } else if (server.hasArg("plain")) { // parse the body as JSON object StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg("plain")); if (!root.success()) { handleStaticFile("/reload_failure.html"); return; } N_JSON_TO_CONFIG(universe, "universe"); N_JSON_TO_CONFIG(offset, "offset"); N_JSON_TO_CONFIG(pixels, "pixels"); N_JSON_TO_CONFIG(leds, "leds"); N_JSON_TO_CONFIG(white, "white"); N_JSON_TO_CONFIG(brightness, "brightness"); N_JSON_TO_CONFIG(hsv, "hsv"); N_JSON_TO_CONFIG(mode, "mode"); N_JSON_TO_CONFIG(reverse, "reverse"); N_JSON_TO_CONFIG(speed, "speed"); N_JSON_TO_CONFIG(split, "split"); handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; // do not save the configuration } saveConfig(); } ================================================ FILE: esp8266_artnet_neopixel/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ #include #include #include #include #include #include #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 /* 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.set(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 universe; int offset; int pixels; int leds; int white; int brightness; int hsv; int mode; int reverse; int speed; int split; }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void handleUpdate1(void); void handleUpdate2(void); void handleDirList(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif // _WEBINTERFACE_H_ ================================================ FILE: esp8266_config_spiffs/data/config.json ================================================ { "var1":10, "var2":20, "var3":30 } ================================================ FILE: esp8266_config_spiffs/esp8266_config_spiffs.ino ================================================ #include #include #include #if defined(ESP32) #include #endif #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 int var1, var2, var3; bool loadConfig() { File configFile = SPIFFS.open("/config.json", "r"); if (!configFile) { Serial.println("Failed to open config file for reading"); return false; } size_t size = configFile.size(); if (size > 1024) { Serial.println("Config file size is too large"); return false; } std::unique_ptr 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() { 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; } root.printTo(configFile); return true; } bool printConfig() { Serial.print("var1 = "); Serial.println(var1); Serial.print("var2 = "); Serial.println(var2); Serial.print("var3 = "); Serial.println(var3); return true; } void setup() { Serial.begin(115200); while (!Serial) { ; } delay(1000); Serial.println(""); Serial.println("setup start"); if (SPIFFS.begin()) { Serial.println("SPIFFS ok"); } else { Serial.println("SPIFFS fail"); } if (loadConfig()) { Serial.println("loadConfig ok"); } else { Serial.println("loadConfig fail"); } if (printConfig()) { Serial.println("printConfig ok"); } else { Serial.println("printConfig fail"); } if (saveConfig()) { Serial.println("saveConfig ok"); } else { Serial.println("loadConfig fail"); } SPIFFS.end(); Serial.println("setup end"); } void loop() { delay(1000); Serial.print("."); } ================================================ FILE: esp8266_config_webinterface/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 esp8266 sketches. It consists of a `settings.json` file in the SPIFFS filesystem and a `settings.html` file with some javascript. The settings can be changed in the webbrowser at http://192.168.1.xxx/settings and clicking "Send". This results in `ESP8266Webserver` parsing the arguments and placing the variables in the header, but also places the query string in the body. The following results in `ESP8266Webserver` 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":10,"var2":20,"var3":30}' curl -X PUT http://192.168.1.xxx/json -d '{"var1":10,"var2":20,"var3":30}' ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## Arduino ESP8266 filesystem uploader This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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: esp8266_config_webinterface/data/config.json ================================================ { "var1":10, "var2":20, "var3":30, "var4":40 } ================================================ FILE: esp8266_config_webinterface/data/hello.html ================================================ Hello

Hello kitty!

================================================ FILE: esp8266_config_webinterface/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_config_webinterface/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp8266_config_webinterface/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_config_webinterface/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_config_webinterface/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_config_webinterface/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: esp8266_config_webinterface/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_config_webinterface/esp8266_config_webinterface.ino ================================================ #include #include // https://arduinojson.org #include // https://github.com/esp8266/Arduino #include // https://github.com/tzapu/WiFiManager #include #include #include #include #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 ESP8266WebServer server(80); const char* host = "esp8266"; 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 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 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"]; printConfig(); 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 handleUpdate1() { Serial.println("handleUpdate1"); server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { Serial.println("handleUpdate2"); HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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 ESP8266Webserver 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; } saveConfig(); } void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.println(""); Serial.println("setup"); SPIFFS.begin(); loadConfig(); 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("/reconnect", HTTP_GET, []() { Serial.println("handleWifi"); handleStaticFile("/reload_success.html"); server.close(); server.stop(); delay(5000); WiFiManager wifiManager; wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0)); wifiManager.startConfigPortal(host); Serial.println("connected"); server.begin(); }); server.on("/restart", HTTP_GET, []() { Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, handleDirList); 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); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // ask server to track these headers const char * headerkeys[] = {"User-Agent","Content-Type"} ; size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*); server.collectHeaders(headerkeys, headerkeyssize ); server.begin(); MDNS.begin(host); MDNS.addService("http", "tcp", 80); } void loop() { // put your main code here, to run repeatedly server.handleClient(); delay(10); // in milliseconds } ================================================ FILE: esp8266_fan_control/esp8266_fan_control.ino ================================================ /* This sketch is for an ESP8266 connected to an ARCTIC BioniX F140 140 mm diameter fan with PWM control. See https://www.arctic.de/media/55/b2/e4/1690270670/Spec_Sheet_BioniX_F140_EN.pdf The sketch controls the speed of the fan and uses an interrupt to measure the speed. Output is provided on the serial interface. */ #include const int controlPin = D1; const int sensorPin = D2; void ICACHE_RAM_ATTR callback(); const char* version = __DATE__ " / " __TIME__; volatile unsigned int counter = 0; void setup() { Serial.begin(115200); Serial.print("\n[esp8266_fan_control / "); Serial.print(version); Serial.println("]"); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); analogWriteFreq(40000); pinMode(controlPin, OUTPUT); analogWrite(controlPin, 255); // this is where the speed is controlled pinMode(sensorPin, INPUT); attachInterrupt(digitalPinToInterrupt(sensorPin), callback, RISING); } void loop() { unsigned int rps, rpm; noInterrupts(); rps = counter; counter = 0; interrupts(); // convert from counts per second to rotations per minute rpm = rps * 60 / 2; Serial.print("Counts: "); Serial.print(rps, 1); Serial.print(" RPM: "); Serial.println(rpm); digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); delay(1000); // run once per second } void callback() { counter++; } ================================================ FILE: esp8266_fieldtrip_buffer/esp8266_fieldtrip_buffer.ino ================================================ #include #include "secret.h" #include "fieldtrip_buffer.h" int ftserver = 0, status = 0; #define NCHANS 1 #define FSAMPLE 1000 #define BLOCKSIZE 100 #define FTHOST "192.168.1.34" #define FTPORT 1972 uint16_t buf[BLOCKSIZE]; /**************************************************************************************************/ void setup() { Serial.begin(115200); delay(1000); Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(SSID); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); ftserver = fieldtrip_open_connection(FTHOST, FTPORT); if (ftserver > 0) { Serial.println("Connection opened"); status = fieldtrip_write_header(ftserver, DATATYPE_UINT16, NCHANS, FSAMPLE); if (status == 0) Serial.println("Wrote header"); else Serial.println("Failed writing header"); } else { Serial.println("Failed opening connection"); } for (int i = 0; i < BLOCKSIZE; i++) buf[i] = 0; } /**************************************************************************************************/ void loop() { if (ftserver == 0) { ftserver = fieldtrip_open_connection(FTHOST, FTPORT); if (ftserver > 0) Serial.println("Connection opened"); else Serial.println("Failed opening connection"); } if (ftserver > 0) { status = fieldtrip_write_data(ftserver, DATATYPE_UINT16, NCHANS, BLOCKSIZE, (byte *)buf); if (status == 0) Serial.println("Wrote data"); else { Serial.println("Failed writing data"); status = fieldtrip_close_connection(ftserver); if (status == 0) Serial.println("Connection closed"); else Serial.println("Failed closing connection"); ftserver = 0; } } for (int i = 0; i < BLOCKSIZE; i++) buf[i] += 1; delay((1000 * BLOCKSIZE) / FSAMPLE); } ================================================ FILE: esp8266_fieldtrip_buffer/fieldtrip_buffer.cpp ================================================ #include "fieldtrip_buffer.h" WiFiClient client; #undef DEBUG /******************************************************************************* OPEN CONNECTION returns file descriptor that should be >0 on success *******************************************************************************/ int fieldtrip_open_connection(const char *address, int port) { int status; status = client.connect(address, port); if (status == 1) return 1; else return -1; }; /******************************************************************************* CLOSE CONNECTION returns 0 on success *******************************************************************************/ int fieldtrip_close_connection(int s) { client.stop(); return 0; }; /******************************************************************************* WRITE HEADER returns 0 on success *******************************************************************************/ int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; headerdef_t *header = NULL; byte msg[8+24]; for (int i = 0; i < 8+24; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_HDR_NORESPONSE; request->bufsize = sizeof(headerdef_t); header = (headerdef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize header->nchans = nchans; header->nsamples = 0; header->nevents = 0; header->fsample = fsample; header->data_type = datatype; header->bufsize = 0; #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(headerdef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(headerdef_t)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n == sizeof(messagedef_t) + sizeof(headerdef_t)) return 0; else return -1; }; /******************************************************************************* WRITE DATA returns 0 on success *******************************************************************************/ int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; datadef_t *data = NULL; byte msg[8+16]; for (int i = 0; i < 8+16; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_DAT_NORESPONSE; request->bufsize = sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype); data = (datadef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize data->nchans = nchans; data->nsamples = nsamples; data->data_type = datatype; data->bufsize = nchans * nsamples * wordsize_from_type(datatype); #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(datadef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(datadef_t)); client.flush(); n += client.write(buffer, nchans * nsamples * wordsize_from_type(datatype)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n == sizeof(messagedef_t) + sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype)) return 0; else return -1; }; int wordsize_from_type(uint32_t datatype) { int wordsize = 0; switch (datatype) { case DATATYPE_CHAR: wordsize = 1; break; case DATATYPE_UINT8: wordsize = 1; break; case DATATYPE_UINT16: wordsize = 2; break; case DATATYPE_UINT32: wordsize = 4; break; case DATATYPE_UINT64: wordsize = 8; break; case DATATYPE_INT8: wordsize = 1; break; case DATATYPE_INT16: wordsize = 2; break; case DATATYPE_INT32: wordsize = 4; break; case DATATYPE_INT64: wordsize = 8; break; case DATATYPE_FLOAT32: wordsize = 4; break; case DATATYPE_FLOAT64: wordsize = 8; break; } return wordsize; }; ================================================ FILE: esp8266_fieldtrip_buffer/fieldtrip_buffer.h ================================================ #ifndef __BUFFER_H_ #define __BUFFER_H_ #include // definition of simplified interface functions, not all of them are implemented int fieldtrip_start_server(int port); int fieldtrip_open_connection(const char *hostname, int port); int fieldtrip_close_connection(int s); int fieldtrip_read_header(int server, uint32_t *datatype, uint32_t *nchans, float *fsample, uint32_t *nsamples, uint32_t *nevents); int fieldtrip_read_data(int server, uint32_t begsample, uint32_t endsample, byte *buffer); int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample); int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer); int fieldtrip_wait_data(int server, uint32_t nsamples, uint32_t nevents, uint32_t milliseconds); int wordsize_from_type(uint32_t datatype); // define the version of the message packet #define VERSION (uint16_t)0x0001 // these define the commands that can be used, which are split over the two available bytes #define PUT_HDR (uint16_t)0x0101 /* decimal 257 */ #define PUT_DAT (uint16_t)0x0102 /* decimal 258 */ #define PUT_EVT (uint16_t)0x0103 /* decimal 259 */ #define PUT_OK (uint16_t)0x0104 /* decimal 260 */ #define PUT_ERR (uint16_t)0x0105 /* decimal 261 */ #define GET_HDR (uint16_t)0x0201 /* decimal 513 */ #define GET_DAT (uint16_t)0x0202 /* decimal 514 */ #define GET_EVT (uint16_t)0x0203 /* decimal 515 */ #define GET_OK (uint16_t)0x0204 /* decimal 516 */ #define GET_ERR (uint16_t)0x0205 /* decimal 517 */ #define FLUSH_HDR (uint16_t)0x0301 /* decimal 769 */ #define FLUSH_DAT (uint16_t)0x0302 /* decimal 770 */ #define FLUSH_EVT (uint16_t)0x0303 /* decimal 771 */ #define FLUSH_OK (uint16_t)0x0304 /* decimal 772 */ #define FLUSH_ERR (uint16_t)0x0305 /* decimal 773 */ #define WAIT_DAT (uint16_t)0x0402 /* decimal 1026 */ #define WAIT_OK (uint16_t)0x0404 /* decimal 1027 */ #define WAIT_ERR (uint16_t)0x0405 /* decimal 1028 */ #define PUT_HDR_NORESPONSE (uint16_t)0x0501 /* decimal 1281 */ #define PUT_DAT_NORESPONSE (uint16_t)0x0502 /* decimal 1282 */ #define PUT_EVT_NORESPONSE (uint16_t)0x0503 /* decimal 1283 */ // these are used in the data_t and event_t structure #define DATATYPE_CHAR (uint32_t)0 #define DATATYPE_UINT8 (uint32_t)1 #define DATATYPE_UINT16 (uint32_t)2 #define DATATYPE_UINT32 (uint32_t)3 #define DATATYPE_UINT64 (uint32_t)4 #define DATATYPE_INT8 (uint32_t)5 #define DATATYPE_INT16 (uint32_t)6 #define DATATYPE_INT32 (uint32_t)7 #define DATATYPE_INT64 (uint32_t)8 #define DATATYPE_FLOAT32 (uint32_t)9 #define DATATYPE_FLOAT64 (uint32_t)10 // a packet that is sent over the network should contain the following typedef struct { uint16_t version; // see VERSION uint16_t command; // see PUT_xxx, GET_xxx and FLUSH_xxx uint32_t bufsize; // size of the buffer in bytes } messagedef_t; // 8 bytes // the header definition is fixed, it can be followed by additional header chunks typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t nevents; float fsample; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } headerdef_t; // 24 bytes // the data definition is fixed, it should be followed by additional data typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } datadef_t; // 16 bytes // the event definition is fixed, it should be followed by additional data typedef struct { uint32_t type_type; /* usual would be DATATYPE_CHAR */ uint32_t type_numel; /* length of the type string */ uint32_t value_type; uint32_t value_numel; int32_t sample; int32_t offset; int32_t duration; uint32_t bufsize; /* size of the buffer in bytes */ } eventdef_t; // 64 bytes #endif ================================================ FILE: esp8266_imu_osc/I2Cscan.cpp ================================================ #include #include #include "I2Cscan.h" void I2Cscan() { // scan for i2c devices byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for (address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknow error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); } ================================================ FILE: esp8266_imu_osc/I2Cscan.h ================================================ #ifndef __I2CSCAN_H__ #define __I2CSCAN_H__ void I2Cscan(); #endif ================================================ FILE: esp8266_imu_osc/README.md ================================================ # Overview This is an Arduino sketch for an ESP8266 module that is connected to a number of inertial motion units (IMUs). It sends the data from the IMUs over OSC to a computer. See https://robertoostenveld.nl/motion-capture-system/ for more details. ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ================================================ FILE: esp8266_imu_osc/data/config.json ================================================ { "sensors": 8, "decimate": 1, "calibrate": 0, "raw": 1, "ahrs": 0, "quaternion": 0, "termperature": 0, "destination": "192.168.1.34", "port": 8000 } ================================================ FILE: esp8266_imu_osc/data/hello.html ================================================ Hello

Hello rabbit!

================================================ FILE: esp8266_imu_osc/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_imu_osc/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
Sample rate:
?
================================================ FILE: esp8266_imu_osc/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_imu_osc/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_imu_osc/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_imu_osc/data/style.css ================================================ .c{ text-align: center; } div,input{ padding:5px;font-size:1em; } input{ width:85%; } 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: esp8266_imu_osc/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_imu_osc/esp8266_imu_osc.ino ================================================ /* This sketch is for an ESP8266 connected to an TCA9548A I2C multiplexer, which in turn connects to multiple MPU9250 inertial motion units (IMUs). It reads the acceletometer, gyroscope and magnetometer data from the IMUs and computes AHRS (yaw/pitch/roll) parameters. All data is combined and sent over UDP using the OSC protocol. It uses WIFiManager for initial configuration and includes a web-interface that allows to monitor and change parameters. The status of the wifi connection, http server and the IMU data transmission is indicated by a RDB led that blinks Red, Green or Blue. */ #include #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include #include // from https://github.com/robertoostenveld/SparkFun_MPU-9250_Breakout_Arduino_Library #include #include #include "webinterface.h" #include "rgb_led.h" #include "tca9548a.h" #include "I2Cscan.h" #define debugLevel 2 // 0 = silent, 1 = blink led, 2 = print on serial console #define maxSensors 8 // this allows some sections of the code to be disabled for debugging purposes #define ENABLE_WEBINTERFACE #define ENABLE_MDNS #define ENABLE_IMU #define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MAX(x,y) ((x) > (y) ? (x) : (y)) // Use either of these to select which I2C address your device is using #define MPU9250_ADDRESS MPU9250_ADDRESS_AD0 // #define MPU9250_ADDRESS MPU9250_ADDRESS_AD1 ESP8266WebServer server(80); const char* host = "IMU-OSC"; const char* version = __DATE__ " / " __TIME__; tca9548a tca; MPU9250 mpu[maxSensors]; String id[maxSensors] = {"imu1", "imu2", "imu3", "imu4", "imu5", "imu6", "imu7", "imu8"}; // UDP destination address, these will be changed according to the configuration IPAddress outIp(192, 168, 1, 100); const unsigned int inPort = 9000, outPort = 8000; WiFiUDP Udp; float rate = 0.0f; float deltat = 0.0f, sum = 0.0f; // integration interval for both filter schemes uint32_t Now = 0, Last[maxSensors]; // used to calculate integration interval uint32_t lastDisplay = 0, lastTemperature = 0, lastTransmit = 0; unsigned int debugCount = 0, measurementCount = 0; // For continous calibration float mag_min[3] = { -1e6, -1e6, -1e6}; float mag_max[3] = {1e6, 1e6, 1e6}; float mag_scale[3] = {1, 1, 1}; // keep track of the timing of the web interface long tic_web = 0; void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.print("\n[esp8266_imu_osc / "); Serial.print(__DATE__ " / " __TIME__); Serial.println("]"); WiFi.hostname(host); WiFi.begin(); pinMode(0, OUTPUT); // RESET=D3=0 digitalWrite(0, LOW); // reset on delay(10); digitalWrite(0, HIGH); // reset off Wire.begin(5, 4); // SDA=D1=5, SCL=D2=4 Wire.setClock(400000L); SPIFFS.begin(); // Used for OSC Udp.begin(inPort); ledInit(); if (loadConfig()) { ledYellow(); delay(1000); } else { ledRed(); delay(1000); } if (WiFi.status() != WL_CONNECTED) ledRed(); 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"); if (WiFi.status() == WL_CONNECTED) ledGreen(); #ifdef ENABLE_WEBINTERFACE // this serves all URIs that can be resolved to a file on the SPIFFS filesystem server.onNotFound(handleNotFound); server.on("/", HTTP_GET, []() { tic_web = millis(); handleRedirect("/index.html"); }); server.on("/defaults", HTTP_GET, []() { tic_web = millis(); Serial.println("handleDefaults"); handleStaticFile("/reload_success.html"); defaultConfig(); saveConfig(); server.close(); server.stop(); ESP.restart(); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); handleStaticFile("/reload_success.html"); ledRed(); 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); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) ledGreen(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); ledRed(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, [] { tic_web = millis(); handleDirList(); }); server.on("/json", HTTP_PUT, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_POST, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_GET, [] { tic_web = millis(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); N_CONFIG_TO_JSON(sensors, "sensors"); N_CONFIG_TO_JSON(decimate, "decimate"); N_CONFIG_TO_JSON(calibrate, "calibrate"); N_CONFIG_TO_JSON(raw, "raw"); N_CONFIG_TO_JSON(ahrs, "ahrs"); N_CONFIG_TO_JSON(quaternion, "quaternion"); N_CONFIG_TO_JSON(temperature, "temperature"); S_CONFIG_TO_JSON(destination, "destination"); N_CONFIG_TO_JSON(port, "port"); root["version"] = version; root["uptime"] = long(millis() / 1000); root["rate"] = rate; String str; root.printTo(str); server.setContentLength(str.length()); server.send(200, "application/json", str); }); server.on("/update", HTTP_GET, [] { tic_web = millis(); handleStaticFile("/update.html"); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // start the web server server.begin(); #endif #ifdef ENABLE_MDNS // announce the hostname and web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); #endif #ifdef ENABLE_IMU ledMagenta(); // Look for I2C devices on the bus I2Cscan(); byte status = 0; for (int i = 0; i < config.sensors; i++) { Serial.println("===================================================="); Serial.println(String("initializing " + id[i])); tca.select(i); // Read the WHO_AM_I register, this is a good test of communication byte c = mpu[i].readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250); Serial.print(F("MPU9250 I am 0x")); Serial.print(c, HEX); Serial.print(F(" I should be 0x")); Serial.println(0x71, HEX); // WHO_AM_I should be 0x71 for MPU9250 or 0x73 for MPU9255 if ((c == 0x71) || (c == 0x73)) { Serial.println(F("MPU9250 is online...")); // Start by performing self test and reporting values mpu[i].MPU9250SelfTest(mpu[i].selfTest); Serial.print(F("x-axis self test: acceleration trim within : ")); Serial.print(mpu[i].selfTest[0], 1); Serial.println("% of factory value"); Serial.print(F("y-axis self test: acceleration trim within : ")); Serial.print(mpu[i].selfTest[1], 1); Serial.println("% of factory value"); Serial.print(F("z-axis self test: acceleration trim within : ")); Serial.print(mpu[i].selfTest[2], 1); Serial.println("% of factory value"); Serial.print(F("x-axis self test: gyration trim within : ")); Serial.print(mpu[i].selfTest[3], 1); Serial.println("% of factory value"); Serial.print(F("y-axis self test: gyration trim within : ")); Serial.print(mpu[i].selfTest[4], 1); Serial.println("% of factory value"); Serial.print(F("z-axis self test: gyration trim within : ")); Serial.print(mpu[i].selfTest[5], 1); Serial.println("% of factory value"); // Calibrate gyro and accelerometers, load biases in bias registers mpu[i].calibrateMPU9250(mpu[i].gyroBias, mpu[i].accelBias); mpu[i].initMPU9250(); // Initialize device for active mode read of acclerometer, gyroscope, and temperature Serial.println("MPU9250 initialized for active data mode...."); // Read the WHO_AM_I register of the magnetometer, this is a good test of communication byte d = mpu[i].readByte(AK8963_ADDRESS, WHO_AM_I_AK8963); Serial.print("AK8963 "); Serial.print("I am 0x"); Serial.print(d, HEX); Serial.print(" I should be 0x"); Serial.println(0x48, HEX); if (d != 0x48) { status++; } // Get magnetometer calibration from AK8963 ROM mpu[i].initAK8963(mpu[i].factoryMagCalibration); // Initialize device for active mode read of magnetometer Serial.println("AK8963 initialized for active data mode...."); // Serial.println("Calibration values: "); Serial.print("X-Axis factory magnetometer adjustment value "); Serial.println(mpu[i].factoryMagCalibration[0], 2); Serial.print("Y-Axis factory magnetometer adjustment value "); Serial.println(mpu[i].factoryMagCalibration[1], 2); Serial.print("Z-Axis factory magnetometer adjustment value "); Serial.println(mpu[i].factoryMagCalibration[2], 2); // Get sensor resolutions, only need to do this once mpu[i].getAres(); mpu[i].getGres(); mpu[i].getMres(); if (config.calibrate == 1) { // The next call delays for 4 seconds, and then records about 15 seconds of data to calculate bias and scale. mpu[i].magCalMPU9250(mpu[i].magBias, mpu[i].magScale); Serial.println("AK8963 mag biases (mG)"); Serial.println(mpu[i].magBias[0]); Serial.println(mpu[i].magBias[1]); Serial.println(mpu[i].magBias[2]); Serial.println("AK8963 mag scale (mG)"); Serial.println(mpu[i].magScale[0]); Serial.println(mpu[i].magScale[1]); Serial.println(mpu[i].magScale[2]); } } // if (c == 0x71) else { status++; } if (status) { Serial.println("not present"); config.sensors = i; // the index starts at 0 break; } } // for each sensor Serial.println("===================================================="); #endif Serial.println("===================================================="); Serial.println("Setup done"); Serial.println("===================================================="); } void loop() { OSCBundle bundle; char msgId[16]; #ifdef ENABLE_WEBINTERFACE server.handleClient(); #endif if (WiFi.status() != WL_CONNECTED) { ledRed(); } else if ((millis() - tic_web) < 3000) { // serving content on the http interface takes a lot of resources and // messes up the regular timing of the acquisition and transmission ledBlue(); } #ifdef ENABLE_IMU for (int i = 0; i < config.sensors; i++) { const float *q; tca.select(i); while (!(mpu[i].readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)) delay(1); mpu[i].readAccelData(mpu[i].accelCount); // Read the x/y/z adc values // Now we'll calculate the accleration value into actual g's // This depends on scale being set mpu[i].ax = (float)mpu[i].accelCount[0] * mpu[i].aRes; // - mpu[i].accelBias[0]; mpu[i].ay = (float)mpu[i].accelCount[1] * mpu[i].aRes; // - mpu[i].accelBias[1]; mpu[i].az = (float)mpu[i].accelCount[2] * mpu[i].aRes; // - mpu[i].accelBias[2]; mpu[i].readGyroData(mpu[i].gyroCount); // Read the x/y/z adc values // Calculate the gyro value into actual degrees per second // This depends on scale being set mpu[i].gx = (float)mpu[i].gyroCount[0] * mpu[i].gRes; // - mpu[i].gyroBias[0]; mpu[i].gy = (float)mpu[i].gyroCount[1] * mpu[i].gRes; // - mpu[i].gyroBias[1]; mpu[i].gz = (float)mpu[i].gyroCount[2] * mpu[i].gRes; // - mpu[i].gyroBias[2]; mpu[i].readMagData(mpu[i].magCount); // Read the x/y/z adc values // Calculate the magnetometer values in milliGauss // Get actual magnetometer value, this depends on scale being set // Include factory calibration per data sheet and user environmental corrections mpu[i].mx = (float)mpu[i].magCount[0] * mpu[i].mRes * mpu[i].factoryMagCalibration[0] * mpu[i].magScale[0] - mpu[i].magBias[0]; mpu[i].my = (float)mpu[i].magCount[1] * mpu[i].mRes * mpu[i].factoryMagCalibration[1] * mpu[i].magScale[1] - mpu[i].magBias[1]; mpu[i].mz = (float)mpu[i].magCount[2] * mpu[i].mRes * mpu[i].factoryMagCalibration[2] * mpu[i].magScale[2] - mpu[i].magBias[2]; if (config.raw) { String(id[i] + "/a").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].ax).add(mpu[i].ay).add(mpu[i].az); String(id[i] + "/g").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].gx).add(mpu[i].gy).add(mpu[i].gz); String(id[i] + "/m").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].mx).add(mpu[i].my).add(mpu[i].mz); } if (config.calibrate > 1) { // calibrate==2 means only for the first 30 seconds // calibrate==3 means continuous // the min value is negative, the max value is positive mag_min[0] = MIN(mpu[i].mx, mag_min[0]); mag_min[1] = MIN(mpu[i].my, mag_min[1]); mag_min[2] = MIN(mpu[i].mz, mag_min[2]); mag_max[0] = MAX(mpu[i].mx, mag_max[0]); mag_max[1] = MAX(mpu[i].my, mag_max[1]); mag_max[2] = MAX(mpu[i].mz, mag_max[2]); // the sum of the min and max should be zero mpu[i].magBias[0] = (mag_max[0] + mag_min[0]) / 2; mpu[i].magBias[1] = (mag_max[1] + mag_min[1]) / 2; mpu[i].magBias[2] = (mag_max[2] + mag_min[2]) / 2; // the min and max themselves should be one mag_scale[0] = (mag_max[0] - mag_min[0]) / 2; mag_scale[1] = (mag_max[1] - mag_min[1]) / 2; mag_scale[2] = (mag_max[2] - mag_min[2]) / 2; mpu[i].magScale[0] = 3 * mag_scale[0] / (mag_scale[0] + mag_scale[1] + mag_scale[2]); mpu[i].magScale[1] = 3 * mag_scale[1] / (mag_scale[0] + mag_scale[1] + mag_scale[2]); mpu[i].magScale[2] = 3 * mag_scale[2] / (mag_scale[0] + mag_scale[1] + mag_scale[2]); } if (config.ahrs || config.quaternion) { // Must be called before updating quaternions! mpu[i].updateTime(); // Sensors x (y)-axis of the accelerometer is aligned with the y (x)-axis of // the magnetometer; the magnetometer z-axis (+ down) is opposite to z-axis // (+ up) of accelerometer and gyro! We have to make some allowance for this // orientationmismatch in feeding the output to the quaternion filter. For the // MPU-9250, we have chosen a magnetic rotation that keeps the sensor forward // along the x-axis just like in the LSM9DS0 sensor. This rotation can be // modified to allow any convenient orientation convention. This is ok by // aircraft orientation standards! Pass gyro rate as rad/s // MadgwickQuaternionUpdate(mpu[i].ax, mpu[i].ay, mpu[i].az, mpu[i].gx * DEG_TO_RAD, mpu[i].gy * DEG_TO_RAD, mpu[i].gz * DEG_TO_RAD, mpu[i].my, mpu[i].mx, -mpu[i].mz, mpu[i].deltat); MahonyQuaternionUpdate(mpu[i].ax, mpu[i].ay, mpu[i].az, mpu[i].gx * DEG_TO_RAD, mpu[i].gy * DEG_TO_RAD, mpu[i].gz * DEG_TO_RAD, mpu[i].my, mpu[i].mx, -mpu[i].mz, mpu[i].deltat); // Define output variables from updated quaternion---these are Tait-Bryan // angles, commonly used in aircraft orientation. In this coordinate system, // the positive z-axis is down toward Earth. Yaw is the angle between Sensor // x-axis and Earth magnetic North (or true North if corrected for local // declination, looking down on the sensor positive yaw is counterclockwise. // Pitch is angle between sensor x-axis and Earth ground plane, toward the // Earth is positive, up toward the sky is negative. Roll is angle between // sensor y-axis and Earth ground plane, y-axis up is positive roll. These // arise from the definition of the homogeneous rotation matrix constructed // from quaternions. Tait-Bryan angles as well as Euler angles are // non-commutative; that is, the get the correct orientation the rotations // must be applied in the correct order which for this configuration is yaw, // pitch, and then roll. // For more see // http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles // which has additional links. q = getQ(); mpu[i].yaw = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]); mpu[i].pitch = -asin(2.0f * (q[1] * q[3] - q[0] * q[2])); mpu[i].roll = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]); mpu[i].yaw *= RAD_TO_DEG; mpu[i].pitch *= RAD_TO_DEG; mpu[i].roll *= RAD_TO_DEG; // See http://www.ngdc.noaa.gov/geomag-web/#declination // Declination of SparkFun Electronics (40°05'26.6"N 105°11'05.9"W) is 8° 30' E ± 0° 21' (or 8.5°) on 2016-07-19 // mpu[i].yaw -= 8.5; // Declination at Nijmegen, NL is 1 degrees 31 minutes and 00 seconds on 2017-12-16 mpu[i].yaw -= 1.5167; if (config.ahrs) { String(id[i] + "/roll").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].roll); String(id[i] + "/yaw").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].yaw); String(id[i] + "/pitch").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].pitch); } if (config.quaternion) { String(id[i] + "/q").toCharArray(msgId, 16); bundle.add(msgId).add(q[0]).add(q[1]).add(q[2]).add(q[3]); } } // if ahrs or quaternion if (config.temperature) { mpu[i].tempCount = mpu[i].readTempData(); // Read the adc values mpu[i].temperature = ((float) mpu[i].tempCount) / 333.87 + 21.0; // Temperature in degrees Centigrade String(id[i] + "/temp").toCharArray(msgId, 16); bundle.add(msgId).add(mpu[i].temperature); } // this section is in micros Now = micros(); rate = 1000000.0f / (Now - Last[i]); String(id[i] + "/rate").toCharArray(msgId, 16); bundle.add(msgId).add(rate); String(id[i] + "/time").toCharArray(msgId, 16); bundle.add(msgId).add(Now / 1000000.0f); Last[i] = Now; } // for each sensor // the previous section was in micros, the following section is in millis Now = millis(); // Update the debug information every second, independent of data rates if ((debugLevel > 0) && (Now - lastDisplay) > 1000) { // blink green LED on and off every second if (digitalRead(LED_G)) { ledBlack(); } else { ledGreen(); } if (debugLevel > 1) { for (int i = 0; i < 1; i++) { // Print acceleration values in milligs! Serial.print("X-acceleration: "); Serial.print(1000 * mpu[i].ax); Serial.print(" mg "); Serial.print("Y-acceleration: "); Serial.print(1000 * mpu[i].ay); Serial.print(" mg "); Serial.print("Z-acceleration: "); Serial.print(1000 * mpu[i].az); Serial.println(" mg "); // Print gyro values in degree/sec Serial.print("X-gyro rate: "); Serial.print(mpu[i].gx, 3); Serial.print(" degrees/sec "); Serial.print("Y-gyro rate: "); Serial.print(mpu[i].gy, 3); Serial.print(" degrees/sec "); Serial.print("Z-gyro rate: "); Serial.print(mpu[i].gz, 3); Serial.println(" degrees/sec"); // Print mag values in milliGauss Serial.print("X-mag field: "); Serial.print(mpu[i].mx); Serial.print(" mG "); Serial.print("Y-mag field: "); Serial.print(mpu[i].my); Serial.print(" mG "); Serial.print("Z-mag field: "); Serial.print(mpu[i].mz); Serial.println(" mG"); if (config.ahrs) { // Print AHRS values in degrees Serial.print("Yaw, Pitch, Roll: "); Serial.print(mpu[i].yaw, 2); Serial.print(", "); Serial.print(mpu[i].pitch, 2); Serial.print(", "); Serial.print(mpu[i].roll, 2); Serial.println(" degrees"); } // if (config.ahrs) if (config.temperature) { Serial.print("Temperature: "); Serial.print(mpu[i].temperature, 1); Serial.println(" degrees C"); } } } // if debugLevel>1 lastDisplay = millis(); } // if debugLevel>0 measurementCount++; if ((measurementCount % config.decimate) == 0) { // only send every Nth measurement outIp.fromString(config.destination); Udp.beginPacket(outIp, config.port); bundle.send(Udp); Udp.endPacket(); bundle.empty(); lastTransmit = Now; measurementCount = 0; } #endif } // loop ================================================ FILE: esp8266_imu_osc/rgb_led.cpp ================================================ #include "rgb_led.h" void ledInit() { pinMode(LED_R, OUTPUT); pinMode(LED_G, OUTPUT); pinMode(LED_B, OUTPUT); } #ifdef COMMON_ANODE #define LED_ON LOW #define LED_OFF HIGH #else #define LED_ON HIGH #define LED_OFF LOW #endif void ledRed() { digitalWrite(LED_R, LED_ON); digitalWrite(LED_G, LED_OFF); digitalWrite(LED_B, LED_OFF); } void ledGreen() { digitalWrite(LED_R, LED_OFF); digitalWrite(LED_G, LED_ON); digitalWrite(LED_B, LED_OFF); } void ledBlue() { digitalWrite(LED_R, LED_OFF); digitalWrite(LED_G, LED_OFF); digitalWrite(LED_B, LED_ON); } void ledYellow() { digitalWrite(LED_R, LED_ON); digitalWrite(LED_G, LED_ON); digitalWrite(LED_B, LED_OFF); } void ledMagenta() { digitalWrite(LED_R, LED_ON); digitalWrite(LED_G, LED_OFF); digitalWrite(LED_B, LED_ON); } void ledCyan() { digitalWrite(LED_R, LED_OFF); digitalWrite(LED_G, LED_ON); digitalWrite(LED_B, LED_ON); } void ledBlack() { digitalWrite(LED_R, LED_OFF); digitalWrite(LED_G, LED_OFF); digitalWrite(LED_B, LED_OFF); } void ledWhite() { digitalWrite(LED_R, LED_ON); digitalWrite(LED_G, LED_ON); digitalWrite(LED_B, LED_ON); } ================================================ FILE: esp8266_imu_osc/rgb_led.h ================================================ #ifndef _RGB_LED_H_ #define _RGB_LED_H_ #include #define LED_R D5 #define LED_G D6 #define LED_B D7 // #define COMMON_ANODE void ledInit(); void ledRed(); void ledGreen(); void ledBlue(); void ledYellow(); void ledMagenta(); void ledCyan(); void ledBlack(); void ledWhite(); #endif ================================================ FILE: esp8266_imu_osc/tca9548a.cpp ================================================ #include "tca9548a.h" tca9548a::tca9548a(void) { } tca9548a::~tca9548a(void) { } void tca9548a::select(uint8_t channel) { if (channel > 7) return; Wire.beginTransmission(TCA9548A_ADDRESS); Wire.write(1 << channel); Wire.endTransmission(); } void tca9548a::disable(void) { Wire.beginTransmission(TCA9548A_ADDRESS); Wire.write(0); // no channel selected Wire.endTransmission(); } ================================================ FILE: esp8266_imu_osc/tca9548a.h ================================================ #ifndef __TCA9548A_H_ #define __TCA9548A_H_ #include #include #define TCA9548A_ADDRESS 0x70 class tca9548a { public: ~tca9548a(void); tca9548a(void); void select(uint8_t); void disable(); private: uint8_t _address; }; // class #endif ================================================ FILE: esp8266_imu_osc/webinterface.cpp ================================================ #include "webinterface.h" #include "rgb_led.h" Config config; extern ESP8266WebServer server; extern unsigned long packetCounter; /***************************************************************************/ 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() { config.sensors = 8; config.decimate = 1; config.calibrate = 0; config.raw = 1; config.ahrs = 0; config.quaternion = 0; config.temperature = 0; strncpy(config.destination, "192.168.1.34", 32); config.port = 8000; 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 buf(new char[size]); configFile.readBytes(buf.get(), size); configFile.close(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(buf.get()); if (!root.success()) { Serial.println("Failed to parse config file"); return false; } N_JSON_TO_CONFIG(sensors, "sensors"); N_JSON_TO_CONFIG(decimate, "decimate"); N_JSON_TO_CONFIG(calibrate, "calibrate"); N_JSON_TO_CONFIG(raw, "raw"); N_JSON_TO_CONFIG(ahrs, "ahrs"); N_JSON_TO_CONFIG(quaternion, "quaternion"); N_JSON_TO_CONFIG(temperature, "temperature"); S_JSON_TO_CONFIG(destination, "destination"); N_JSON_TO_CONFIG(port, "port"); return true; } bool saveConfig() { Serial.println("saveConfig"); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); N_CONFIG_TO_JSON(sensors, "sensors"); N_CONFIG_TO_JSON(decimate, "decimate"); N_CONFIG_TO_JSON(calibrate, "calibrate"); N_CONFIG_TO_JSON(raw, "raw"); N_CONFIG_TO_JSON(ahrs, "ahrs"); N_CONFIG_TO_JSON(quaternion, "quaternion"); N_CONFIG_TO_JSON(temperature, "temperature"); S_CONFIG_TO_JSON(destination, "destination"); N_CONFIG_TO_JSON(port, "port"); 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"); root.printTo(configFile); configFile.close(); return true; } } 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 handleUpdate1() { server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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("sensors") || server.hasArg("decimate") || server.hasArg("calibrate") || server.hasArg("raw") || server.hasArg("ahrs") || server.hasArg("quaternion") || server.hasArg("temperature") || server.hasArg("destination") || server.hasArg("port")) { // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it N_KEYVAL_TO_CONFIG(sensors, "sensors"); N_KEYVAL_TO_CONFIG(decimate, "decimate"); N_KEYVAL_TO_CONFIG(calibrate, "calibrate"); N_KEYVAL_TO_CONFIG(raw, "raw"); N_KEYVAL_TO_CONFIG(ahrs, "ahrs"); N_KEYVAL_TO_CONFIG(quaternion, "quaternion"); N_KEYVAL_TO_CONFIG(temperature, "temperature"); S_KEYVAL_TO_CONFIG(destination, "destination"); N_KEYVAL_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else if (server.hasArg("plain")) { // parse the body as JSON object StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg("plain")); if (!root.success()) { handleStaticFile("/reload_failure.html"); return; } N_JSON_TO_CONFIG(sensors, "sensors"); N_JSON_TO_CONFIG(decimate, "decimate"); N_JSON_TO_CONFIG(calibrate, "calibrate"); N_JSON_TO_CONFIG(raw, "raw"); N_JSON_TO_CONFIG(ahrs, "ahrs"); N_JSON_TO_CONFIG(quaternion, "quaternion"); N_JSON_TO_CONFIG(temperature, "temperature"); S_JSON_TO_CONFIG(destination, "destination"); N_JSON_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; // do not save the configuration } saveConfig(); // blink five times, this takes 2 seconds for (int i = 0; i < 5; i++) { ledRed(); delay(200); ledBlack(); delay(200); } // some of the settings require re-initialization ESP.restart(); } void handleFileUpload() { // upload a new file to the SPIFFS File fsUploadFile; // a File object to temporarily store the received file HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { String filename = upload.filename; if (!filename.startsWith("/")) filename = "/" + filename; Serial.print("handleFileUpload Name: "); Serial.println(filename); fsUploadFile = SPIFFS.open(filename, "w"); // Open the file for writing in SPIFFS (create if it doesn't exist) filename = String(); } else if (upload.status == UPLOAD_FILE_WRITE) { if (fsUploadFile) fsUploadFile.write(upload.buf, upload.currentSize); // Write the received bytes to the file } else if (upload.status == UPLOAD_FILE_END) { if (fsUploadFile) { // If the file was successfully created fsUploadFile.close(); // Close the file again Serial.print("handleFileUpload Size: "); Serial.println(upload.totalSize); server.sendHeader("Location", "/success.html"); // Redirect the client to the success page server.send(303); } else { server.send(500, "text/plain", "500: couldn't create file"); } } } ================================================ FILE: esp8266_imu_osc/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ #include #include #include #include #include #include #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 /* 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.set(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 sensors; int decimate; int calibrate; int raw; int ahrs; int quaternion; int temperature; char destination[32]; int port; }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void handleUpdate1(void); void handleUpdate2(void); void handleDirList(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); void handleFileUpload(); // https://tttapa.github.io/ESP8266/Chap12%20-%20Uploading%20to%20Server.html #endif ================================================ FILE: esp8266_inmp411/RunningStat.h ================================================ /* See https://www.johndcook.com/blog/standard_deviation/ */ class RunningStat { public: RunningStat() : m_n(0) {} void Clear() { m_n = 0; } void Push(double x) { m_n++; // See Knuth TAOCP vol 2, 3rd edition, page 232 if (m_n == 1) { m_oldM = m_newM = x; m_oldS = 0.0; } else { m_newM = m_oldM + (x - m_oldM) / m_n; m_newS = m_oldS + (x - m_oldM) * (x - m_newM); // set up for next iteration m_oldM = m_newM; m_oldS = m_newS; } if (m_n == 1) { m_min = x; m_max = x; } else { m_min = (x < m_min ? x : m_min); m_max = (x > m_max ? x : m_max); } } int NumDataValues() const { return m_n; } double Mean() const { return (m_n > 0) ? m_newM : 0.0; } double Variance() const { return ( (m_n > 1) ? m_newS / (m_n - 1) : 0.0 ); } double StandardDeviation() const { return sqrt( Variance() ); } double Min() const { return m_min; } double Max() const { return m_max; } private: int m_n; double m_oldM, m_newM, m_oldS, m_newS, m_min, m_max; }; ================================================ FILE: esp8266_inmp411/esp8266_inmp411.ino ================================================ /* Sketch for ESP8266 board, like the WEMOS D1 mini pro connected to a INMP411 I2S microphone The *internal GPIO number* which is NOT NECESSARIALY the same as the pin numbers, are as follows: I2SI_DATA = GPIO12 IS2I_BCK = GPIO13 I2SI_WS/LRCLK = GPIO14 See https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/I2SInput/I2SInput.ino */ #include #include #include #include "secret.h" #include "RunningStat.h" IPAddress serverAddress(192, 168, 1, 21); const unsigned int serverPort = 4000; const unsigned int recvPort = 4001; WiFiClient Tcp; unsigned long lastBlink = 0; unsigned long lastConnect = 0; unsigned long blinkTime = 250; bool connected = false; const unsigned int sampleRate = 22050; const unsigned int nMessage = 720; float smoothedMean = 0; bool meanInitialized = 0; const float alpha = 10. / sampleRate; // if the sampling time dT is much smaller than the time constant T, then alpha=1/(T*sampleRate) and T=1/(alpha*sampleRate) struct message_t { uint32_t version = 1; uint32_t id = 0; uint32_t counter; uint32_t samples; int16_t data[nMessage]; } message __attribute__((packed)); RunningStat shortstat; void setup() { Serial.begin(115200); // initialize the status LED pinMode(LED_BUILTIN, OUTPUT); lastBlink = millis(); // set A0 LOW, this is connected to the L/R selection pinMode(A0, OUTPUT); digitalWrite(A0, LOW); // set GPIO16 LOW and use that as GND, rather than the GND pin // this allows for a cleaner routing of the wires pinMode(16, OUTPUT); digitalWrite(16, LOW); Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); /* Explicitly set the ESP8266 to be a WiFi-client, otherwise, it by default, would try to act as both a client and an access-point and could cause network-issues with your other WiFi-devices on your WiFi-network. */ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); i2s_rxtx_begin(true, false); // Enable I2S RX i2s_set_rate(sampleRate); // use the last part of the IP address as identifier message.id = WiFi.localIP()[3]; } // setup void loop() { int16_t l, r; if ((millis() - lastBlink) > 2 * blinkTime) { digitalWrite(LED_BUILTIN, LOW); // status LED on lastBlink = millis(); } else if ((millis() - lastBlink) > 1 * blinkTime) { digitalWrite(LED_BUILTIN, HIGH); // status LED off } while (i2s_rx_available() && message.samples < nMessage) { i2s_read_sample(&l, &r, true); float value = l; // compute a smooth running mean with https://en.wikipedia.org/wiki/Exponential_smoothing if (meanInitialized) { smoothedMean = alpha * value + (1 - alpha) * smoothedMean; } else { smoothedMean = value; meanInitialized = 1; } value -= smoothedMean; /* Serial.print(value); Serial.println(); */ message.data[message.samples] = value; // this casts the value back to an int16 message.samples++; shortstat.Push(value); } if (message.samples == nMessage) { Serial.print(shortstat.Min()); Serial.print(", "); Serial.print(shortstat.Mean()); Serial.print(", "); Serial.println(shortstat.Max()); /* Serial.print(message.version); Serial.print(", "); Serial.print(message.counter); Serial.print(", "); Serial.print(message.samples); Serial.print(", "); Serial.print(message.data[0]); Serial.println(); */ if ((millis() - lastConnect) > 10000) { // reconnect, but don't try to reconnect too often lastConnect = millis(); // turn the status LED on digitalWrite(LED_BUILTIN, LOW); lastBlink = millis(); if (!connected) { connected = Tcp.connect(serverAddress, serverPort); if (connected) { Serial.print("Connected to "); Serial.println(serverAddress); } else { Serial.print("Failed to connect to "); Serial.println(serverAddress); } } } if (connected) { blinkTime = 1000; int count = Tcp.write((uint8_t *)(&message), sizeof(message)); connected = (count == sizeof(message)); } else { blinkTime = 250; } message.counter++; message.samples = 0; shortstat.Clear(); } } // loop ================================================ FILE: esp8266_p1_thingspeak/esp8266_p1_thingspeak.ino ================================================ /* This sketch is designed for the P1 port of an ISKRA AM500 smart energy meter. It runs on a Wemos D1 mini PRO that reads and parses the energy usage data and subsequently sends it to http://thingspeak.com/ Pin 5 appears to behave as an https://en.wikipedia.org/wiki/Open_collector and hence requires to be connected over a ~1kOhm resistor to VCC of the ESP8266. The Wemos D1 mini PRO includes a lithium battery charging circuit, which I connected to a small LiPo battery. This allows running it from the low power that the P1 port provides, as the battery takes care of the current spikes during wifi transmission. The battery needed to be fully charged prior to connecting it to the P1 port, otherwise too much current would be drawn and the P1 port would switch off and on repeatedly. See http://domoticx.com/p1-poort-slimme-meter-hardware/ and http://domoticx.com/p1-poort-slimme-meter-hardware/ The schematic below shows how I wired the 6 pins of the RJ12 connector (on the left) to the Wemos D1 mini PRO (on the right). 1---PWR_VCC-------------------5V 2-------RTS-------------------D1 3---DAT_GND-------------------GND 4--------NC- +----------D2 = SoftwareSerial RX 5--------TX--------|---R670---3V3 6---PWR_GND-------------------GND */ #include #include // https://github.com/plerup/espsoftwareserial/ #include // https://github.com/knolleary/pubsubclient #include // https://github.com/matthijskooijman/arduino-dsmr #include "secret.h" /* these are defined in secret.h for the wifi #define SSID "XXXXXXXXXXXXXXXX" #define PASS "XXXXXXXXXXXXXXXX" for thingspeak mqtt #define CLIENTID "XXXXXXXXXXXXXXXX" #define USERNAME "XXXXXXXXXXXXXXXX" #define PASSWORD "XXXXXXXXXXXXXXXX" for thingspeak http #define CHANNEL "XXXXXXXXXXXXXXXX" #define APIKEY "XXXXXXXXXXXXXXXX" */ const char* version = __DATE__ " / " __TIME__; const int8_t rxPin = D2; const int8_t txPin = -1; const int8_t requestPin = D1; #define BUFSIZE 1024 // one of these options is to be selected: LOCAL is a local mqtt broker, MQTT is thingspeak mqtt, REST is thingspeak http // #define USE_LOCAL #define USE_MQTT // #define USE_REST SoftwareSerial MySerial; P1Reader reader(&MySerial, requestPin); WiFiClient wifi_client; PubSubClient mqtt_client(wifi_client); #if defined USE_LOCAL const char* mqtt_server = "192.168.1.16"; const int mqtt_port = 1883; #elif defined USE_MQTT const char* mqtt_server = "mqtt3.thingspeak.com"; const int mqtt_port = 1883; #elif defined USE_REST // this uses the server and port defined below #endif const char* server = "api.thingspeak.com"; const int port = 80; const unsigned long intervalTime = 20 * 1000; // post data every 20 seconds const unsigned long durationLed = 1000; // the status LED remains on for 1 second unsigned long resetCounter = 0, lastLed = 0, lastTime = 0; using MyData = ParsedData < /* String */ identification, /* String */ p1_version, /* String */ timestamp, /* String */ equipment_id, /* FixedValue */ energy_delivered_tariff1, /* FixedValue */ energy_delivered_tariff2, /* FixedValue */ energy_returned_tariff1, /* FixedValue */ energy_returned_tariff2, /* String */ electricity_tariff, /* FixedValue */ power_delivered, /* FixedValue */ power_returned, /* FixedValue */ electricity_threshold, /* uint8_t */ electricity_switch_position, /* uint32_t */ electricity_failures, /* uint32_t */ electricity_long_failures, /* String */ electricity_failure_log, /* uint32_t */ electricity_sags_l1, /* uint32_t */ electricity_sags_l2, /* uint32_t */ electricity_sags_l3, /* uint32_t */ electricity_swells_l1, /* uint32_t */ electricity_swells_l2, /* uint32_t */ electricity_swells_l3, /* String */ message_short, /* String */ message_long, /* FixedValue */ voltage_l1, /* FixedValue */ voltage_l2, /* FixedValue */ voltage_l3, /* FixedValue */ current_l1, /* FixedValue */ current_l2, /* FixedValue */ current_l3, /* FixedValue */ power_delivered_l1, /* FixedValue */ power_delivered_l2, /* FixedValue */ power_delivered_l3, /* FixedValue */ power_returned_l1, /* FixedValue */ power_returned_l2, /* FixedValue */ power_returned_l3, /* uint16_t */ gas_device_type, /* String */ gas_equipment_id, /* uint8_t */ gas_valve_position, /* TimestampedFixedValue */ gas_delivered, /* uint16_t /*/ thermal_device_type, /* String */ thermal_equipment_id, /* uint8_t */ thermal_valve_position, /* TimestampedFixedValue */ thermal_delivered, /* uint16_t */ water_device_type, /* String */ water_equipment_id, /* uint8_t */ water_valve_position, /* TimestampedFixedValue */ water_delivered, /* uint16_t */ slave_device_type, /* String */ slave_equipment_id, /* uint8_t */ slave_valve_position, /* TimestampedFixedValue */ slave_delivered >; MyData *globaldata; /*****************************************************************************/ struct Printer { template void apply(Item &i) { if (i.present()) { Serial.print(Item::name); Serial.print(F(": ")); Serial.print(i.val()); Serial.print(Item::unit()); Serial.println(); } } }; /*****************************************************************************/ void setup() { Serial.begin(115200); MySerial.begin(115200, SWSERIAL_8N1, rxPin, txPin, true, BUFSIZE); pinMode(LED_BUILTIN, OUTPUT); Serial.println(); Serial.print("\n[esp2866_p1_thingspeak / "); Serial.print(version); Serial.println("]"); Serial.println(); Serial.print("Connecting to "); Serial.println(SSID); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASS); delay(500); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); #if defined USE_LOCAL mqtt_client.setServer(mqtt_server, mqtt_port); #elif defined USE_MQTT mqtt_client.setServer(mqtt_server, mqtt_port); #endif //Upon restart, fire off a one-off reading reader.enable(true); lastTime = millis(); } /*****************************************************************************/ void loop () { // this object should be recreated on every call, otherwise the parsing fails MyData localdata; // share a pointer to the object to the sendThingspeak functions globaldata = &localdata; // Allow the reader to check the serial buffer regularly reader.loop(); // Allow the MQTT client to do its things mqtt_client.loop(); // Fire off a one-off reading if there is no message for some time unsigned long now = millis(); if (now - lastTime > intervalTime) { reader.enable(true); lastTime = now; } // Switch the LED off, it was switched on when sending a HTTP or MQTT message if (now - lastLed > durationLed) { digitalWrite(LED_BUILTIN, HIGH); // it is inverted lastLed = now; } if (reader.available()) { // A complete telegram has arrived Serial.println("---raw telegram-------------------------------------------------"); Serial.println(reader.raw()); lastTime = now; String err; if (reader.parse(&localdata, &err)) { // Parse succesful, print result Serial.println("---parse succesful---------------------------------------------"); localdata.applyEach(Printer()); #if defined USE_LOCAL sendLOCALMQTT(); #elif defined USE_MQTT sendThingspeakMQTT(); #elif defined USE_REST sendThingspeakREST(); #endif } else { // Parser error, print error Serial.println("---parse error-------------------------------------------------"); Serial.println(err); } // parse result ok } // full telegram available } // loop /*****************************************************************************/ void sendLOCALMQTT() { while (!mqtt_client.connected()) { Serial.print("Reconnecting to MQTT..."); if (mqtt_client.connect(CLIENTID)) { Serial.println(" done!"); } else { Serial.println(" failed."); resetCounter++; if (resetCounter >= 5 ) ESP.restart(); delay(5000); } } if (mqtt_client.connected()) { digitalWrite(LED_BUILTIN, LOW); // it is inverted lastLed = millis(); mqtt_client.publish ("p1/power_delivered", String(globaldata->power_delivered).c_str()); mqtt_client.publish ("p1/power_returned", String(globaldata->power_returned).c_str()); mqtt_client.publish ("p1/gas_delivered", String(globaldata->gas_delivered).c_str()); mqtt_client.publish ("p1/energy_delivered_tariff1", String(globaldata->energy_delivered_tariff1).c_str()); mqtt_client.publish ("p1/energy_delivered_tariff2", String(globaldata->energy_delivered_tariff2).c_str()); mqtt_client.publish ("p1/energy_returned_tariff1", String(globaldata->energy_returned_tariff1).c_str()); mqtt_client.publish ("p1/energy_returned_tariff2", String(globaldata->energy_returned_tariff2).c_str()); } } // sendLOCALMQTT /*****************************************************************************/ void sendThingspeakMQTT() { while (!mqtt_client.connected()) { Serial.print("Reconnecting to MQTT..."); if (mqtt_client.connect(CLIENTID, USERNAME, PASSWORD)) { Serial.println(" done!"); } else { Serial.println(" failed."); resetCounter++; if (resetCounter >= 5 ) ESP.restart(); delay(5000); } } if (mqtt_client.connected()) { digitalWrite(LED_BUILTIN, LOW); // it is inverted lastLed = millis(); // Construct MQTT message String body = "field1="; body += String(globaldata->power_delivered); body += String("&field2="); body += String(globaldata->power_returned); body += String("&field3="); body += String(globaldata->gas_delivered); body += String("&field4="); body += String(globaldata->energy_delivered_tariff1); body += String("&field5="); body += String(globaldata->energy_delivered_tariff2); body += String("&field6="); body += String(globaldata->energy_returned_tariff1); body += String("&field7="); body += String(globaldata->energy_returned_tariff2); Serial.println("---mqtt message------------------------------------------------"); Serial.println(body); if (mqtt_client.publish("channels/" CHANNEL "/publish", body.c_str())) resetCounter = 0; } } // sendThingspeakMQTT /*****************************************************************************/ void sendThingspeakREST() { if (wifi_client.connect(server, port)) { digitalWrite(LED_BUILTIN, LOW); // it is inverted lastLed = millis(); // Construct request body String body = "field1="; body += String(globaldata->power_delivered); body += String("&field2="); body += String(globaldata->power_returned); body += String("&field3="); body += String(globaldata->gas_delivered); body += String("&field4="); body += String(globaldata->energy_delivered_tariff1); body += String("&field5="); body += String(globaldata->energy_delivered_tariff2); body += String("&field6="); body += String(globaldata->energy_returned_tariff1); body += String("&field7="); body += String(globaldata->energy_returned_tariff2); Serial.println("---post url----------------------------------------------------"); Serial.println(body); // Send message to thingspeak wifi_client.print("POST /update HTTP/1.1\n"); wifi_client.print("Host: " + String(server) + "\n"); wifi_client.print("Connection: close\n"); wifi_client.print("X-THINGSPEAKAPIKEY: " + String(APIKEY) + "\n"); wifi_client.print("Content-Type: application/x-www-form-urlencoded\n"); wifi_client.print("Content-Length: " + String(body.length()) + "\n"); wifi_client.print("\n"); wifi_client.print(body); wifi_client.print("\n"); Serial.println("---server response---------------------------------------------"); while (wifi_client.available()) { char c = wifi_client.read(); Serial.print(c); } wifi_client.stop(); resetCounter = 0; } else { Serial.println("REST connection Failed."); Serial.println(); resetCounter++; if (resetCounter >= 5 ) { ESP.restart(); } } } // sendThingspeakREST ================================================ FILE: esp8266_polar_wearlink/README.md ================================================ # Overview This is designed to link https://www.adafruit.com/product/1077 to http://www.eegsynth.org ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## Arduino ESP8266 filesystem uploader This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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: esp8266_polar_wearlink/data/config.json ================================================ { "redis": "192.168.1.34", "port": 6379, "duration": 100 } ================================================ FILE: esp8266_polar_wearlink/data/hello.html ================================================ Hello

Hello kangaroo!

================================================ FILE: esp8266_polar_wearlink/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_polar_wearlink/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
Heart rate:
?
================================================ FILE: esp8266_polar_wearlink/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_polar_wearlink/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_polar_wearlink/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_polar_wearlink/data/style.css ================================================ .c{ text-align: center; } div,input{ padding:5px;font-size:1em; } input{ width:85%; } 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: esp8266_polar_wearlink/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_polar_wearlink/esp8266_polar_wearlink.ino ================================================ /* This sketch is for an ESP8266 connected to the Polar WearLink+ receiver that comes with the Adafruit Polar T34 starter pack https://www.adafruit.com/product/1077. It uses WIFiManager for initial configuration and includes a web-interface that allows to monitor and change parameters. The status of the wifi connection, http server and received data is indicated by an RDB led that blinks Red, Green or Blue. */ #include #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include #include // https://github.com/remicaumette/esp8266-redis #include "webinterface.h" #include "rgb_led.h" // this allows some sections of the code to be disabled for debugging purposes #define ENABLE_WEBINTERFACE #define ENABLE_MDNS #define ENABLE_REDIS #define ENABLE_INTERRUPT // pointer to object is needed because the initialization is delayed Redis *redis_p = NULL; // this port and address are not used, they are taken from the config instead #define REDIS_ADDR "127.0.0.1" #define REDIS_PORT 6379 #define REDIS_PASSWORD "" ESP8266WebServer server(80); const char* host = "POLAR-WEARLINK"; const char* version = __DATE__ " / " __TIME__; // keep track of the timing of the web interface long tic_web = 0; long tic_redis = 0; long tic_heartbeat = 0; bool pulse = 0; float bpm = 0; #define INTERRUPT_PIN 13 // GPIO13 maps to pin D7 #define INTERRUPT_DEBOUNCE 200 // milliseconds volatile byte interruptCounter = 0; /************************************************************************************************/ void interruptHandler() { // prevent roll-around if (interruptCounter < 255) interruptCounter++; } /************************************************************************************************/ void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.print("\n[esp8266_polar_wearlink / "); Serial.print(__DATE__ " / " __TIME__); Serial.println("]"); WiFi.hostname(host); WiFi.begin(); SPIFFS.begin(); ledInit(); #ifdef ENABLE_INTERRUPT pinMode(INTERRUPT_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interruptHandler, RISING); #endif if (loadConfig()) { ledYellow(); delay(1000); } else { ledRed(); delay(1000); } if (WiFi.status() != WL_CONNECTED) ledRed(); 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"); if (WiFi.status() == WL_CONNECTED) ledGreen(); #ifdef ENABLE_WEBINTERFACE // this serves all URIs that cannot be resolved to a file on the SPIFFS filesystem server.onNotFound(handleNotFound); server.on("/", HTTP_GET, []() { tic_web = millis(); handleRedirect("/index.html"); }); server.on("/defaults", HTTP_GET, []() { tic_web = millis(); Serial.println("handleDefaults"); handleStaticFile("/reload_success.html"); defaultConfig(); saveConfig(); server.close(); server.stop(); ESP.restart(); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); handleStaticFile("/reload_success.html"); ledRed(); 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); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) ledGreen(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); ledRed(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, [] { tic_web = millis(); handleDirList(); }); server.on("/json", HTTP_PUT, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_POST, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_GET, [] { tic_web = millis(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(redis, "redis"); N_CONFIG_TO_JSON(port, "port"); N_CONFIG_TO_JSON(duration, "duration"); root["heartrate"] = bpm; root["version"] = version; root["uptime"] = long(millis() / 1000); String str; root.printTo(str); server.setContentLength(str.length()); server.send(200, "application/json", str); }); server.on("/update", HTTP_GET, [] { tic_web = millis(); handleStaticFile("/update.html"); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // start the web server server.begin(); #endif #ifdef ENABLE_MDNS // announce the hostname and web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); #endif #ifdef ENABLE_REDIS // pointer to object is needed because the initialization is delayed Serial.print("redis = "); Serial.println(config.redis); Serial.print("port = "); Serial.println(config.port); Serial.print("duration = "); Serial.println(config.duration); Redis redis(config.redis, config.port); redis.setNoDelay(false); redis.setTimeout(100); if (redis.begin(REDIS_PASSWORD)) { Serial.println("Connected to the Redis server!"); redis_p = &redis; } else { Serial.println("Failed to connect to the Redis server!"); redis_p = NULL; } #endif Serial.println("===================================================="); Serial.println("Setup done"); Serial.println("===================================================="); while (true) { // somehow the redis connection fails when control moves from setup() to loop() // hence remain in the setup() function loop(); } } /************************************************************************************************/ void loop() { #ifdef ENABLE_WEBINTERFACE server.handleClient(); #endif #ifdef ENABLE_REDIS if ((millis() - tic_redis) > 5000) { tic_redis = millis(); String str(tic_redis); char buf[32]; str.toCharArray(buf, 32); if (redis_p != NULL) redis_p->set("polar.millis", buf); } #endif #ifdef ENABLE_INTERRUPT if (interruptCounter) { if ((millis() - tic_heartbeat) > INTERRUPT_DEBOUNCE) { long now = millis(); ledMagenta(); // skip the first beat, since the BPM cannot be computed if (tic_heartbeat > 0) { bpm = 60000. / (now - tic_heartbeat); String str(bpm); char buf[32]; str.toCharArray(buf, 32); if (redis_p != NULL) { redis_p->publish("polar.heartbeat", buf); redis_p->set("polar.heartrate", buf); redis_p->set("polar.heartbeat", "1"); pulse = 1; } } Serial.print("Heartbeat, BPM = "); Serial.println(bpm); tic_heartbeat = now; } // if debounce interruptCounter = 0; } // if interruptCounter if (pulse && ((millis() - tic_heartbeat) > config.duration)) { pulse = 0; if (redis_p != NULL) { redis_p->set("polar.heartbeat", "0"); } } if ((millis() - tic_heartbeat) > 100) { // switch the LED back to the normal status color if ((millis() - tic_web) < 5000) ledBlue(); else if ((WiFi.status() == WL_CONNECTED) && (redis_p != NULL)) ledGreen(); else if ((WiFi.status() == WL_CONNECTED) && (redis_p == NULL)) ledCyan(); else ledRed(); } #endif } ================================================ FILE: esp8266_polar_wearlink/rgb_led.cpp ================================================ #include "rgb_led.h" void ledInit() { pinMode(LED_R, OUTPUT); pinMode(LED_G, OUTPUT); pinMode(LED_B, OUTPUT); } #ifdef COMMON_ANODE void ledRed() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, HIGH); } void ledGreen() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, LOW); digitalWrite(LED_B, HIGH); } void ledBlue() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, LOW); } void ledYellow() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, LOW); digitalWrite(LED_B, HIGH); } void ledMagenta() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, LOW); } void ledCyan() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, LOW); digitalWrite(LED_B, LOW); } void ledBlack() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, HIGH); } void ledWhite() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, LOW); digitalWrite(LED_B, LOW); } #else void ledRed() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, LOW); digitalWrite(LED_B, LOW); } void ledGreen() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, LOW); } void ledBlue() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, LOW); digitalWrite(LED_B, HIGH); } void ledYellow() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, LOW); } void ledMagenta() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, LOW); digitalWrite(LED_B, HIGH); } void ledCyan() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, HIGH); } void ledBlack() { digitalWrite(LED_R, LOW); digitalWrite(LED_G, LOW); digitalWrite(LED_B, LOW); } void ledWhite() { digitalWrite(LED_R, HIGH); digitalWrite(LED_G, HIGH); digitalWrite(LED_B, HIGH); } #endif ================================================ FILE: esp8266_polar_wearlink/rgb_led.h ================================================ #ifndef _RGB_LED_H_ #define _RGB_LED_H_ #include #define LED_R D1 #define LED_G D2 #define LED_B D3 // #define COMMON_ANODE void ledInit(); void ledRed(); void ledGreen(); void ledBlue(); void ledYellow(); void ledMagenta(); void ledCyan(); void ledBlack(); void ledWhite(); #endif ================================================ FILE: esp8266_polar_wearlink/webinterface.cpp ================================================ #include "webinterface.h" #include "rgb_led.h" Config config; extern ESP8266WebServer server; extern unsigned long packetCounter; /***************************************************************************/ 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("loadConfig"); strncpy(config.redis, "192.168.1.34", 32); config.port = 6379; config.duration = 100; 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 buf(new char[size]); configFile.readBytes(buf.get(), size); configFile.close(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(buf.get()); if (!root.success()) { Serial.println("Failed to parse config file"); return false; } S_JSON_TO_CONFIG(redis, "redis"); N_JSON_TO_CONFIG(port, "port"); N_JSON_TO_CONFIG(duration, "duration"); return true; } bool saveConfig() { Serial.println("saveConfig"); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(redis, "redis"); N_CONFIG_TO_JSON(port, "port"); N_CONFIG_TO_JSON(duration, "duration"); 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"); root.printTo(configFile); configFile.close(); return true; } } 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 handleUpdate1() { server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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("redis") || server.hasArg("port") || server.hasArg("duration")) { // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it S_KEYVAL_TO_CONFIG(redis, "redis"); N_KEYVAL_TO_CONFIG(port, "port"); N_KEYVAL_TO_CONFIG(duration, "duration"); handleStaticFile("/reload_success.html"); } else if (server.hasArg("plain")) { // parse the body as JSON object StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg("plain")); if (!root.success()) { handleStaticFile("/reload_failure.html"); return; } S_JSON_TO_CONFIG(redis, "redis"); N_JSON_TO_CONFIG(port, "port"); N_JSON_TO_CONFIG(duration, "duration"); handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; // do not save the configuration } saveConfig(); // blink five times for (int i = 0; i < 5; i++) { ledRed(); delay(200); ledBlack(); delay(200); } // some of the settings require re-initialization ESP.restart(); } ================================================ FILE: esp8266_polar_wearlink/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ #include #include #include #include #include #include #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 /* 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.set(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 { char redis[32]; int port; int duration; // in milliseconds }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void handleUpdate1(void); void handleUpdate2(void); void handleDirList(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif ================================================ FILE: esp8266_pulse_sensor/README.md ================================================ # Overview This is an Arduino sketch for an ESP8266 module connected to a [PulseSensor](http://pulsesensor.com) heart beat sensor. It uses the ADC to sample the data and writes it as a continuous stream to a computer that is running a FieldTrip buffer. You can use the webinterface to set the IP address and port of the receiving computer. ## SPIFFS for static files You should not only write the firmware to the ESP8266 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 ESP8266. See for example http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html and https://www.instructables.com/id/Using-ESP8266-SPIFFS for instructions. You will get a "file not found" error if the firmware cannot access the data files. ## Arduino ESP8266 filesystem uploader This Arduino sketch includes a `data` directory with a number of files that should be uploaded to the ESP8266 using the [SPIFFS filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-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: esp8266_pulse_sensor/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: esp8266_pulse_sensor/blink_led.h ================================================ #ifndef _BLINK_LED_H_ #define _BLINK_LED_H_ #include #include #define LED 2 // GPIO2 is connected to the builtin LED void ledInit(void); void ledOn(void); void ledOff(void); void ledSlow(void); void ledMedium(void); void ledFast(void); #endif ================================================ FILE: esp8266_pulse_sensor/data/config.json ================================================ { "address": "192.168.1.34", "port": 1972 } ================================================ FILE: esp8266_pulse_sensor/data/hello.html ================================================ Hello

Hello capibara!

================================================ FILE: esp8266_pulse_sensor/data/index.html ================================================ Index Hello
Monitor
Change settings
Reconnect WiFi
Default settings
Update firmware
Restart hardware
================================================ FILE: esp8266_pulse_sensor/data/monitor.html ================================================ Monitor

Monitor

Firmware version:
?
Uptime:
?
================================================ FILE: esp8266_pulse_sensor/data/reload_failure.html ================================================ Failure

Failure

================================================ FILE: esp8266_pulse_sensor/data/reload_success.html ================================================ Success

Success

================================================ FILE: esp8266_pulse_sensor/data/settings.html ================================================ Settings

Settings

================================================ FILE: esp8266_pulse_sensor/data/style.css ================================================ .c{ text-align: center; } div,input{ padding:5px;font-size:1em; } input{ width:85%; } 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: esp8266_pulse_sensor/data/update.html ================================================ Update

Update the ESP8266 firmware

================================================ FILE: esp8266_pulse_sensor/esp8266_pulse_sensor.ino ================================================ /* This sketch is for an ESP8266 connected to an optical Pulse Sensor like this https://pulsesensor.com. It sends the continuous signal with the Fieldtrip buffer protocol to a server. It uses WIFiManager for initial configuration and includes a web-interface that allows to monitor and change parameters. The status of the wifi connection, http server and received data is indicated by the builtin LED. */ #include #include // https://github.com/esp8266/Arduino #include #include #include // https://github.com/tzapu/WiFiManager #include #include "webinterface.h" #include "blink_led.h" #include "fieldtrip_buffer.h" // this allows some sections of the code to be disabled for debugging purposes #define ENABLE_WEBINTERFACE #define ENABLE_MDNS #define ENABLE_BUFFER const char* host = "PULSE-SENSOR"; const char* version = __DATE__ " / " __TIME__; ESP8266WebServer server(80); Ticker sampler; #define ADC A0 #define NCHANS 1 #define FSAMPLE 200 #define BLOCKSIZE (FSAMPLE/10) long tic_web = 0; int sample = 0, block = 0; bool flush0 = false, flush1 = false; uint16_t block0[BLOCKSIZE], block1[BLOCKSIZE]; int ftserver = 0, status = 0; /************************************************************************************************/ void getSample() { if (sample == BLOCKSIZE) { // switch to the start of the other block switch (block) { case 0: sample = 0; flush0 = true; block = 1; break; case 1: sample = 0; flush1 = true; block = 0; break; } } // get the current ECG value, it is from a 10-bits ADC, value between 0 and 1023 uint16_t value = analogRead(ADC); // store it in the active block switch (block) { case 0: block0[sample++] = value; break; case 1: block1[sample++] = value; break; } } /************************************************************************************************/ void setup() { Serial.begin(115200); while (!Serial) { ; } Serial.print("\n[esp8266_pulse_sensor / "); Serial.print(__DATE__ " / " __TIME__); Serial.println("]"); ledInit(); WiFi.hostname(host); WiFi.begin(); SPIFFS.begin(); if (loadConfig()) { ledSlow(); delay(1000); } else { ledFast(); delay(1000); } Serial.println(config.address); Serial.println(config.port); if (WiFi.status() != WL_CONNECTED) ledFast(); 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"); if (WiFi.status() == WL_CONNECTED) ledSlow(); #ifdef ENABLE_WEBINTERFACE // this serves all URIs that cannot be resolved to a file on the SPIFFS filesystem server.onNotFound(handleNotFound); server.on("/", HTTP_GET, []() { tic_web = millis(); handleRedirect("/index.html"); }); server.on("/defaults", HTTP_GET, []() { tic_web = millis(); Serial.println("handleDefaults"); handleStaticFile("/reload_success.html"); defaultConfig(); saveConfig(); server.close(); server.stop(); ESP.restart(); }); server.on("/reconnect", HTTP_GET, []() { tic_web = millis(); Serial.println("handleReconnect"); handleStaticFile("/reload_success.html"); ledFast(); 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); Serial.println("connected"); server.begin(); if (WiFi.status() == WL_CONNECTED) ledSlow(); }); server.on("/restart", HTTP_GET, []() { tic_web = millis(); Serial.println("handleRestart"); handleStaticFile("/reload_success.html"); ledFast(); server.close(); server.stop(); SPIFFS.end(); delay(5000); ESP.restart(); }); server.on("/dir", HTTP_GET, [] { tic_web = millis(); handleDirList(); }); server.on("/json", HTTP_PUT, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_POST, [] { tic_web = millis(); handleJSON(); }); server.on("/json", HTTP_GET, [] { tic_web = millis(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(address, "address"); N_CONFIG_TO_JSON(port, "port"); root["version"] = version; root["uptime"] = long(millis() / 1000); String str; root.printTo(str); server.setContentLength(str.length()); server.send(200, "application/json", str); }); server.on("/update", HTTP_GET, [] { tic_web = millis(); handleStaticFile("/update.html"); }); server.on("/update", HTTP_POST, handleUpdate1, handleUpdate2); // start the web server server.begin(); #endif #ifdef ENABLE_MDNS // announce the hostname and web server through zeroconf MDNS.begin(host); MDNS.addService("http", "tcp", 80); #endif #ifdef ENABLE_BUFFER Serial.print("Connecting to "); Serial.print(config.address); Serial.print(" on port "); Serial.println(config.port); ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) { Serial.println("Connection opened"); status = fieldtrip_write_header(ftserver, DATATYPE_UINT16, NCHANS, FSAMPLE); if (status == 0) Serial.println("Wrote header"); else Serial.println("Failed writing header"); } else { Serial.println("Failed opening connection"); } #endif // start sampling sampler.attach_ms(1000 / FSAMPLE, getSample); Serial.println("===================================================="); Serial.println("Setup done"); Serial.println("===================================================="); return; } // setup /************************************************************************************************/ void loop() { #ifdef ENABLE_WEBINTERFACE server.handleClient(); #endif #ifdef ENABLE_BUFFER byte *ptr = NULL; if (flush0) { ptr = (byte *)block0; flush0 = false; } else if (flush1) { ptr = (byte *)block1; flush1 = false; } if (ptr) { if (ftserver == 0) { ftserver = fieldtrip_open_connection(config.address, config.port); if (ftserver > 0) Serial.println("Connection opened"); else Serial.println("Failed opening connection"); } if (ftserver > 0) { status = fieldtrip_write_data(ftserver, DATATYPE_UINT16, NCHANS, BLOCKSIZE, ptr); if (status == 0) Serial.println("Wrote data"); else { Serial.println("Failed writing data"); status = fieldtrip_close_connection(ftserver); if (status == 0) Serial.println("Connection closed"); else Serial.println("Failed closing connection"); ftserver = 0; } } } #endif delay(10); return; } // loop ================================================ FILE: esp8266_pulse_sensor/fieldtrip_buffer.cpp ================================================ #include "fieldtrip_buffer.h" WiFiClient client; #undef DEBUG /******************************************************************************* OPEN CONNECTION returns file descriptor that should be >0 on success *******************************************************************************/ int fieldtrip_open_connection(const char *address, int port) { #ifdef DEBUG Serial.println("fieldtrip_open_connection"); #endif if (client.connect(address, port)) return 1; else return -1; }; /******************************************************************************* CLOSE CONNECTION returns 0 on success *******************************************************************************/ int fieldtrip_close_connection(int s) { #ifdef DEBUG Serial.println("fieldtrip_close_connection"); #endif client.stop(); return 0; }; /******************************************************************************* WRITE HEADER returns 0 on success *******************************************************************************/ int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample) { int status; messagedef_t *request = NULL; messagedef_t *response = NULL; headerdef_t *header = NULL; #ifdef DEBUG Serial.println("fieldtrip_write_header"); #endif byte msg[8+24]; for (int i = 0; i < 8+24; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_HDR; request->bufsize = sizeof(headerdef_t); header = (headerdef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize header->nchans = nchans; header->nsamples = 0; header->nevents = 0; header->fsample = fsample; header->data_type = datatype; header->bufsize = 0; #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(headerdef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(headerdef_t)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n != sizeof(messagedef_t) + sizeof(headerdef_t)) status++; // read and ignore whatever response we get while (client.available()) client.read(); return status; }; /******************************************************************************* WRITE DATA returns 0 on success *******************************************************************************/ int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer) { int status = 0; messagedef_t *request = NULL; messagedef_t *response = NULL; datadef_t *data = NULL; #ifdef DEBUG Serial.println("fieldtrip_write_data"); #endif byte msg[8+16]; for (int i = 0; i < 8+16; i++) msg[i] = 0; request = (messagedef_t *)(msg + 0); request->version = VERSION; request->command = PUT_DAT; request->bufsize = sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype); data = (datadef_t *)(msg + sizeof(messagedef_t)); // the first 8 bytes are version, command and bufsize data->nchans = nchans; data->nsamples = nsamples; data->data_type = datatype; data->bufsize = nchans * nsamples * wordsize_from_type(datatype); #ifdef DEBUG Serial.print("msg = "); for (int i = 0; i < sizeof(messagedef_t) + sizeof(datadef_t); i++) { Serial.print(msg[i]); Serial.print(" "); } Serial.println(); #endif int n = 0; n += client.write(msg, sizeof(messagedef_t) + sizeof(datadef_t)); client.flush(); n += client.write(buffer, nchans * nsamples * wordsize_from_type(datatype)); client.flush(); #ifdef DEBUG Serial.print("Wrote "); Serial.print(n); Serial.println(" bytes"); #endif if (n != sizeof(messagedef_t) + sizeof(datadef_t) + nchans * nsamples * wordsize_from_type(datatype)) status++; // read and ignore whatever response we get while (client.available()) client.read(); return status; }; int wordsize_from_type(uint32_t datatype) { int wordsize = 0; switch (datatype) { case DATATYPE_CHAR: wordsize = 1; break; case DATATYPE_UINT8: wordsize = 1; break; case DATATYPE_UINT16: wordsize = 2; break; case DATATYPE_UINT32: wordsize = 4; break; case DATATYPE_UINT64: wordsize = 8; break; case DATATYPE_INT8: wordsize = 1; break; case DATATYPE_INT16: wordsize = 2; break; case DATATYPE_INT32: wordsize = 4; break; case DATATYPE_INT64: wordsize = 8; break; case DATATYPE_FLOAT32: wordsize = 4; break; case DATATYPE_FLOAT64: wordsize = 8; break; } return wordsize; }; ================================================ FILE: esp8266_pulse_sensor/fieldtrip_buffer.h ================================================ #ifndef __BUFFER_H_ #define __BUFFER_H_ #include // definition of simplified interface functions, not all of them are implemented int fieldtrip_start_server(int port); int fieldtrip_open_connection(const char *hostname, int port); int fieldtrip_close_connection(int s); int fieldtrip_read_header(int server, uint32_t *datatype, uint32_t *nchans, float *fsample, uint32_t *nsamples, uint32_t *nevents); int fieldtrip_read_data(int server, uint32_t begsample, uint32_t endsample, byte *buffer); int fieldtrip_write_header(int server, uint32_t datatype, uint32_t nchans, float fsample); int fieldtrip_write_data(int server, uint32_t datatype, uint32_t nchans, uint32_t nsamples, byte *buffer); int fieldtrip_wait_data(int server, uint32_t nsamples, uint32_t nevents, uint32_t milliseconds); int wordsize_from_type(uint32_t datatype); // define the version of the message packet #define VERSION (uint16_t)0x0001 // these define the commands that can be used, which are split over the two available bytes #define PUT_HDR (uint16_t)0x0101 /* decimal 257 */ #define PUT_DAT (uint16_t)0x0102 /* decimal 258 */ #define PUT_EVT (uint16_t)0x0103 /* decimal 259 */ #define PUT_OK (uint16_t)0x0104 /* decimal 260 */ #define PUT_ERR (uint16_t)0x0105 /* decimal 261 */ #define GET_HDR (uint16_t)0x0201 /* decimal 513 */ #define GET_DAT (uint16_t)0x0202 /* decimal 514 */ #define GET_EVT (uint16_t)0x0203 /* decimal 515 */ #define GET_OK (uint16_t)0x0204 /* decimal 516 */ #define GET_ERR (uint16_t)0x0205 /* decimal 517 */ #define FLUSH_HDR (uint16_t)0x0301 /* decimal 769 */ #define FLUSH_DAT (uint16_t)0x0302 /* decimal 770 */ #define FLUSH_EVT (uint16_t)0x0303 /* decimal 771 */ #define FLUSH_OK (uint16_t)0x0304 /* decimal 772 */ #define FLUSH_ERR (uint16_t)0x0305 /* decimal 773 */ #define WAIT_DAT (uint16_t)0x0402 /* decimal 1026 */ #define WAIT_OK (uint16_t)0x0404 /* decimal 1027 */ #define WAIT_ERR (uint16_t)0x0405 /* decimal 1028 */ #define PUT_HDR_NORESPONSE (uint16_t)0x0501 /* decimal 1281 */ #define PUT_DAT_NORESPONSE (uint16_t)0x0502 /* decimal 1282 */ #define PUT_EVT_NORESPONSE (uint16_t)0x0503 /* decimal 1283 */ // these are used in the data_t and event_t structure #define DATATYPE_CHAR (uint32_t)0 #define DATATYPE_UINT8 (uint32_t)1 #define DATATYPE_UINT16 (uint32_t)2 #define DATATYPE_UINT32 (uint32_t)3 #define DATATYPE_UINT64 (uint32_t)4 #define DATATYPE_INT8 (uint32_t)5 #define DATATYPE_INT16 (uint32_t)6 #define DATATYPE_INT32 (uint32_t)7 #define DATATYPE_INT64 (uint32_t)8 #define DATATYPE_FLOAT32 (uint32_t)9 #define DATATYPE_FLOAT64 (uint32_t)10 // a packet that is sent over the network should contain the following typedef struct { uint16_t version; // see VERSION uint16_t command; // see PUT_xxx, GET_xxx and FLUSH_xxx uint32_t bufsize; // size of the buffer in bytes } messagedef_t; // 8 bytes // the header definition is fixed, it can be followed by additional header chunks typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t nevents; float fsample; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } headerdef_t; // 24 bytes // the data definition is fixed, it should be followed by additional data typedef struct { uint32_t nchans; uint32_t nsamples; uint32_t data_type; uint32_t bufsize; // size of the buffer in bytes } datadef_t; // 16 bytes // the event definition is fixed, it should be followed by additional data typedef struct { uint32_t type_type; /* usual would be DATATYPE_CHAR */ uint32_t type_numel; /* length of the type string */ uint32_t value_type; uint32_t value_numel; int32_t sample; int32_t offset; int32_t duration; uint32_t bufsize; /* size of the buffer in bytes */ } eventdef_t; // 64 bytes #endif _BUFFER_H_ ================================================ FILE: esp8266_pulse_sensor/webinterface.cpp ================================================ #include "webinterface.h" #include "blink_led.h" Config config; extern ESP8266WebServer 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"); strncpy(config.address, "192.168.1.34", 32); config.port = 1972; 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 buf(new char[size]); configFile.readBytes(buf.get(), size); configFile.close(); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(buf.get()); if (!root.success()) { Serial.println("Failed to parse config file"); return false; } S_JSON_TO_CONFIG(address, "address"); N_JSON_TO_CONFIG(port, "port"); return true; } bool saveConfig() { Serial.println("saveConfig"); StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); S_CONFIG_TO_JSON(address, "address"); N_CONFIG_TO_JSON(port, "port"); 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"); root.printTo(configFile); configFile.close(); return true; } } 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 handleUpdate1() { server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); } void handleUpdate2() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); } void handleDirList() { Serial.println("handleDirList"); String str = ""; Dir dir = SPIFFS.openDir("/"); while (dir.next()) { str += dir.fileName(); str += " "; str += dir.fileSize(); str += " bytes\r\n"; } server.send(200, "text/plain", str); } 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("address") || server.hasArg("port")) { // the body is key1=val1&key2=val2&key3=val3 and the ESP8266Webserver has already parsed it S_KEYVAL_TO_CONFIG(address, "address"); N_KEYVAL_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else if (server.hasArg("plain")) { // parse the body as JSON object StaticJsonBuffer<300> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg("plain")); if (!root.success()) { handleStaticFile("/reload_failure.html"); return; } S_JSON_TO_CONFIG(address, "address"); N_JSON_TO_CONFIG(port, "port"); handleStaticFile("/reload_success.html"); } else { handleStaticFile("/reload_failure.html"); return; // do not save the configuration } saveConfig(); // blink five times for (int i = 0; i < 5; i++) { ledOn(); delay(200); ledOff(); delay(200); } // some of the settings require re-initialization ESP.restart(); } ================================================ FILE: esp8266_pulse_sensor/webinterface.h ================================================ #ifndef _WEBINTERFACE_H_ #define _WEBINTERFACE_H_ #include #include #include #include #include #include #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 /* 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.set(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 { char address[32]; int port; }; extern Config config; bool defaultConfig(void); bool loadConfig(void); bool saveConfig(void); void handleUpdate1(void); void handleUpdate2(void); void handleDirList(void); void handleNotFound(void); void handleRedirect(String); void handleRedirect(const char *); bool handleStaticFile(String); bool handleStaticFile(const char *); void handleJSON(); #endif ================================================ FILE: esp8266_thingspeak/esp8266_thingspeak.ino ================================================ #include #include #include "secret.h" /* these are defined in secret.h const char* SSID = "XXXXXXXXXXXXXXXX"; const char* PASS = "XXXXXXXXXXXXXXXX"; const char* APIKEY = "XXXXXXXXXXXXXXXX"; // Write API Key for my ThingSpeak Channel */ const char* version = __DATE__ " / " __TIME__; WiFiClient client; const char* server = "api.thingspeak.com"; const int postingInterval = 20 * 1000; // post data every 20 seconds unsigned int resetCounter = 0; /*****************************************************************************/ void setup () { Serial.begin(115200); Serial.print("\n[esp2866_thingspeak / "); Serial.print(version); Serial.println("]"); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASS); Serial.println(""); // Wait for connection int count = 0; while (WiFi.status() != WL_CONNECTED) { delay(1000); if (count++ > 20) { Serial.println("No WiFi connection, restart..."); ESP.restart(); } else Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(SSID); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } // setup /*****************************************************************************/ void loop() { if (client.connect(server, 80)) { // Measure Signal Strength (RSSI) of Wi-Fi connection long rssi = WiFi.RSSI(); Serial.print("RSSI: "); Serial.println(rssi); // Construct API request body String body = "field1="; body += String(rssi); client.print("POST /update HTTP/1.1\n"); client.print("Host: " + String(server) + "\n"); client.print("Connection: close\n"); client.print("X-THINGSPEAKAPIKEY: " + String(APIKEY) + "\n"); client.print("Content-Type: application/x-www-form-urlencoded\n"); client.print("Content-Length: " + String(body.length()) + "\n"); client.print("\n"); client.print(body); client.print("\n"); Serial.println("------------------------------------------------------------"); while (client.available()) { char c = client.read(); Serial.print(c); } Serial.println("------------------------------------------------------------"); client.stop(); // wait and then post again delay(postingInterval); resetCounter = 0; } else { Serial.println("Connection Failed."); Serial.println(); resetCounter++; if (resetCounter >= 5 ) { ESP.restart(); } } } // loop ================================================ FILE: m5dial_midi/colormap.h ================================================ #ifndef _COLORMAP_H_ #define _COLORMAP_H_ // This is the colorcet "r2" or "rainbow2" colormap, which goes from blue via green and orange to to red. // https://cmap-docs.readthedocs.io/en/latest/catalog/miscellaneous/colorcet:cet_r2/ uint8_t r[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 29, 36, 41, 46, 49, 52, 55, 57, 59, 60, 61, 62, 63, 63, 63, 63, 63, 63, 63, 65, 67, 70, 75, 80, 86, 91, 97, 102, 107, 112, 117, 122, 127, 132, 137, 141, 146, 151, 155, 160, 164, 169, 173, 178, 182, 186, 191, 195, 199, 204, 208, 212, 217, 221, 225, 229, 234, 238, 241, 244, 247, 249, 250, 251, 252, 253, 253, 253, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 254, 253, 253, 253, 253}; uint8_t g[128] = {52, 58, 63, 68, 72, 77, 81, 85, 89, 92, 96, 100, 103, 106, 109, 112, 115, 117, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 168, 169, 171, 172, 173, 174, 176, 177, 178, 179, 180, 181, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 205, 205, 205, 204, 202, 200, 198, 195, 193, 190, 187, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 157, 154, 151, 148, 145, 142, 139, 136, 133, 130, 126, 123, 120, 117, 113, 110, 106, 103, 99, 96, 92, 88, 84, 80, 75, 70, 66, 60, 55, 48}; uint8_t b[128] = {245, 240, 234, 228, 223, 217, 212, 206, 200, 195, 189, 184, 178, 173, 167, 162, 157, 152, 147, 142, 137, 132, 128, 123, 118, 113, 108, 103, 98, 93, 88, 82, 77, 71, 65, 59, 52, 46, 39, 32, 26, 21, 17, 15, 15, 15, 16, 17, 18, 19, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 42, 43, 44, 44, 44, 44, 44, 43, 42, 41, 40, 39, 38, 37, 36, 34, 33, 32, 31, 30, 29, 28, 27, 25, 24, 23, 22, 21, 20, 19, 17, 16, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0}; #endif ================================================ FILE: m5dial_midi/m5dial_midi.ino ================================================ #include //#include #include "colormap.h" #define clip(x, lo, hi) (x < lo ? lo : (x > hi ? hi : x)) long chan = 0; const int NCHAN = 16; long value[NCHAN]; long oldPosition; //USBMIDI_Interface midi; enum { CHAN, VALUE } mode = CHAN; void updateMIDI() { // MIDIAddress controller = {chan, Channel_1}; // midi.sendControlChange(controller, value[chan]); } void updateScreen() { String s; if (mode == CHAN) { s = String("c ") + String(chan); } else { s = String("v ") + String(value[chan]); } Serial.println(s); M5Dial.Display.clear(); M5Dial.Display.drawString(s, M5Dial.Display.width() / 2, M5Dial.Display.height() / 2); } void setup() { auto cfg = M5.config(); Control_Surface.begin(); // Initialize the Control Surface M5Dial.begin(cfg, true, false); M5Dial.Display.setTextColor(GREEN); M5Dial.Display.setTextDatum(middle_center); M5Dial.Display.setTextFont(&fonts::Orbitron_Light_32); M5Dial.Display.setTextSize(1); oldPosition = M5Dial.Encoder.read(); for (int chan = 0; chan < NCHAN; chan++) value[chan] = 0; } void loop() { M5Dial.update(); Control_Surface.loop(); // Update the Control Surface if (M5Dial.BtnA.wasPressed()) { if (mode == CHAN) mode = VALUE; else mode = CHAN; updateScreen(); } long newPosition = M5Dial.Encoder.read(); if (newPosition != oldPosition) { if (mode == CHAN) { chan += (newPosition - oldPosition); chan = clip(chan, 0, NCHAN - 1); } else { value[chan] += (newPosition - oldPosition); value[chan] = clip(value[chan], 0, 127); updateMIDI(); } updateScreen(); oldPosition = newPosition; } } ================================================ FILE: m5nanoc6_angle8_midi/colormap.h ================================================ #ifndef _COLORMAP_H_ #define _COLORMAP_H_ // This is the colorcet "r2" or "rainbow2" colormap, which goes from blue via green and orange to red. // https://cmap-docs.readthedocs.io/en/latest/catalog/miscellaneous/colorcet:cet_r2/ uint8_t r[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 29, 36, 41, 46, 49, 52, 55, 57, 59, 60, 61, 62, 63, 63, 63, 63, 63, 63, 63, 65, 67, 70, 75, 80, 86, 91, 97, 102, 107, 112, 117, 122, 127, 132, 137, 141, 146, 151, 155, 160, 164, 169, 173, 178, 182, 186, 191, 195, 199, 204, 208, 212, 217, 221, 225, 229, 234, 238, 241, 244, 247, 249, 250, 251, 252, 253, 253, 253, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 254, 253, 253, 253, 253}; uint8_t g[128] = {52, 58, 63, 68, 72, 77, 81, 85, 89, 92, 96, 100, 103, 106, 109, 112, 115, 117, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 168, 169, 171, 172, 173, 174, 176, 177, 178, 179, 180, 181, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 205, 205, 205, 204, 202, 200, 198, 195, 193, 190, 187, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 157, 154, 151, 148, 145, 142, 139, 136, 133, 130, 126, 123, 120, 117, 113, 110, 106, 103, 99, 96, 92, 88, 84, 80, 75, 70, 66, 60, 55, 48}; uint8_t b[128] = {245, 240, 234, 228, 223, 217, 212, 206, 200, 195, 189, 184, 178, 173, 167, 162, 157, 152, 147, 142, 137, 132, 128, 123, 118, 113, 108, 103, 98, 93, 88, 82, 77, 71, 65, 59, 52, 46, 39, 32, 26, 21, 17, 15, 15, 15, 16, 17, 18, 19, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 42, 43, 44, 44, 44, 44, 44, 43, 42, 41, 40, 39, 38, 37, 36, 34, 33, 32, 31, 30, 29, 28, 27, 25, 24, 23, 22, 21, 20, 19, 17, 16, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0}; #endif ================================================ FILE: m5nanoc6_angle8_midi/m5nanoc6_angle8_midi.ino ================================================ /* This is a sketch for an M5NanoC6 or M5Dial controller connected to an 8Angle input module. It provides MIDI control change (CC) messages over Bluetooth. See https://robertoostenveld.nl/low-cost-8-channel-midi-controller/ */ #include // https://github.com/m5stack/M5Unified #include // https://github.com/m5stack/M5Unit-8Angle #include // https://github.com/tttapa/Control-Surface #include "colormap.h" // #define I2C_SDA G13 // for the M5Dial // #define I2C_SCL G15 #define I2C_SDA 2 // for the M5NanoC6, they are listed as G1 and G2 #define I2C_SCL 1 #define I2C_ADDR 0x43 #define I2C_SPEED 4000000L #define MIDI_DELAY 10 // do not send the MIDI messages too fast/frequent BluetoothMIDI_Interface midi; M5_ANGLE8 angle8; bool sw; uint16_t value[8]; void updateSwitch(bool sw) { uint32_t rgb = 0x0000FF00 * sw; // green or black angle8.setLEDColor(8, rgb, 64); } void updateValue(bool sw, uint8_t knob, uint8_t value) { uint32_t rgb = (r[value] << 16) | (g[value] << 8) | (b[value] << 0); angle8.setLEDColor(knob, rgb, 64); // the switch allows selecting MIDI channel 1 or 2 if (sw == 0) { MIDIAddress controller = { knob, Channel_1 }; midi.sendControlChange(controller, value); } else { MIDIAddress controller = { knob, Channel_2 }; midi.sendControlChange(controller, value); } Serial.print(sw); Serial.print(" "); Serial.print(knob); Serial.print(" "); Serial.println(value); delay(MIDI_DELAY); } void setup() { auto cfg = M5.config(); cfg.serial_baudrate = 115200; M5.begin(); Serial.println("setup start"); while (!angle8.begin(I2C_ADDR, I2C_SDA, I2C_SCL, I2C_SPEED)) { Serial.println("angle8 connect error"); delay(100); } Serial.println("angle8 connect OK"); Control_Surface.begin(); esp_log_level_set("i2c.master", ESP_LOG_NONE); // see https://github.com/espressif/arduino-esp32/issues/11787 sw = angle8.getDigitalInput(); updateSwitch(sw); for (uint8_t knob = 0; knob < 8; knob++) { value[knob] = (255 - angle8.getAnalogInput(knob, _8bit)) / 2; updateValue(sw, knob, value[knob]); } Serial.println("setup done"); } void loop() { M5.update(); Control_Surface.loop(); bool newsw = angle8.getDigitalInput(); if (newsw != sw) { updateSwitch(newsw); sw = newsw; } for (uint8_t knob = 0; knob < ANGLE8_TOTAL_ADC; knob++) { uint16_t newvalue = (255 - angle8.getAnalogInput(knob, _8bit)) / 2; if (value[knob] != newvalue) { value[knob] = newvalue; updateValue(sw, knob, value[knob]); } } } ================================================ FILE: m5nanoc6_encoder8_midi/colormap.h ================================================ #ifndef _COLORMAP_H_ #define _COLORMAP_H_ // This is the colorcet "r2" or "rainbow2" colormap, which goes from blue via green and orange to red. // https://cmap-docs.readthedocs.io/en/latest/catalog/miscellaneous/colorcet:cet_r2/ uint8_t r[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 29, 36, 41, 46, 49, 52, 55, 57, 59, 60, 61, 62, 63, 63, 63, 63, 63, 63, 63, 65, 67, 70, 75, 80, 86, 91, 97, 102, 107, 112, 117, 122, 127, 132, 137, 141, 146, 151, 155, 160, 164, 169, 173, 178, 182, 186, 191, 195, 199, 204, 208, 212, 217, 221, 225, 229, 234, 238, 241, 244, 247, 249, 250, 251, 252, 253, 253, 253, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 254, 253, 253, 253, 253}; uint8_t g[128] = {52, 58, 63, 68, 72, 77, 81, 85, 89, 92, 96, 100, 103, 106, 109, 112, 115, 117, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 168, 169, 171, 172, 173, 174, 176, 177, 178, 179, 180, 181, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 205, 205, 205, 204, 202, 200, 198, 195, 193, 190, 187, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 157, 154, 151, 148, 145, 142, 139, 136, 133, 130, 126, 123, 120, 117, 113, 110, 106, 103, 99, 96, 92, 88, 84, 80, 75, 70, 66, 60, 55, 48}; uint8_t b[128] = {245, 240, 234, 228, 223, 217, 212, 206, 200, 195, 189, 184, 178, 173, 167, 162, 157, 152, 147, 142, 137, 132, 128, 123, 118, 113, 108, 103, 98, 93, 88, 82, 77, 71, 65, 59, 52, 46, 39, 32, 26, 21, 17, 15, 15, 15, 16, 17, 18, 19, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 42, 43, 44, 44, 44, 44, 44, 43, 42, 41, 40, 39, 38, 37, 36, 34, 33, 32, 31, 30, 29, 28, 27, 25, 24, 23, 22, 21, 20, 19, 17, 16, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0}; #endif ================================================ FILE: m5nanoc6_encoder8_midi/m5nanoc6_encoder8_midi.ino ================================================ /* This is a sketch for an M5NanoC6 or M5Dial controller connected to an 8Encoder input module. It provides MIDI control change (CC) messages over Bluetooth. See https://robertoostenveld.nl/low-cost-8-channel-midi-controller/ */ #include // https://github.com/m5stack/M5Unified #include // https://github.com/m5stack/M5Unit-8Encoder #include // https://github.com/tttapa/Control-Surface #include "colormap.h" // #define I2C_SDA G13 // for the M5Dial // #define I2C_SCL G15 #define I2C_SDA 2 // for the M5NanoC6, they are listed as G1 and G2 #define I2C_SCL 1 #define I2C_ADDR 0x41 #define I2C_SPEED 4000000L #define MIDI_DELAY 10 // do not send the MIDI messages too fast/frequent BluetoothMIDI_Interface midi; UNIT_8ENCODER encoder8; bool sw; int32_t value[8]; bool button[8]; void updateSwitch(bool sw) { uint32_t rgb = 0x0000FF00 * sw; // green or black encoder8.setLEDColor(8, rgb); } void updateValue(bool sw, uint8_t knob, int32_t value, bool button) { uint8_t rb = (r[value] / 4), gb = g[value] / 4, bb = b[value] / 4; // reduce the brightness uint32_t rgb = (rb << 16) | (gb << 8) | (bb << 0); encoder8.setLEDColor(knob, rgb); // the switch allows selecting MIDI channel 1 or 2 if (sw == 0) { MIDIAddress controller = { knob, Channel_1 }; midi.sendControlChange(controller, value); } else { MIDIAddress controller = { knob, Channel_2 }; midi.sendControlChange(controller, value); } Serial.print(sw); Serial.print(" "); Serial.print(knob); Serial.print(" "); Serial.print(value); Serial.print(" "); Serial.println(button); delay(MIDI_DELAY); } void setup() { auto cfg = M5.config(); cfg.serial_baudrate = 115200; M5.begin(); Serial.println("setup start"); while (!encoder8.begin(&Wire, I2C_ADDR, I2C_SDA, I2C_SCL, I2C_SPEED)) { Serial.println("encoder8 connect error"); delay(100); } Serial.println("encoder8 connect OK"); Control_Surface.begin(); esp_log_level_set("i2c.master", ESP_LOG_NONE); // see https://github.com/espressif/arduino-esp32/issues/11787 sw = encoder8.getSwitchStatus(); updateSwitch(sw); for (uint8_t knob = 0; knob < 8; knob++) { value[knob] = 0; button[knob] = false; encoder8.setEncoderValue(knob, value[knob]); updateValue(sw, knob, value[knob], button[knob]); } Serial.println("setup done"); } void loop() { M5.update(); Control_Surface.loop(); bool newsw = encoder8.getSwitchStatus(); if (newsw != sw) { updateSwitch(newsw); sw = newsw; } for (uint8_t knob = 0; knob < 8; knob++) { bool newbutton = !encoder8.getButtonStatus(knob); // the pushbutton seems to be inverted if (button[knob] != newbutton) { button[knob] = newbutton; updateValue(sw, knob, value[knob], button[knob]); } } for (uint8_t knob = 0; knob < 8; knob++) { int32_t newvalue = encoder8.getEncoderValue(knob); if (value[knob] != newvalue) { if (newvalue < 0) { newvalue = 0; encoder8.setEncoderValue(knob, newvalue); } else if (newvalue > 127) { newvalue = 127; encoder8.setEncoderValue(knob, newvalue); } value[knob] = newvalue; updateValue(sw, knob, value[knob], button[knob]); } } } ================================================ FILE: rfm12b_recv_xxxx/README.md ================================================ # Arduino relay between RFM12b and ThingSpeak This is a sketch for an Arduino pro mini attached to an RFM12b module. It operates in conjunction with the "rfm12b_thingspeak" sketch, which is running on an Arduino Uno with an Ethershield. The two Arduino's exchange sensor data over a i2c connection. See http://robertoostenveld.nl/arduino-relay-rfm12b-thingspeak/ for details. ================================================ FILE: rfm12b_recv_xxxx/rfm12b_recv_xxxx.ino ================================================ #include // https://github.com/jcw/jeelib #include #define BLIP_DEBUG #define BLIP_NODE 1 // set this to a unique ID to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define RF_CHIPSELECT 10 #define getbyte1(x) (x>> 0 & 0xff) #define getbyte2(x) (x>> 8 & 0xff) #define getbyte3(x) (x>>16 & 0xff) #define getbyte4(x) (x>>24 & 0xff) typedef struct payload_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; payload_t payload; /*******************************************************************************************************************/ void setup () { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_recv_xxxx / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif Wire.begin(); // Start I2C Bus as Master pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); } /*******************************************************************************************************************/ void loop () { // volatile byte rf12_hdr - Contains the header byte of the received packet - with flag bits and node ID of either the sender or the receiver. // volatile byte rf12_len - The number of data bytes in the packet. A value in the range 0 .. 66. // volatile byte rf12_data - A pointer to the received data. // volatile byte rf12_crc - CRC of the received packet, zero indicates correct reception. If != 0 then rf12_hdr, rf12_len, and rf12_data should not be relied upon. if (rf12_recvDone()) { if (RF12_WANTS_ACK) { rf12_sendStart(RF12_ACK_REPLY, 0, 0); } if (rf12_crc != 0) { Serial.println("Invalid crc"); } else if (rf12_len != sizeof payload) { Serial.println("Invalid len"); } else { memcpy(&payload, (void *)rf12_data, sizeof payload); unsigned long check = crc_buf((char *)&payload, sizeof(payload_t) - sizeof(unsigned long)); if (payload.crc == check) { write_i2c((byte *)&payload.id, sizeof(unsigned long)); write_i2c((byte *)&payload.counter, sizeof(unsigned long)); write_i2c((byte *)&payload.value1, sizeof(float)); write_i2c((byte *)&payload.value2, sizeof(float)); write_i2c((byte *)&payload.value3, sizeof(float)); write_i2c((byte *)&payload.value4, sizeof(float)); write_i2c((byte *)&payload.value5, sizeof(float)); write_i2c((byte *)&payload.crc, sizeof(unsigned long)); } else { Serial.print("checksum mismatch "); Serial.print(payload.crc); Serial.print(" != "); Serial.println(check); } // DISPLAY DATA Serial.print(payload.id); Serial.print(",\t"); Serial.print(payload.counter); Serial.print(",\t"); Serial.print(payload.value1, 2); Serial.print(",\t"); Serial.print(payload.value2, 2); Serial.print(",\t"); Serial.print(payload.value3, 2); Serial.print(",\t"); Serial.print(payload.value4, 2); Serial.print(",\t"); Serial.print(payload.value5, 2); Serial.print(",\t"); Serial.println(payload.crc); } } } // loop /*******************************************************************************************************************/ void write_i2c(byte *b, int n) { Wire.beginTransmission(9); // transmit to device #9 for (i = 0; i < n; i++) Wire.write(b[i]); Wire.endTransmission(); // stop transmitting delay(5); // otherwise the next transmission hangs } /*******************************************************************************************************************/ static PROGMEM prog_uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_idx; tbl_idx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); tbl_idx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i = 0; i < l; i++) crc = crc_update(crc, ((char *)b)[i]); crc = ~crc; return crc; } unsigned long crc_string(char *s) { unsigned long crc = ~0L; while (*s) crc = crc_update(crc, *s++); crc = ~crc; return crc; } ================================================ FILE: rfm12b_send_am2301/README.md ================================================ # Arduino with AM2301 temperature and humidity sensor See http://robertoostenveld.nl/arduino-am2301/ for details. ================================================ FILE: rfm12b_send_am2301/rfm12b_send_am2301.ino ================================================ #include // https://github.com/jcw/jeelib #include // http://arduino.cc/playground/Main/DHTLib #define BLIP_DEBUG #define BLIP_NODE x // set this to a unique ID to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define DHT21_PIN 3 #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 //#define RF_CHIPSELECT 9 #define RF_CHIPSELECT 10 // this must be defined since we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } dht DHT; typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; message_t message; float previous1 = 0, previous2 = 0, previous3 = 0; /*****************************************************************************/ void setup () { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_send_am2301 / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep message.id = BLIP_NODE; message.counter = 0; message.value1 = NAN; message.value2 = NAN; message.value3 = NAN; message.value4 = NAN; message.value5 = NAN; } /*****************************************************************************/ void loop () { boolean stable = false; while (!stable) { // READ DATA int chk = DHT.read21(DHT21_PIN); #ifdef BLIP_DEBUG Serial.print("DHT21, \t"); switch (chk) { case DHTLIB_OK: Serial.println("OK\t"); break; case DHTLIB_ERROR_CHECKSUM: Serial.println("Checksum error\t"); break; case DHTLIB_ERROR_TIMEOUT: Serial.println("Time out error\t"); break; default: Serial.println("Unknown error\t"); break; } #endif message.value1 = 0.001*readVcc(); // this is in mV, we want V message.value2 = DHT.temperature; // this is in Celcius message.value3 = DHT.humidity; // this is in % // compute the difference with the previous values previous1 -= message.value1; previous1 = abs(previous1); previous2 -= message.value2; previous2 = abs(previous2); previous3 -= message.value3; previous3 = abs(previous3); // the voltage should be stable within 0.1, the temperature within 1, the humidity within 5 stable = (previous1<0.1) && (previous2<1) && (previous3<5); // update the previous values previous1 = message.value1; previous2 = message.value2; previous3 = message.value3; if (!stable) { // the sensor is sluggish, you can't query it more than once every second or two https://learn.adafruit.com/dht // wait for three seconds before trying again delay(3000); } } // if stable message.counter += 1; message.crc = crc_buf((char *)&message, sizeof(message_t) - sizeof(unsigned long)); rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &message, sizeof message); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); // DISPLAY DATA #ifdef BLIP_DEBUG Serial.print(message.id); Serial.print(",\t"); Serial.print(message.counter); Serial.print(",\t"); Serial.print(message.value1, 2); Serial.print(",\t"); Serial.print(message.value2, 2); Serial.print(",\t"); Serial.print(message.value3, 2); Serial.print(",\t"); Serial.print(message.value4, 2); Serial.print(",\t"); Serial.print(message.value5, 2); Serial.print(",\t"); Serial.println(message.crc); #endif delay(100); // give it some time to send before falling asleep Sleepy::loseSomeTime(63000); } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1134050 / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ PROGMEM const uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_idx; tbl_idx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); tbl_idx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i=0; i #include #include #include // https://github.com/jcw/jeelib #include // https://github.com/jcw/jeelib #include // http://excamera.com/sphinx/article-crc.html #include //#define BLIP_DEBUG #define BLIP_NODE 4 // set this to a unique ID to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 #define RF_CHIPSELECT 10 // this must be defined since we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085); typedef struct payload_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; payload_t payload; float previous1 = 0, previous2 = 0, previous3 = 0; /**************************************************************************/ void displaySensorDetails(void) { sensor_t sensor; bmp.getSensor(&sensor); Serial.println("------------------------------------"); Serial.print ("Sensor: "); Serial.println(sensor.name); Serial.print ("Driver Ver: "); Serial.println(sensor.version); Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id); Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" hPa"); Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" hPa"); Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" hPa"); Serial.println("------------------------------------"); Serial.println(""); delay(500); } /**************************************************************************/ void setup(void) { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_send_bmp085 / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep /* Initialise the sensor */ if (!bmp.begin()) { /* There was a problem detecting the BMP085 ... check your connections */ Serial.print("Ooops, no BMP085 detected ... Check your wiring or I2C ADDR!"); while (1); } else { Serial.print("BMP085 detected."); } /* Display some basic information on this sensor */ displaySensorDetails(); payload.id = BLIP_NODE; payload.counter = 0; payload.value1 = NAN; payload.value2 = NAN; payload.value3 = NAN; payload.value4 = NAN; payload.value5 = NAN; } /**************************************************************************/ void loop(void) { boolean stable = false; float pressure, temperature; while (!stable) { bmp.getTemperature(&temperature); bmp.getPressure(&pressure); payload.value1 = 0.001 * readVcc(); // this is in mV, we want V payload.value2 = temperature; // this is in Celcius payload.value3 = 0.01 * pressure; // this is in Pa, we want hPa, i.e. mbar // compute the difference with the previous values previous1 -= payload.value1; previous1 = abs(previous1); previous2 -= payload.value2; previous2 = abs(previous2); previous3 -= payload.value3; previous3 = abs(previous3); // the voltage should be stable within 0.1, the temperature withing 1, the pressure within 1 stable = (previous1 < 0.1) && (previous2 < 1) && (previous3 < 1); #ifdef BLIP_DEBUG // DISPLAY DATA Serial.print("BMP085,\t"); Serial.print(payload.id); Serial.print(",\t"); Serial.print(payload.counter); Serial.print(",\t"); Serial.print(payload.value1, 2); Serial.print(",\t"); Serial.print(payload.value2, 2); Serial.print(",\t"); Serial.print(payload.value3, 2); Serial.print(",\t"); Serial.print(payload.value4, 2); Serial.print(",\t"); Serial.print(payload.value5, 2); Serial.print(",\t"); Serial.println(payload.crc); if (!stable) Serial.println("Not stable"); else Serial.println("Stable"); #endif // update the previous values previous1 = payload.value1; previous2 = payload.value2; previous3 = payload.value3; delay(100); } // if stable payload.counter += 1; payload.crc = crc_buf((char *)&payload, sizeof(payload_t) - sizeof(unsigned long)); #ifdef BLIP_DEBUG Serial.println("Sending..."); delay(100); #endif rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &payload, sizeof payload); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep Sleepy::loseSomeTime(64000); } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA, ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high << 8) | low; result = 1155700L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ PROGMEM const uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_idx; tbl_idx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); tbl_idx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i = 0; i < l; i++) crc = crc_update(crc, ((char *)b)[i]); crc = ~crc; return crc; } unsigned long crc_string(char *s) { unsigned long crc = ~0L; while (*s) crc = crc_update(crc, *s++); crc = ~crc; return crc; } ================================================ FILE: rfm12b_send_cny70/README.md ================================================ # Arduino with CNY70 reflective optical sensor. See http://robertoostenveld.nl/arduino-cny70/ for details. ================================================ FILE: rfm12b_send_cny70/rfm12b_send_cny70.ino ================================================ #include // https://github.com/jcw/jeelib #include // https://github.com/jcw/jeelib #include // http://excamera.com/sphinx/article-crc.html // RFM12demo for node N should be configured as // 1 B iN g17 @868.0000 MHz 0x3BB #define BLIP_DEBUG #define BLIP_NODE 5 // set this to a unique NODE to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 #define RF_CHIPSELECT 10 #define LED 9 #define KWH_DETECTOR A1 // pin number for the CNY50 sensor #define GAS_DETECTOR A2 // pin number for the CNY50 sensor typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; message_t message; unsigned int sensorValue = 0; // present sensor value unsigned int sensorMin = 1023; // minimum sensor value, start at max unsigned int sensorMax = 0; // maximum sensor value, start at min unsigned int prevValue = 0; unsigned long prevTime = 0; unsigned long currTime = 0; unsigned long diffTime = 0; unsigned long prevSend = 0; unsigned int pulseCount = 0; /*****************************************************************************/ void setup() { #ifdef BLIP_DEBUG Serial.begin(57600); while (!Serial); Serial.print("\n[rfm12b_send_cny70 / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(KWH_DETECTOR,INPUT); pinMode(GAS_DETECTOR,INPUT); pinMode(LED, OUTPUT); pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); message.id = BLIP_NODE; message.counter = 0; message.value1 = NAN; message.value2 = NAN; message.value3 = NAN; message.value4 = NAN; message.value5 = NAN; } /*****************************************************************************/ void loop() { // read the sensor, apply an IIR filter sensorValue = (5*analogRead(KWH_DETECTOR) + 5*sensorValue)/10; if (sensorValuesensorMax) sensorMax = sensorValue; Serial.print("val = "); Serial.print(sensorValue); Serial.print(" min = "); Serial.print(sensorMin); Serial.print(" max = "); Serial.print(sensorMax); Serial.println(); // threshold the sensor value somewhere between the min and max boolean pulse = sensorValue>((8*sensorMax+2*sensorMin)/10); if (pulse) digitalWrite(LED, HIGH); else digitalWrite(LED, LOW); // compute the time to the previous event, deal with integer overflow currTime = millis(); diffTime = currTime - prevTime; // detect the upgoing flank, debounce it with 100 ms if (pulse && prevValue==0 && diffTime>100) { prevTime = currTime; prevValue = 1; pulseCount++; #ifdef BLIP_DEBUG float watt = 6000000.0/diffTime; Serial.print("instantaneous watt = "); Serial.println(watt); #endif } else { prevValue = 0; } if ((currTime-prevSend)>65000) { // compute average power usage since last transmission float watt = (float)pulseCount * 6000000.0/(currTime-prevSend); prevSend = currTime; pulseCount = 0; message.value1 = 0.001*readVcc(); // this is in mV, we want V message.value2 = watt; message.counter += 1; message.crc = crc_buf((char *)&message, sizeof(message_t) - sizeof(unsigned long)); rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &message, sizeof message); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); #ifdef BLIP_DEBUG // DISPLAY DATA Serial.print("CNY70 \t"); Serial.print(message.id); Serial.print(",\t"); Serial.print(message.counter); Serial.print(",\t"); Serial.print(message.value1, 2); Serial.print(",\t"); Serial.print(message.value2, 2); Serial.print(",\t"); Serial.print(message.value3, 2); Serial.print(",\t"); Serial.print(message.value4, 2); Serial.print(",\t"); Serial.print(message.value5, 2); Serial.print(",\t"); Serial.println(message.crc); #endif } if (sensorMin<1023) sensorMin++; // if (sensorMax>0) // sensorMax--; // sample the CNY50 sensor approximately at 100 Hz delay(10); } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1108848L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ static PROGMEM prog_uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_NODEx; tbl_NODEx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); tbl_NODEx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i=0; i // http://www.pjrc.com/teensy/td_libs_OneWire.html #include // https://github.com/jcw/jeelib #include // https://github.com/jcw/jeelib #include // http://excamera.com/sphinx/article-crc.html #include //#define BLIP_DEBUG //#define USE_SLEEPY #define BLIP_NODE 6 // set this to a unique NODE to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 #define RF_CHIPSELECT 10 OneWire ds(9); // two ds18b20's are connected to this pin // this must be defined since we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; message_t message; /*****************************************************************************/ void setup () { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_send_ds18b20 / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep message.id = BLIP_NODE; message.counter = 0; message.value1 = NAN; message.value2 = NAN; message.value3 = NAN; message.value4 = NAN; message.value5 = NAN; } /*****************************************************************************/ void loop () { byte addr1[8] = { 0x28, 0x50, 0x49, 0x6B, 0x05, 0x00, 0x00, 0x1B }; byte addr2[8] = { 0x28, 0x07, 0x61, 0x98, 0x06, 0x00, 0x00, 0x6B }; ds.reset(); ds.select(addr1); ds.write(0x44, 1); // start conversion, with parasite power on at the end ds.reset(); ds.select(addr2); ds.write(0x44, 1); // start conversion, with parasite power on at the end delay(1000); // maybe 750ms is enough, maybe not message.value1 = 0.001*readVcc(); // this is in mV, we want V message.value2 = readTemp(addr1); message.value3 = readTemp(addr2); message.counter += 1; message.crc = crc_buf((char *)&message, sizeof(message_t) - sizeof(unsigned long)); rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &message, sizeof message); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); #ifdef BLIP_DEBUG // DISPLAY DATA Serial.print("DS18B20,\t"); Serial.print(message.id); Serial.print(",\t"); Serial.print(message.counter); Serial.print(",\t"); Serial.print(message.value1, 2); Serial.print(",\t"); Serial.print(message.value2, 2); Serial.print(",\t"); Serial.print(message.value3, 2); Serial.print(",\t"); Serial.print(message.value4, 2); Serial.print(",\t"); Serial.print(message.value5, 2); Serial.print(",\t"); Serial.println(message.crc); #endif delay(100); // give it some time to send before falling asleep #ifdef USE_SLEEPY // this does not work in combination with the OneWire library Sleepy::loseSomeTime(66000); #else delay(66000); #endif } /*****************************************************************************/ float readTemp(byte addr[]) { byte data[9]; float celsius, fahrenheit; ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for ( int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } // Convert the data to actual temperature // because the result is a 16 bit signed integer, it should // be stored to an "int16_t" type, which is always 16 bits // even when compiled on a 32 bit processor. int16_t raw = (data[1] << 8) | data[0]; byte cfg = (data[4] & 0x60); // at lower res, the low bits are undefined, so let's zero them if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 bit resolution, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 bit resolution, 375 ms //// default is 12 bit resolution, 750 ms conversion time celsius = (float)raw / 16.0; fahrenheit = celsius * 1.8 + 32.0; return celsius; } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1108848L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ static PROGMEM prog_uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_NODEx; tbl_NODEx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); tbl_NODEx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i=0; i // https://github.com/jcw/jeelib #include // https://github.com/jcw/jeelib #include // http://excamera.com/sphinx/article-crc.html #include #define BLIP_DEBUG #define BLIP_NODE 2 // set this to a unique NODE to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define LM35_PIN A2 #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 #define RF_CHIPSELECT 10 // this must be defined since we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; message_t message; float previous1 = 0, previous2 = 0; /*****************************************************************************/ void setup () { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_send_lm35 / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(LM35_PIN, INPUT); pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep message.id = BLIP_NODE; message.counter = 0; message.value1 = NAN; message.value2 = NAN; message.value3 = NAN; message.value4 = NAN; message.value5 = NAN; } /*****************************************************************************/ void loop () { boolean stable = false; while (!stable) { long vcc = readVcc(); float sensorValue = analogRead(LM35_PIN); sensorValue *= vcc; sensorValue /= 10230; message.value1 = 0.001*readVcc(); // this is in mV, we want V message.value2 = sensorValue; // this is Celcius // compute the difference with the previous values previous1 -= message.value1; previous1 = abs(previous1); previous2 -= message.value2; previous2 = abs(previous2); // the voltage should be stable within 0.1, the temperature within 1 stable = (previous1<0.1) && (previous2<1); // update the previous values previous1 = message.value1; previous2 = message.value2; } // if stable message.counter += 1; message.crc = crc_buf((char *)&message, sizeof(message_t) - sizeof(unsigned long)); rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &message, sizeof message); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); #ifdef BLIP_DEBUG // DISPLAY DATA Serial.print("LM35,\t"); Serial.print(message.id); Serial.print(",\t"); Serial.print(message.counter); Serial.print(",\t"); Serial.print(message.value1, 2); Serial.print(",\t"); Serial.print(message.value2, 2); Serial.print(",\t"); Serial.print(message.value3, 2); Serial.print(",\t"); Serial.print(message.value4, 2); Serial.print(",\t"); Serial.print(message.value5, 2); Serial.print(",\t"); Serial.println(message.crc); delay(100); // give it some time to send before falling asleep #endif Sleepy::loseSomeTime(62000); } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1108848L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ static PROGMEM prog_uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_NODEx; tbl_NODEx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); tbl_NODEx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i=0; i // https://github.com/jcw/jeelib #include // https://github.com/jcw/jeelib #include // http://excamera.com/sphinx/article-crc.html #include #define BLIP_DEBUG #define BLIP_NODE 7 // set this to a unique NODE to disambiguate multiple nodes #define BLIP_GRP 17 // wireless net group to use for sending blips #define SEND_MODE 2 // set to 3 if fuses are e=06/h=DE/l=CE, else set to 2 #define RF_CHIPSELECT 10 // this must be defined since we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; message_t message; /*****************************************************************************/ void setup () { #ifdef BLIP_DEBUG Serial.begin(57600); Serial.print("\n[rfm12b_send_random / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); #endif pinMode(RF_CHIPSELECT, OUTPUT); rf12_set_cs(RF_CHIPSELECT); rf12_initialize(BLIP_NODE, RF12_868MHZ, BLIP_GRP); rf12_sleep(RF12_SLEEP); delay(100); // give it some time to send before falling asleep message.id = BLIP_NODE; message.counter = 0; message.value1 = NAN; message.value2 = NAN; message.value3 = NAN; message.value4 = NAN; message.value5 = NAN; } /*****************************************************************************/ void loop () { boolean stable = false; long vcc = readVcc(); message.value1 = 0.001*readVcc(); // this is in mV, we want V message.value2 = (float)(random(2147483647L))/(2147483647L); message.value3 = (float)(random(2147483647L))/(2147483647L); message.value4 = (float)(random(2147483647L))/(2147483647L); message.value5 = (float)(random(2147483647L))/(2147483647L); message.counter += 1; message.crc = crc_buf((char *)&message, sizeof(message_t) - sizeof(unsigned long)); rf12_sleep(RF12_WAKEUP); rf12_sendNow(0, &message, sizeof message); rf12_sendWait(SEND_MODE); rf12_sleep(RF12_SLEEP); #ifdef BLIP_DEBUG // DISPLAY DATA Serial.print("RANDOM,\t"); Serial.print(message.id); Serial.print(",\t"); Serial.print(message.counter); Serial.print(",\t"); Serial.print(message.value1, 2); Serial.print(",\t"); Serial.print(message.value2, 2); Serial.print(",\t"); Serial.print(message.value3, 2); Serial.print(",\t"); Serial.print(message.value4, 2); Serial.print(",\t"); Serial.print(message.value5, 2); Serial.print(",\t"); Serial.println(message.crc); delay(100); // give it some time to send before falling asleep #endif Sleepy::loseSomeTime(67000); } /*****************************************************************************/ long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1108848L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } /*****************************************************************************/ PROGMEM const uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_NODEx; tbl_NODEx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); tbl_NODEx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i=0; i #include #include #include #include "secret.h" /* The write API keys for ThingSpeak are defined in secret.h like this #define APIKeyChannel2 "XXXXXXXXXXXXXXXX" #define APIKeyChannel3 "XXXXXXXXXXXXXXXX" #define APIKeyChannel4 "XXXXXXXXXXXXXXXX" #define APIKeyChannel5 "XXXXXXXXXXXXXXXX" #define APIKeyChannel6 "XXXXXXXXXXXXXXXX" */ #ifndef DEBUG #define DEBUG_PRINT(x) #define DEBUG_PRINT2(x, y) #define DEBUG_PRINTLN(x) #elif DEBUG == SERIAL #define DEBUG_PRINT(x) Serial.print (x) #define DEBUG_PRINT2(x, y) Serial.print (x, y) #define DEBUG_PRINTLN(x) Serial.println (x) #elif DEBUG == LOGGER #include #define DEBUG_PRINT(x) Syslog.logger(1, 6, "rfm12b_thingspeak:", "", x); #define DEBUG_PRINT2(x, y) Syslog.logger(1, 6, "rfm12b_thingspeak:", "", x); #define DEBUG_PRINTLN(x) Syslog.logger(1, 6, "rfm12b_thingspeak:", "", x); #else #error "Unexpected value of DEBUG." #endif byte buf[32]; // ring buffer unsigned int bufptr = 0; // pointer into ring buffer unsigned int bufblk = 0; // boolean to block buffer updates // ThingSpeak Settings IPAddress server(184, 106, 153, 149); // IP Address for the ThingSpeak API EthernetClient client; byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x2A, 0x4A }; byte loghost[] = { 192, 168, 1, 7 }; typedef struct message_t { unsigned long id; unsigned long counter; float value1; float value2; float value3; float value4; float value5; unsigned long crc; }; // Variable Setup unsigned long lastConnectionTime = 0; boolean lastConnectionStatus = false; unsigned int resetCounter = 0; /*******************************************************************************************************************/ void(* resetArduino) (void) = 0; // declare reset function at address 0 /*******************************************************************************************************************/ void setup() { Serial.begin(57600); Serial.println("\n[rfm12b_thingspeak / " __DATE__ " / " __TIME__ "]"); // start the Ethernet connection: while (Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); delay(5000); } // print your local IP address: Serial.print("My IP address: "); for (byte thisByte = 0; thisByte < 4; thisByte++) { // print the value of each byte of the IP address: Serial.print(Ethernet.localIP()[thisByte], DEC); Serial.print("."); } Serial.println(); for (int i; i < 32; i++) { buf[i] = 0; } Wire.begin(9); // Start I2C Bus as a Slave (Device Number 9) Wire.onReceive(receiveEvent); // register event #if defined(DEBUG) && DEBUG == LOGGER Syslog.setLoghost(loghost); Syslog.logger(1, 5, "", "rfm12b_thingspeak:", "setup finished"); #endif } // setup /*******************************************************************************************************************/ void loop() { // Clock rollover, reset after 49 days if (millis() < lastConnectionTime) resetArduino(); // There is a problem with ethernet, restart the shield if (resetCounter >= 5 ) { resetEthernetShield(); resetCounter = 0; } if (bufblk) { // the I2C buffer is full and needs to be processed message_t *message = (message_t *)buf; DEBUG_PRINTLN("packet received"); DEBUG_PRINT(message->id); DEBUG_PRINT(",\t"); DEBUG_PRINT(message->counter); DEBUG_PRINT(",\t"); DEBUG_PRINT2(message->value1, 2); DEBUG_PRINT(",\t"); DEBUG_PRINT2(message->value2, 2); DEBUG_PRINT(",\t"); DEBUG_PRINT2(message->value3, 2); DEBUG_PRINT(",\t"); DEBUG_PRINT2(message->value4, 2); DEBUG_PRINT(",\t"); DEBUG_PRINT2(message->value5, 2); DEBUG_PRINT(",\t"); DEBUG_PRINTLN(message->crc); if (message->crc == crc_buf((char *)message, sizeof(message_t) - sizeof(unsigned long))) { DEBUG_PRINTLN("CRC valid."); String postString = "&field1=" + String(message->value1) + "&field2=" + String(message->value2) + "&field3=" + String(message->value3) + "&field4=" + String(message->value4) + "&field5=" + String(message->value5) + "&field6=" + String(message->counter); DEBUG_PRINTLN(postString); // connect to ThingSpeak and forward the received message switch (message->id) { case 1: updateThingSpeak(postString, APIKeyChannel1); break; case 2: updateThingSpeak(postString, APIKeyChannel2); break; case 3: updateThingSpeak(postString, APIKeyChannel3); break; case 4: updateThingSpeak(postString, APIKeyChannel4); break; case 5: updateThingSpeak(postString, APIKeyChannel5); break; case 6: updateThingSpeak(postString, APIKeyChannel6); break; } // switch } // if message with correct CRC else { DEBUG_PRINTLN("CRC mismatch.\n"); } bufblk = 0; // release buffer } // if the I2C buffer is full } // loop /*******************************************************************************************************************/ void updateThingSpeak(const String tsData, const String writeAPIKey) { if (client.connected() || client.connect(server, 80)) { DEBUG_PRINTLN("Connected to ThingSpeak...\n"); lastConnectionTime = millis(); resetCounter = 0; client.print(F("POST /update HTTP/1.1\n")); client.print(F("Host: api.thingspeak.com\n")); client.print(F("Connection: close\n")); client.print(F("X-THINGSPEAKAPIKEY: ")); client.print(writeAPIKey); client.print(F("\n")); client.print(F("Content-Type: application/x-www-form-urlencoded\n")); client.print(F("Content-Length: ")); client.print(tsData.length()); client.print(F("\n\n")); client.print(tsData); // Print Update Response to Serial Monitor while (client.available()) { char c = client.read(); DEBUG_PRINT(c); } // disconnect from ThingSpeak client.stop(); } else { DEBUG_PRINTLN("Not connected.\n"); client.stop(); resetCounter++; } } // updateThingSpeak void resetEthernetShield() { DEBUG_PRINTLN("Resetting Ethernet Shield.\n"); client.stop(); delay(1000); while (Ethernet.begin(mac) == 0) { DEBUG_PRINTLN("Failed to configure Ethernet using DHCP"); delay(5000); } } // resetEthernetShield void receiveEvent(int howMany) { //DEBUG_PRINT("receiveEvent "); //DEBUG_PRINT(howMany); //DEBUG_PRINT(" : "); while (howMany-- > 0) { int x = Wire.read(); // receive byte as an integer //DEBUG_PRINT(x); //DEBUG_PRINT(" "); if (!bufblk) buf[bufptr++] = x; // insert into buffer if ((bufptr % 32) == 0) { bufptr = 0; // point to the first element bufblk = 1; // block buffer } } //DEBUG_PRINTLN(""); } // receiveEvent /*******************************************************************************************************************/ static const uint32_t PROGMEM crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; unsigned long crc_update(unsigned long crc, byte data) { byte tbl_idx; tbl_idx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); tbl_idx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4); return crc; } unsigned long crc_buf(char *b, long l) { unsigned long crc = ~0L; for (unsigned long i = 0; i < l; i++) crc = crc_update(crc, ((char *)b)[i]); crc = ~crc; return crc; } unsigned long crc_string(char *s) { unsigned long crc = ~0L; while (*s) crc = crc_update(crc, *s++); crc = ~crc; return crc; } ================================================ FILE: rp2040_dac7578/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: rp2040_dac7578/blink_led.h ================================================ #ifndef _BLINK_LED_H_ #define _BLINK_LED_H_ #include #include #define LED D13 //connected to the builtin LED on the Adafruit Feather RP2040 void ledInit(void); void ledOn(void); void ledOff(void); void ledSlow(void); void ledMedium(void); void ledFast(void); #endif ================================================ FILE: rp2040_dac7578/rp2040_dac7578.ino ================================================ /* * Sketch for an Adafruit RP2040 feather connected to two Adafruit DAC7578 modules. The purpose is to * act as a MEG phantom driver, controlling a small current through up to 16 magnetic dipole or * equivalent current dipole sources, thereby generating small magnetic fields that can be recorded * using an OPM- or SQUID-based MEG system. * * See * https://learn.adafruit.com/adafruit-feather-rp2040-pico * https://learn.adafruit.com/adafruit-dac7578-8-x-channel-12-bit-i2c-dac */ #include #include #include #include "blink_led.h" Adafruit_DACX578 dac0(12); Adafruit_DACX578 dac1(12); Adafruit_DACX578 dac2(12); const uint8_t nchannel = 24; const uint8_t addr0 = 0x4C; // open pads const uint8_t addr1 = 0x48; // left pads const uint8_t addr2 = 0x4A; // right pads float frequency[24] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; float phase[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const float freqshift = 0; const uint16_t amplitude = 2048; // Half of full scale for 12-bit dac0 (0 to 4095) const uint16_t offset = 2048; // DC offset to keep sine wave positive const uint32_t sampleTime = 1000; // Approximate time between samples, in microseconds const uint32_t feedbackTime = -1; // Time between serial feedback, in microseconds void setup() { // blink rapidly, this will continue indefinitely when the setup fails ledInit(); ledFast(); Serial.begin(115200); while (!Serial && (millis() < 5000)) { // wait for the serial interface to start, but not longer than 5 seconds delay(100); } Serial.println("-------------------------------"); Serial.println("rp2040 dac7578 sine wave generator"); Serial.print("firmware "); Serial.print(__TIME__); Serial.print(" / "); Serial.println(__DATE__); if (nchannel > 0) { // initialize the first DACX578 while (!dac0.begin(addr0, &Wire)) { Serial.println("Failed to find dac0"); delay(1000); } Serial.println("dac0 initialized"); } if (nchannel > 8) { // initialize the second DACX578 while (!dac1.begin(addr1, &Wire)) { Serial.println("Failed to find dac1"); delay(1000); } Serial.println("dac1 initialized"); } if (nchannel > 16) { // initialize the third DACX578 while (!dac2.begin(addr2, &Wire)) { Serial.println("Failed to find dac2"); delay(1000); } Serial.println("dac2 initialized"); } for (uint8_t channel = 0; channel < nchannel; channel++) { // shift the frequency frequency[channel] += freqshift; Serial.print("channel "); Serial.print(channel + 1); Serial.print(" = "); Serial.print(frequency[channel]); Serial.println(" Hz"); } // set I2C frequency to 3.4 MHz for faster communication Wire.setClock(3400000); // blink slowly for the rest of the time ledSlow(); } void loop() { static uint32_t lastSample = micros(); static uint32_t lastFeedback = micros(); uint32_t currentTime = micros(); uint32_t deltaTime = currentTime - lastSample; if (deltaTime >= sampleTime) { for (uint8_t channel = 0; channel < nchannel; channel++) { // increment the phase according to the channels-specific frequency phase[channel] += 2.0 * M_PI * frequency[channel] * ((float)deltaTime / 1000000); if (phase[channel] >= 2.0 * M_PI) { phase[channel] -= 2.0 * M_PI; } // compute the new value float sineValue = sin(phase[channel]) * amplitude + offset; // set the output to the new value if (channel < 8) // channels 0-7 on the first DAC dac0.writeAndUpdateChannelValue(channel, (uint16_t)sineValue); else if (channel < 16) // channels 8-15 correspond to channels 0-7 on the second DAC dac1.writeAndUpdateChannelValue(channel - 8, (uint16_t)sineValue); else if (channel < 24) // channels 16-23 correspond to channels 0-7 on the third DAC dac2.writeAndUpdateChannelValue(channel - 16, (uint16_t)sineValue); } lastSample = currentTime; if ((currentTime - lastFeedback) > feedbackTime) { Serial.println(deltaTime); lastFeedback = currentTime; } } // if deltaTime } // loop ================================================ FILE: teensy_cvgate_mcp4725_neopixel/colormap.h ================================================ /* * This is the "turbo" colormap, which resembles the MATLAB "jet" colormap * See https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html * * It consists of 256 RGB triplets with values between 0 and 1. */ float R[] = {0.18995,0.19483,0.19956,0.20415,0.20860,0.21291,0.21708,0.22111,0.22500,0.22875,0.23236,0.23582,0.23915,0.24234,0.24539,0.24830,0.25107,0.25369,0.25618,0.25853,0.26074,0.26280,0.26473,0.26652,0.26816,0.26967,0.27103,0.27226,0.27334,0.27429,0.27509,0.27576,0.27628,0.27667,0.27691,0.27701,0.27698,0.27680,0.27648,0.27603,0.27543,0.27469,0.27381,0.27273,0.27106,0.26878,0.26592,0.26252,0.25862,0.25425,0.24946,0.24427,0.23874,0.23288,0.22676,0.22039,0.21382,0.20708,0.20021,0.19326,0.18625,0.17923,0.17223,0.16529,0.15844,0.15173,0.14519,0.13886,0.13278,0.12698,0.12151,0.11639,0.11167,0.10738,0.10357,0.10026,0.09750,0.09532,0.09377,0.09287,0.09267,0.09320,0.09451,0.09662,0.09958,0.10342,0.10815,0.11374,0.12014,0.12733,0.13526,0.14391,0.15323,0.16319,0.17377,0.18491,0.19659,0.20877,0.22142,0.23449,0.24797,0.26180,0.27597,0.29042,0.30513,0.32006,0.33517,0.35043,0.36581,0.38127,0.39678,0.41229,0.42778,0.44321,0.45854,0.47375,0.48879,0.50362,0.51822,0.53255,0.54658,0.56026,0.57357,0.58646,0.59891,0.61088,0.62233,0.63323,0.64362,0.65394,0.66428,0.67462,0.68494,0.69525,0.70553,0.71577,0.72596,0.73610,0.74617,0.75617,0.76608,0.77591,0.78563,0.79524,0.80473,0.81410,0.82333,0.83241,0.84133,0.85010,0.85868,0.86709,0.87530,0.88331,0.89112,0.89870,0.90605,0.91317,0.92004,0.92666,0.93301,0.93909,0.94489,0.95039,0.95560,0.96049,0.96507,0.96931,0.97323,0.97679,0.98000,0.98289,0.98549,0.98781,0.98986,0.99163,0.99314,0.99438,0.99535,0.99607,0.99654,0.99675,0.99672,0.99644,0.99593,0.99517,0.99419,0.99297,0.99153,0.98987,0.98799,0.98590,0.98360,0.98108,0.97837,0.97545,0.97234,0.96904,0.96555,0.96187,0.95801,0.95398,0.94977,0.94538,0.94084,0.93612,0.93125,0.92623,0.92105,0.91572,0.91024,0.90463,0.89888,0.89298,0.88691,0.88066,0.87422,0.86760,0.86079,0.85380,0.84662,0.83926,0.83172,0.82399,0.81608,0.80799,0.79971,0.79125,0.78260,0.77377,0.76476,0.75556,0.74617,0.73661,0.72686,0.71692,0.70680,0.69650,0.68602,0.67535,0.66449,0.65345,0.64223,0.63082,0.61923,0.60746,0.59550,0.58336,0.57103,0.55852,0.54583,0.53295,0.51989,0.50664,0.49321,0.47960}; float G[] = {0.07176,0.08339,0.09498,0.10652,0.11802,0.12947,0.14087,0.15223,0.16354,0.17481,0.18603,0.19720,0.20833,0.21941,0.23044,0.24143,0.25237,0.26327,0.27412,0.28492,0.29568,0.30639,0.31706,0.32768,0.33825,0.34878,0.35926,0.36970,0.38008,0.39043,0.40072,0.41097,0.42118,0.43134,0.44145,0.45152,0.46153,0.47151,0.48144,0.49132,0.50115,0.51094,0.52069,0.53040,0.54015,0.54995,0.55979,0.56967,0.57958,0.58950,0.59943,0.60937,0.61931,0.62923,0.63913,0.64901,0.65886,0.66866,0.67842,0.68812,0.69775,0.70732,0.71680,0.72620,0.73551,0.74472,0.75381,0.76279,0.77165,0.78037,0.78896,0.79740,0.80569,0.81381,0.82177,0.82955,0.83714,0.84455,0.85175,0.85875,0.86554,0.87211,0.87844,0.88454,0.89040,0.89600,0.90142,0.90673,0.91193,0.91701,0.92197,0.92680,0.93151,0.93609,0.94053,0.94484,0.94901,0.95304,0.95692,0.96065,0.96423,0.96765,0.97092,0.97403,0.97697,0.97974,0.98234,0.98477,0.98702,0.98909,0.99098,0.99268,0.99419,0.99551,0.99663,0.99755,0.99828,0.99879,0.99910,0.99919,0.99907,0.99873,0.99817,0.99739,0.99638,0.99514,0.99366,0.99195,0.98999,0.98775,0.98524,0.98246,0.97941,0.97610,0.97255,0.96875,0.96470,0.96043,0.95593,0.95121,0.94627,0.94113,0.93579,0.93025,0.92452,0.91861,0.91253,0.90627,0.89986,0.89328,0.88655,0.87968,0.87267,0.86553,0.85826,0.85087,0.84337,0.83576,0.82806,0.82025,0.81236,0.80439,0.79634,0.78823,0.78005,0.77181,0.76352,0.75519,0.74682,0.73842,0.73000,0.72140,0.71250,0.70330,0.69382,0.68408,0.67408,0.66386,0.65341,0.64277,0.63193,0.62093,0.60977,0.59846,0.58703,0.57549,0.56386,0.55214,0.54036,0.52854,0.51667,0.50479,0.49291,0.48104,0.46920,0.45740,0.44565,0.43399,0.42241,0.41093,0.39958,0.38836,0.37729,0.36638,0.35566,0.34513,0.33482,0.32473,0.31489,0.30530,0.29599,0.28696,0.27824,0.26981,0.26152,0.25334,0.24526,0.23730,0.22945,0.22170,0.21407,0.20654,0.19912,0.19182,0.18462,0.17753,0.17055,0.16368,0.15693,0.15028,0.14374,0.13731,0.13098,0.12477,0.11867,0.11268,0.10680,0.10102,0.09536,0.08980,0.08436,0.07902,0.07380,0.06868,0.06367,0.05878,0.05399,0.04931,0.04474,0.04028,0.03593,0.03169,0.02756,0.02354,0.01963,0.01583}; float B[] = {0.23217,0.26149,0.29024,0.31844,0.34607,0.37314,0.39964,0.42558,0.45096,0.47578,0.50004,0.52373,0.54686,0.56942,0.59142,0.61286,0.63374,0.65406,0.67381,0.69300,0.71162,0.72968,0.74718,0.76412,0.78050,0.79631,0.81156,0.82624,0.84037,0.85393,0.86692,0.87936,0.89123,0.90254,0.91328,0.92347,0.93309,0.94214,0.95064,0.95857,0.96594,0.97275,0.97899,0.98461,0.98930,0.99303,0.99583,0.99773,0.99876,0.99896,0.99835,0.99697,0.99485,0.99202,0.98851,0.98436,0.97959,0.97423,0.96833,0.96190,0.95498,0.94761,0.93981,0.93161,0.92305,0.91416,0.90496,0.89550,0.88580,0.87590,0.86581,0.85559,0.84525,0.83484,0.82437,0.81389,0.80342,0.79299,0.78264,0.77240,0.76230,0.75237,0.74265,0.73316,0.72393,0.71500,0.70599,0.69651,0.68660,0.67627,0.66556,0.65448,0.64308,0.63137,0.61938,0.60713,0.59466,0.58199,0.56914,0.55614,0.54303,0.52981,0.51653,0.50321,0.48987,0.47654,0.46325,0.45002,0.43688,0.42386,0.41098,0.39826,0.38575,0.37345,0.36140,0.34963,0.33816,0.32701,0.31622,0.30581,0.29581,0.28623,0.27712,0.26849,0.26038,0.25280,0.24579,0.23937,0.23356,0.22835,0.22370,0.21960,0.21602,0.21294,0.21032,0.20815,0.20640,0.20504,0.20406,0.20343,0.20311,0.20310,0.20336,0.20386,0.20459,0.20552,0.20663,0.20788,0.20926,0.21074,0.21230,0.21391,0.21555,0.21719,0.21880,0.22038,0.22188,0.22328,0.22456,0.22570,0.22667,0.22744,0.22800,0.22831,0.22836,0.22811,0.22754,0.22663,0.22536,0.22369,0.22161,0.21918,0.21650,0.21358,0.21043,0.20706,0.20348,0.19971,0.19577,0.19165,0.18738,0.18297,0.17842,0.17376,0.16899,0.16412,0.15918,0.15417,0.14910,0.14398,0.13883,0.13367,0.12849,0.12332,0.11817,0.11305,0.10797,0.10294,0.09798,0.09310,0.08831,0.08362,0.07905,0.07461,0.07031,0.06616,0.06218,0.05837,0.05475,0.05134,0.04814,0.04516,0.04243,0.03993,0.03753,0.03521,0.03297,0.03082,0.02875,0.02677,0.02487,0.02305,0.02131,0.01966,0.01809,0.01660,0.01520,0.01387,0.01264,0.01148,0.01041,0.00942,0.00851,0.00769,0.00695,0.00629,0.00571,0.00522,0.00481,0.00449,0.00424,0.00408,0.00401,0.00401,0.00410,0.00427,0.00453,0.00486,0.00529,0.00579,0.00638,0.00705,0.00780,0.00863,0.00955,0.01055}; ================================================ FILE: teensy_cvgate_mcp4725_neopixel/teensy_cvgate_mcp4725_neopixel.ino ================================================ /* * EEGSynth Teensy based CV/Gate controller. This sketch allows * one to use control voltages and gates to interface a computer * with an analog synthesizer. The hardware comprises a Teensy * with two MCP4725 12-bit DAC breakout boards. * * 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) 2020, Robert Oostenveld, http://www.eegsynth.org/ */ #include //Include the Wire library to talk I2C #include #include "colormap.h" #define NEOPIXEL_PIN 14 #define GATE1_PIN 15 #define GATE2_PIN 16 #define WIRE_SDAPIN 18 #define WIRE_SCLPIN 19 // 0x60 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 address1 0x60 #define address2 0x61 #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) // the values of the DAC range from 0 to 4095 (12 bits) #define MAXVALUE 4095. // these are used to interpret the commands over the serial interface #define NONE 0 #define VOLTAGE 1 #define GATE 2 // this is the status after parsing the commands over the serial interface #define OK 0 #define ERROR -1 #define NUMPIXELS 4 #define BRIGHTNESS 0.3 Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); // tghese are the initial values of the output control voltages and gates int voltage1 = 0, voltage2 = 0; int gate1 = 0, gate2 = 0; void setColor(int led, float value) { byte r, g, b; int index = value*255.; index = (index > 255 ? 255 : index); // must be between 0 and 255 r = 255*R[index]*BRIGHTNESS; g = 255*G[index]*BRIGHTNESS; b = 255*B[index]*BRIGHTNESS; pixels.setPixelColor(led, pixels.Color(r, g, b)); pixels.show(); return; } void setValue(uint16_t address, uint16_t value) { uint8_t msb, lsb; msb = (value / 16); lsb = (value % 16) << 4; Wire.beginTransmission(address); 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(); return; } void setup() { // initialize the serial communication: while (!Serial) {;} Serial.begin(115200); Serial.print("\n[teensy_cvgate_mcp4725_neopixel/ "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); Serial.setTimeout(1000); Wire.begin(); Wire.setSDA(WIRE_SDAPIN); Wire.setSCL(WIRE_SCLPIN); // initialize the gate pins as output: pinMode(GATE1_PIN, OUTPUT); pinMode(GATE2_PIN, OUTPUT); // INITIALIZE NeoPixel strip object pixels.begin(); delay(1000); // Set all pixel colors to 'off' pixels.clear(); // Set the pixels to RGBW to indicate that setup is done pixels.setPixelColor(0, pixels.Color(128, 0, 0)); pixels.show(); delay(250); pixels.setPixelColor(1, pixels.Color(0, 128, 0)); pixels.show(); delay(250); pixels.setPixelColor(2, pixels.Color(0, 0, 128)); pixels.show(); delay(250); pixels.setPixelColor(3, pixels.Color(128, 128, 128)); pixels.show(); delay(250); Serial.println("Setup done."); return; } 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 > MAXVALUE ? MAXVALUE : value); status = OK; break; case 2: voltage2 = (value > MAXVALUE ? MAXVALUE : value); status = OK; 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; default: status = ERROR; } } else { status = ERROR; } if (status == OK) Serial.println("OK"); else if (status == ERROR) Serial.println("error"); } else { // update the output cointrol voltages and gates setValue(address1, voltage1); setValue(address2, voltage2); digitalWrite(GATE1_PIN, gate1); digitalWrite(GATE2_PIN, gate2); // update the Neopixels by mapping a value between 0 and 1 onto the colormap // note that they are mounted in the opposite order than the 3.5 mm jacks if (!Serial) { // switch all pixels off when there is no serial connection pixels.clear(); pixels.show(); } else { // the integer representation of the control voltage is represented between 0 and 4095 setColor(3, voltage1/MAXVALUE); setColor(2, voltage2/MAXVALUE); // the integer representation of the gate voltage is either 0 or 1 setColor(1, gate1); setColor(0, gate2); } } } //main ================================================ FILE: teensy_gps_temp_ttn/payload.h ================================================ // This is identical to the messages I send with my RFM12b nodes struct payload_t { unsigned int id; unsigned int counter; float value1; float value2; float value3; float value4; float value5; unsigned int crc; }; ================================================ FILE: teensy_gps_temp_ttn/sensor.cpp ================================================ // Include the libraries we need #include #include #include #include "payload.h" extern payload_t payload; // Data wire is plugged into port 15 on the Teensy #define ONE_WIRE_BUS 15 // pin 13 corresponds to the onboard LED, but it is already used for SPI // hence we are using an external LED #define LED 14 // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) OneWire oneWire(ONE_WIRE_BUS); // Pass our oneWire reference to Dallas Temperature. DallasTemperature dallasTemperature(&oneWire); // TinyGPS++ requires the use of SoftwareSerial, and assumes that you have a // 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). static const int RXPin = 4, TXPin = 3; static const uint32_t GPSBaud = 9600; // The TinyGPS++ object TinyGPSPlus gps; // The serial connection to the GPS device #define ss Serial1 // These functions are defined further down uint32_t readVcc(); unsigned long crc_buf(char *b, unsigned long l); /*****************************************************************************/ void init_sensor() { pinMode(LED, OUTPUT); // Start up the GPS serial port ss.begin(GPSBaud); // Start up the Wire interface dallasTemperature.begin(); // Start up the internal voltage measurement analogReference(EXTERNAL); analogReadResolution(12); analogReadAveraging(32); // this one is optional } /*****************************************************************************/ void read_sensor() { float lat = 0, lng = 0, alt = 0, temp = 0, volt = 0; int verbose = 1; digitalWrite(LED, HIGH); volt = 0.001 * readVcc(); // this is in mV, we want V dallasTemperature.requestTemperatures(); temp = dallasTemperature.getTempCByIndex(0); unsigned long timeout = millis() + 1000; while (millis() < timeout) { // keep reading until a full sentence is correctly encoded if (ss.available() > 0) { if (gps.encode(ss.read()) || millis() > timeout) { timeout = millis(); if (gps.location.isValid()) { lat = gps.location.lat(); lng = gps.location.lng(); alt = gps.altitude.meters(); } } } else { // wait for some new data to come in from the GPS delay(10); } } payload.value1 = volt; payload.value2 = temp; payload.value3 = lat; payload.value4 = lng; payload.value5 = alt; payload.counter += 1; payload.crc = crc_buf((char *)&payload, sizeof(payload_t) - sizeof(unsigned long)); if (verbose) { Serial.print(payload.id); Serial.print(",\t"); Serial.print(payload.counter); Serial.print(",\t"); Serial.print(payload.value1, 2); Serial.print(",\t"); Serial.print(payload.value2, 2); Serial.print(",\t"); Serial.print(payload.value3, 6); Serial.print(",\t"); Serial.print(payload.value4, 6); Serial.print(",\t"); Serial.print(payload.value5, 2); Serial.print(",\t"); Serial.println(payload.crc); } digitalWrite(LED, LOW); } /*****************************************************************************/ // see https://forum.pjrc.com/threads/26117-Teensy-3-1-Voltage-sensing-and-low-battery-alert // for Teensy 3.0, only valid between 2.0V and 3.5V. Returns in millivolts. uint32_t readVcc() { uint32_t x = analogRead(39); return (178 * x * x + 2688757565 - 1184375 * x) / 372346; }; /*****************************************************************************/ static PROGMEM prog_uint32_t crc_table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; /*****************************************************************************/ unsigned long crc_update(unsigned long crc, byte data) { byte tbl_NODEx; tbl_NODEx = crc ^ (data >> (0 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); tbl_NODEx = crc ^ (data >> (1 * 4)); crc = pgm_read_dword_near(crc_table + (tbl_NODEx & 0x0f)) ^ (crc >> 4); return crc; } /*****************************************************************************/ unsigned long crc_buf(char *b, unsigned long l) { unsigned long crc = ~0L; for (unsigned long i = 0; i < l; i++) crc = crc_update(crc, ((char *)b)[i]); crc = ~crc; return crc; } /*****************************************************************************/ unsigned long crc_string(char *s) { unsigned long crc = ~0L; while (*s) crc = crc_update(crc, *s++); crc = ~crc; return crc; } ================================================ FILE: teensy_gps_temp_ttn/teensy_gps_temp_ttn.ino ================================================ #include #include #include // the LoRa keys and IDs are secret and should not be disclosed #include "secret.h" //static const PROGMEM u1_t NWKSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u1_t PROGMEM APPSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u4_t DEVADDR = 0x00000000; #include "payload.h" payload_t payload; void init_sensor(); void read_sensor(); // These callbacks are only used in over-the-air activation, so they are // left empty here (we cannot leave them out completely unless // DISABLE_JOIN is set in config.h, otherwise the linker will complain). void os_getArtEui (u1_t* buf) { } void os_getDevEui (u1_t* buf) { } void os_getDevKey (u1_t* buf) { } static osjob_t sendjob; // Schedule TX every this many seconds (might become longer due to duty cycle limitations). const unsigned TX_INTERVAL = 10; // Pin mapping const lmic_pinmap lmic_pins = { .nss = 10, .rxtx = LMIC_UNUSED_PIN, .rst = 9, .dio = {2, 5, 6}, }; void onEvent (ev_t ev) { Serial.print(os_getTime()); Serial.print(": "); switch (ev) { case EV_SCAN_TIMEOUT: Serial.println(F("EV_SCAN_TIMEOUT")); break; case EV_BEACON_FOUND: Serial.println(F("EV_BEACON_FOUND")); break; case EV_BEACON_MISSED: Serial.println(F("EV_BEACON_MISSED")); break; case EV_BEACON_TRACKED: Serial.println(F("EV_BEACON_TRACKED")); break; case EV_JOINING: Serial.println(F("EV_JOINING")); break; case EV_JOINED: Serial.println(F("EV_JOINED")); break; case EV_RFU1: Serial.println(F("EV_RFU1")); break; case EV_JOIN_FAILED: Serial.println(F("EV_JOIN_FAILED")); break; case EV_REJOIN_FAILED: Serial.println(F("EV_REJOIN_FAILED")); break; case EV_TXCOMPLETE: Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); if (LMIC.dataLen) { // data received in rx slot after tx Serial.print(F("Data Received: ")); Serial.write(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); Serial.println(); } // Schedule next transmission os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send); break; case EV_LOST_TSYNC: Serial.println(F("EV_LOST_TSYNC")); break; case EV_RESET: Serial.println(F("EV_RESET")); break; case EV_RXCOMPLETE: // data received in ping slot Serial.println(F("EV_RXCOMPLETE")); break; case EV_LINK_DEAD: Serial.println(F("EV_LINK_DEAD")); break; case EV_LINK_ALIVE: Serial.println(F("EV_LINK_ALIVE")); break; default: Serial.println(F("Unknown event")); break; } } void do_send(osjob_t* j) { // Check if there is not a current TX/RX job running if (LMIC.opmode & OP_TXRXPEND) { Serial.println(F("OP_TXRXPEND, not sending")); } else { // get the sensor data read_sensor(); // Prepare upstream data transmission at the next possible time. LMIC_setTxData2(1, (unsigned char *)&payload, sizeof(payload_t), 0); Serial.println(F("Packet queued")); } // Next TX is scheduled after TX_COMPLETE event. } void setup() { Serial.begin(115200); unsigned long timeout = millis() + 1000; while (!Serial && millis() < timeout) { ; // wait for serial port to connect. Needed for native USB } Serial.println(F("Starting")); // initialize the sensors init_sensor(); // Start with an empty packet payload.id = 1; payload.counter = 0; // LMIC init os_init(); // Reset the MAC state. Session and pending data transfers will be discarded. LMIC_reset(); // Set static session parameters. Instead of dynamically establishing a session // by joining the network, precomputed session parameters are be provided. #ifdef PROGMEM // On AVR, these values are stored in flash and only copied to RAM // once. Copy them to a temporary buffer here, LMIC_setSession will // copy them into a buffer of its own again. uint8_t appskey[sizeof(APPSKEY)]; uint8_t nwkskey[sizeof(NWKSKEY)]; memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); #else // If not running an AVR with PROGMEM, just use the arrays directly LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); #endif // Set up the channels used by the Things Network, which corresponds // to the defaults of most gateways. Without this, only three base // channels from the LoRaWAN specification are used, which certainly // works, so it is good for debugging, but can overload those // frequencies, so be sure to configure the full frequency range of // your network here (unless your network autoconfigures them). // Setting up channels should happen after LMIC_setSession, as that // configures the minimal channel set. LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band // TTN defines an additional channel at 869.525Mhz using SF9 for class B // devices' ping slots. LMIC does not have an easy way to define set this // frequency and support for class B is spotty and untested, so this // frequency is not configured here. // Disable link check validation LMIC_setLinkCheckMode(0); // Set data rate and transmit power (note: txpow seems to be ignored by the library) LMIC_setDrTxpow(DR_SF7, 14); // Start job do_send(&sendjob); } void loop() { os_runloop_once(); } ================================================ FILE: teensy_midifilter/midiname.c ================================================ // To give your project a unique name, this code must be // placed into a .c file (its own tab). It can not be in // a .cpp file or your main sketch (the .ino file). #include "usb_names.h" // Edit these lines to create your own name. The length must // match the number of characters in your custom name. #define MIDI_NAME {'T','e','e','n','s','y'} #define MIDI_NAME_LEN 6 // Do not change this part. This exact format is required by USB. struct usb_string_descriptor_struct usb_string_product_name = { 2 + MIDI_NAME_LEN * 2, 3, MIDI_NAME }; ================================================ FILE: teensy_midifilter/teensy_midifilter.ino ================================================ #include // select "tools"->"usb type"->"serial + MIDI" to instantiate the usbMIDI device MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, inMIDI); MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, outMIDI); // #define DEBUG_NOTE // #define DEBUG_SERIAL /************************************************************************************************************/ void handleNoteOff(byte Channel, byte NoteNumber, byte Velocity) { outMIDI.sendNoteOff(NoteNumber, Velocity, Channel); }; void handleNoteOn(byte Channel, byte NoteNumber, byte Velocity) { outMIDI.sendNoteOn(NoteNumber, Velocity, Channel); }; void handleAfterTouchPoly(byte Channel, byte NoteNumber, byte Pressure) { outMIDI.sendPolyPressure(NoteNumber, Pressure, Channel); }; void handleControlChange(byte Channel, byte ControlNumber, byte ControlValue) { outMIDI.sendControlChange(ControlNumber, ControlValue, Channel); }; void handleProgramChange(byte Channel, byte ProgramNumber) { outMIDI.sendProgramChange(ProgramNumber, Channel); }; void handleAfterTouchChannel(byte Channel, byte Pressure) { outMIDI.sendAfterTouch(Pressure, Channel); }; void handlePitchBend(byte Channel, int PitchValue) { outMIDI.sendPitchBend(PitchValue, Channel); }; void handleSystemExclusive(byte* Array, unsigned Size) { outMIDI.sendSysEx(Size, Array); }; void handleTimeCodeQuarterFrame(byte Data) { outMIDI.sendTimeCodeQuarterFrame(Data); }; void handleSongPosition(unsigned int Beats) { outMIDI.sendSongPosition(Beats); }; void handleSongSelect(byte SongNumber) { outMIDI.sendSongSelect(SongNumber); }; void handleTuneRequest(void) { outMIDI.sendTuneRequest(); }; void handleClock(void) { outMIDI.sendRealTime(midi::Clock); }; void handleStart(void) { outMIDI.sendRealTime(midi::Start); }; void handleContinue(void) { outMIDI.sendRealTime(midi::Continue); }; void handleStop(void) { outMIDI.sendRealTime(midi::Stop); }; void handleActiveSensing(void) { outMIDI.sendRealTime(midi::ActiveSensing); }; void handleSystemReset(void) { outMIDI.sendRealTime(midi::SystemReset); }; /************************************************************************************************************/ long prev = 0; const int led = 13; #ifdef DEBUG_NOTE const int channel = 1; const int note = 42; const int velocity = 127; #endif /************************************************************************************************************/ void setup() { Serial.print("\n[teensy_midifilter / "); Serial.print(__DATE__); Serial.print(" / "); Serial.print(__TIME__); Serial.println("]"); pinMode(led, OUTPUT); outMIDI.begin(MIDI_CHANNEL_OMNI); inMIDI.begin(MIDI_CHANNEL_OMNI); inMIDI.setHandleNoteOff(handleNoteOff); inMIDI.setHandleNoteOn(handleNoteOn); inMIDI.setHandleAfterTouchPoly(handleAfterTouchPoly); inMIDI.setHandleControlChange(handleControlChange); inMIDI.setHandleProgramChange(handleProgramChange); inMIDI.setHandleAfterTouchChannel(handleAfterTouchChannel); inMIDI.setHandlePitchBend(handlePitchBend); inMIDI.setHandleSystemExclusive(handleSystemExclusive); inMIDI.setHandleTimeCodeQuarterFrame(handleTimeCodeQuarterFrame); inMIDI.setHandleSongPosition(handleSongPosition); inMIDI.setHandleSongSelect(handleSongSelect); inMIDI.setHandleTuneRequest(handleTuneRequest); inMIDI.setHandleClock(handleClock); //inMIDI.setHandleStart(handleStart); //inMIDI.setHandleContinue(handleContinue); //inMIDI.setHandleStop(handleStop); inMIDI.setHandleActiveSensing(handleActiveSensing); inMIDI.setHandleSystemReset(handleSystemReset); } /************************************************************************************************************/ void loop() { if ((millis() - prev) > 1000) { // blink every second digitalWrite(led, !digitalRead(led)); #ifdef DEBUG_NOTE // send a MIDI note every second outMIDI.sendNoteOn(note, velocity, channel); usbMIDI.sendNoteOn(note, velocity, channel); #endif prev = millis(); } while (inMIDI.read()) { #ifdef DEBUG_SERIAL Serial.print(inMIDI.getType()); Serial.print(" "); switch (inMIDI.getType()) { case midi::ActiveSensing: Serial.println("ActiveSensing"); break; case midi::AfterTouchChannel: Serial.println("AfterTouchChannel"); break; case midi::AfterTouchPoly: Serial.println("AfterTouchPoly"); break; case midi::Clock: Serial.println("Clock"); break; case midi::Continue: Serial.println("Continue"); break; case midi::ControlChange: Serial.println("ControlChange"); break; case midi::InvalidType: Serial.println("InvalidType"); break; case midi::NoteOff: Serial.println("NoteOff"); break; case midi::NoteOn: Serial.println("NoteOn"); break; case midi::PitchBend: Serial.println("PitchBend"); break; case midi::ProgramChange: Serial.println("ProgramChange"); break; case midi::SongPosition: Serial.println("SongPosition"); break; case midi::SongSelect: Serial.println("SongSelect"); break; case midi::Start: Serial.println("Start"); break; case midi::Stop: Serial.println("Stop"); break; case midi::SystemExclusive: Serial.println("SystemExclusive"); break; case midi::SystemReset: Serial.println("SystemReset"); break; case midi::TimeCodeQuarterFrame: Serial.println("TimeCodeQuarterFrame"); break; case midi::TuneRequest: Serial.println("TuneRequest"); default: Serial.println("Other"); break; } #endif } } ================================================ FILE: teensy_ttn1/teensy_ttn1.ino ================================================ #include #include #include // the LoRa keys and IDs are secret and should not be disclosed #include "secret.h" //static const PROGMEM u1_t NWKSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u1_t PROGMEM APPSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u4_t DEVADDR = 0x00000000; // These callbacks are only used in over-the-air activation, so they are // left empty here (we cannot leave them out completely unless // DISABLE_JOIN is set in config.h, otherwise the linker will complain). void os_getArtEui (u1_t* buf) { } void os_getDevEui (u1_t* buf) { } void os_getDevKey (u1_t* buf) { } static uint8_t mydata[] = "Hello, world!"; static osjob_t sendjob; // Schedule TX every this many seconds (might become longer due to duty cycle limitations). const unsigned TX_INTERVAL = 15; // Pin mapping const lmic_pinmap lmic_pins = { .nss = 10, .rxtx = LMIC_UNUSED_PIN, .rst = 9, .dio = {2, 5, 6}, }; void onEvent (ev_t ev) { Serial.print(os_getTime()); Serial.print(": "); switch(ev) { case EV_SCAN_TIMEOUT: Serial.println(F("EV_SCAN_TIMEOUT")); break; case EV_BEACON_FOUND: Serial.println(F("EV_BEACON_FOUND")); break; case EV_BEACON_MISSED: Serial.println(F("EV_BEACON_MISSED")); break; case EV_BEACON_TRACKED: Serial.println(F("EV_BEACON_TRACKED")); break; case EV_JOINING: Serial.println(F("EV_JOINING")); break; case EV_JOINED: Serial.println(F("EV_JOINED")); break; case EV_RFU1: Serial.println(F("EV_RFU1")); break; case EV_JOIN_FAILED: Serial.println(F("EV_JOIN_FAILED")); break; case EV_REJOIN_FAILED: Serial.println(F("EV_REJOIN_FAILED")); break; case EV_TXCOMPLETE: Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); if(LMIC.dataLen) { // data received in rx slot after tx Serial.print(F("Data Received: ")); Serial.write(LMIC.frame+LMIC.dataBeg, LMIC.dataLen); Serial.println(); } // Schedule next transmission os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); break; case EV_LOST_TSYNC: Serial.println(F("EV_LOST_TSYNC")); break; case EV_RESET: Serial.println(F("EV_RESET")); break; case EV_RXCOMPLETE: // data received in ping slot Serial.println(F("EV_RXCOMPLETE")); break; case EV_LINK_DEAD: Serial.println(F("EV_LINK_DEAD")); break; case EV_LINK_ALIVE: Serial.println(F("EV_LINK_ALIVE")); break; default: Serial.println(F("Unknown event")); break; } } void do_send(osjob_t* j){ // Check if there is not a current TX/RX job running if (LMIC.opmode & OP_TXRXPEND) { Serial.println(F("OP_TXRXPEND, not sending")); } else { // Prepare upstream data transmission at the next possible time. LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); Serial.println(F("Packet queued")); } // Next TX is scheduled after TX_COMPLETE event. } void setup() { Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for native USB } Serial.println(F("Starting")); #ifdef VCC_ENABLE // For Pinoccio Scout boards pinMode(VCC_ENABLE, OUTPUT); digitalWrite(VCC_ENABLE, HIGH); delay(1000); #endif // LMIC init os_init(); // Reset the MAC state. Session and pending data transfers will be discarded. LMIC_reset(); // Set static session parameters. Instead of dynamically establishing a session // by joining the network, precomputed session parameters are be provided. #ifdef PROGMEM // On AVR, these values are stored in flash and only copied to RAM // once. Copy them to a temporary buffer here, LMIC_setSession will // copy them into a buffer of its own again. uint8_t appskey[sizeof(APPSKEY)]; uint8_t nwkskey[sizeof(NWKSKEY)]; memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); #else // If not running an AVR with PROGMEM, just use the arrays directly LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); #endif // Set up the channels used by the Things Network, which corresponds // to the defaults of most gateways. Without this, only three base // channels from the LoRaWAN specification are used, which certainly // works, so it is good for debugging, but can overload those // frequencies, so be sure to configure the full frequency range of // your network here (unless your network autoconfigures them). // Setting up channels should happen after LMIC_setSession, as that // configures the minimal channel set. LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band // TTN defines an additional channel at 869.525Mhz using SF9 for class B // devices' ping slots. LMIC does not have an easy way to define set this // frequency and support for class B is spotty and untested, so this // frequency is not configured here. // Disable link check validation LMIC_setLinkCheckMode(0); // Set data rate and transmit power (note: txpow seems to be ignored by the library) LMIC_setDrTxpow(DR_SF7,14); // Start job do_send(&sendjob); } void loop() { os_runloop_once(); } ================================================ FILE: teensy_ttn2/teensy_ttn2.ino ================================================ #include #include #include #include #include #include // the LoRa keys and IDs are secret and should not be disclosed #include "secret.h" //static const PROGMEM u1_t NWKSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u1_t PROGMEM APPSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //static const u4_t DEVADDR = 0x00000000; #define BUTTON_PIN 20 int button = 0; #define LED_R 21 #define LED_G 22 #define LED_B 23 int color = NULL; #define ONE_WIRE_BUS 15 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature dallasTemperature(&oneWire); float temp = 0.0; // These callbacks are only used in over-the-air activation, so they are left empty here (we cannot // leave them out completely unless DISABLE_JOIN is set in config.h, otherwise the linker will complain). void os_getArtEui (u1_t* buf) { } void os_getDevEui (u1_t* buf) { } void os_getDevKey (u1_t* buf) { } #define DATALEN 32 // static uint8_t datatx[] = "Hello, world!"; static uint8_t datatx[DATALEN]; // transmit static uint8_t datarx[DATALEN]; // receive static osjob_t sendjob; static osjob_t recvjob; static osjob_t tempjob; static osjob_t ledjob; // schedule some functions to execute every this many seconds (might become longer due to duty cycle limitations) const unsigned TX_INTERVAL = 10; const unsigned RX_INTERVAL = 2; const unsigned TEMP_INTERVAL = 2; const unsigned LED_INTERVAL = 150; // in miliseconds // Pin mapping const lmic_pinmap lmic_pins = { .nss = 10, .rxtx = LMIC_UNUSED_PIN, .rst = 9, .dio = {2, 5, 6}, }; void onEvent (ev_t ev) { Serial.print(os_getTime()); Serial.print(": "); switch (ev) { case EV_SCAN_TIMEOUT: Serial.println(F("EV_SCAN_TIMEOUT")); break; case EV_BEACON_FOUND: Serial.println(F("EV_BEACON_FOUND")); break; case EV_BEACON_MISSED: Serial.println(F("EV_BEACON_MISSED")); break; case EV_BEACON_TRACKED: Serial.println(F("EV_BEACON_TRACKED")); break; case EV_JOINING: Serial.println(F("EV_JOINING")); break; case EV_JOINED: Serial.println(F("EV_JOINED")); break; case EV_RFU1: Serial.println(F("EV_RFU1")); break; case EV_JOIN_FAILED: Serial.println(F("EV_JOIN_FAILED")); break; case EV_REJOIN_FAILED: Serial.println(F("EV_REJOIN_FAILED")); break; case EV_TXCOMPLETE: Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); if (LMIC.dataLen) { // data received in rx slot after tx Serial.print(F("Data Received: ")); Serial.write(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); Serial.println(); // another scheduled function will deal with the received data bzero(datarx, DATALEN); memcpy(datarx, LMIC.frame + LMIC.dataBeg, min(LMIC.dataLen, DATALEN - 1)); } // schedule next transmission os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(TX_INTERVAL), send_message); break; case EV_LOST_TSYNC: Serial.println(F("EV_LOST_TSYNC")); break; case EV_RESET: Serial.println(F("EV_RESET")); break; case EV_RXCOMPLETE: // data received in ping slot Serial.println(F("EV_RXCOMPLETE")); break; case EV_LINK_DEAD: Serial.println(F("EV_LINK_DEAD")); break; case EV_LINK_ALIVE: Serial.println(F("EV_LINK_ALIVE")); break; default: Serial.println(F("Unknown event")); break; } } void switch_led() { // Serial.print("color = "); // Serial.println(color); digitalWrite(LED_R, LOW); // turn the LED off by making the voltage LOW digitalWrite(LED_G, LOW); // turn the LED off by making the voltage LOW digitalWrite(LED_B, LOW); // turn the LED off by making the voltage LOW switch (color) { case LED_R: digitalWrite(LED_R, HIGH); // turn the LED on (HIGH is the voltage level) break; case LED_G: digitalWrite(LED_G, HIGH); // turn the LED on (HIGH is the voltage level) break; case LED_B: digitalWrite(LED_B, HIGH); // turn the LED on (HIGH is the voltage level) break; default: break; } } void blink_led(osjob_t* j) { switch_led(); if (color) { // schedule the LED to switch off color = NULL; os_setTimedCallback(j, os_getTime() + ms2osticks(LED_INTERVAL), blink_led); } } void update_button() { // this function is triggered by an interrupt delay(50); // debounce if (digitalRead(BUTTON_PIN) == HIGH) { color = LED_B; blink_led(&ledjob); button = 1; Serial.print("button = "); Serial.println(button); } } void receive_message(osjob_t* j) { if (datarx[0]) { Serial.println("message = ["); unsigned int i = 0; while (datarx[i]) Serial.println((int)datarx[i++]); Serial.println("]"); bzero(datarx, DATALEN); // blink color = LED_B; switch_led(); } os_setTimedCallback(j, os_getTime() + sec2osticks(RX_INTERVAL), receive_message); } void update_temp(osjob_t* j) { dallasTemperature.requestTemperatures(); // Send the command to get temperatures temp = dallasTemperature.getTempCByIndex(0); // Get the temperature of the first sensor // Serial.print("temp = "); // Serial.println(temp); // the measurement takes quite some time, hence we blink afterwards (otherwise the blink duration would be incorrect) color = LED_G; blink_led(&ledjob); // schedule the next measurement os_setTimedCallback(j, os_getTime() + sec2osticks(TEMP_INTERVAL), update_temp); } void send_message(osjob_t* j) { color = LED_R; blink_led(&ledjob); // determine whether to send the temperature or the button press if (button) { String str = String("Button pressed"); str.toCharArray((char *)datatx, DATALEN); } else { String str = String("Temperature = "); str += String(temp); str.toCharArray((char *)datatx, DATALEN); } // Check if there is not a current TX/RX job running if (LMIC.opmode & OP_TXRXPEND) { Serial.println(F("OP_TXRXPEND, not sending")); } else { // reset the button status button = 0; // Prepare upstream data transmission at the next possible time. LMIC_setTxData2(1, datatx, sizeof(datatx) - 1, 0); Serial.println(F("Packet queued")); } // Next TX is scheduled after TX_COMPLETE event. } void setup() { // setup the serial port for debugging Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for native USB } Serial.println(F("Starting")); // setup the button pinMode(BUTTON_PIN, INPUT_PULLDOWN); attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), update_button, RISING); // setup the temperature sensor dallasTemperature.begin(); // setup the message content bzero(datatx, DATALEN); bzero(datarx, DATALEN); // setup the RGB led pinMode(LED_R, OUTPUT); pinMode(LED_G, OUTPUT); pinMode(LED_B, OUTPUT); #ifdef VCC_ENABLE // For Pinoccio Scout boards pinMode(VCC_ENABLE, OUTPUT); digitalWrite(VCC_ENABLE, HIGH); delay(1000); #endif // LMIC init os_init(); // Reset the MAC state. Session and pending data transfers will be discarded. LMIC_reset(); // Set static session parameters. Instead of dynamically establishing a session // by joining the network, precomputed session parameters are to be provided. #ifdef PROGMEM // On AVR, these values are stored in flash and only copied to RAM // once. Copy them to a temporary buffer here, LMIC_setSession will // copy them into a buffer of its own again. uint8_t appskey[sizeof(APPSKEY)]; uint8_t nwkskey[sizeof(NWKSKEY)]; memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); #else // If not running an AVR with PROGMEM, just use the arrays directly LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); #endif // Set up the channels used by the Things Network, which corresponds // to the defaults of most gateways. Without this, only three base // channels from the LoRaWAN specification are used, which certainly // works, so it is good for debugging, but can overload those // frequencies, so be sure to configure the full frequency range of // your network here (unless your network autoconfigures them). // Setting up channels should happen after LMIC_setSession, as that // configures the minimal channel set. LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band // TTN defines an additional channel at 869.525Mhz using SF9 for class B // devices' ping slots. LMIC does not have an easy way to define set this // frequency and support for class B is spotty and untested, so this // frequency is not configured here. // Disable link check validation LMIC_setLinkCheckMode(FALSE); // Set adaptive data rate LMIC_setAdrMode(FALSE); // Set data rate and transmit power (note: txpow seems to be ignored by the library) LMIC_setDrTxpow(DR_SF7, 20); // Start the periodic jobs update_temp(&tempjob); send_message(&sendjob); receive_message(&recvjob); } void loop() { // all functionality is implemented in scheduled function calls os_runloop(); } ================================================ FILE: uno_dac7578/blink_led.cpp ================================================ #include "blink_led.h" Ticker blink(ledFlip, 1000, 0, MILLIS); enum { LED_ON, LED_OFF, LED_SLOW, LED_MEDIUM, LED_FAST, } ledState; void ledInit() { pinMode(LED_BUILTIN, OUTPUT); blink.pause(); } void ledOn() { if (ledState != LED_ON) { ledState = LED_ON; blink.pause(); digitalWrite(LED_BUILTIN, LOW); } } void ledOff() { if (ledState != LED_OFF) { ledState = LED_OFF; blink.pause(); digitalWrite(LED_BUILTIN, HIGH); } } void ledSlow() { if (ledState != LED_SLOW) { ledState = LED_SLOW; blink.interval(1000); blink.resume(); } } void ledMedium() { if (ledState != LED_MEDIUM) { ledState = LED_MEDIUM; blink.interval(250); blink.resume(); } } void ledFast() { if (ledState != LED_FAST) { ledState = LED_FAST; blink.interval(100); blink.resume(); } } void ledFlip() { // Invert the current state of the LED_BUILTIN digitalWrite(LED_BUILTIN, !(digitalRead(LED_BUILTIN))); } ================================================ FILE: uno_dac7578/blink_led.h ================================================ #ifndef _BLINK_LED_H_ #define _BLINK_LED_H_ #include #include // see https://github.com/sstaub/Ticker extern Ticker blink; void ledInit(void); void ledOn(void); void ledOff(void); void ledSlow(void); void ledMedium(void); void ledFast(void); void ledFlip(void); #endif ================================================ FILE: uno_dac7578/uno_dac7578.ino ================================================ /* * Sketch for an Arduino Uno or Leonardo connected to two Adafruit DAC7578 modules. The purpose is to * act as a MEG phantom driver, controlling a small current through up to 16 magnetic dipole or * equivalent current dipole sources, thereby generating small magnetic fields that can be recorded * using an OPM or SQUID-based MEG system. * * See * https://store.arduino.cc/products/arduino-uno-rev3-smd * https://store.arduino.cc/products/arduino-leonardo-with-headers * https://learn.adafruit.com/adafruit-dac7578-8-x-channel-12-bit-i2c-dac */ #include #include #include #include "blink_led.h" Adafruit_DACX578 dac0(12); Adafruit_DACX578 dac1(12); Adafruit_DACX578 dac2(12); bool dac0Found = false; bool dac1Found = false; bool dac2Found = false; const uint8_t addr0 = 0x4C; // open pads const uint8_t addr1 = 0x48; // left pads const uint8_t addr2 = 0x4A; // right pads // these will be updated according to the number of DACs found int nchannels = 24; int dacBoard[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 }; int dacChannel[24] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 }; float frequency[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; float phase[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; float freqoffset = 5; float freqstepsize = 0.2; const float offset = 2048; // Offset for the 12-bit DAC output (0 to 4095) const float amplitude = 2047; // Half of the full scale for 12-bit DAC (0 to 4095) const uint32_t sampleTime = 10000; // Approximate time between samples, in microseconds const uint32_t feedbackTime = -1; // Time between serial feedback, in microseconds (-1 for no feedback) void blinkdelay(uint32_t duration) { // wait for some time, but keep the LED blinking uint32_t start = millis(); while ((millis() - start) < duration) { blink.update(); delay(10); } }; void setup() { // blink rapidly, this will continue indefinitely when the setup fails ledInit(); ledFast(); Serial.begin(115200); while (!Serial && (millis() < 3000)) { // wait for the serial interface to start, but not longer than 3 seconds blinkdelay(200); } Serial.println("-------------------------------"); Serial.println("Arduino dac7578 sine wave generator"); Serial.print("Firmware version "); Serial.print(__TIME__); Serial.print(" / "); Serial.println(__DATE__); // initialize the first DAC7578 dac0Found = dac0.begin(addr0, &Wire); long initstart = millis(); while (!dac0Found && (millis() - initstart < 2000)) { blinkdelay(200); dac0Found = dac0.begin(addr0, &Wire); } if (!dac0Found) { Serial.print("Failed to find dac0 at address "); Serial.println(addr0, HEX); } else { Serial.print("Initialized dac0 at address "); Serial.println(addr0, HEX); } // initialize the second DACC7578 dac1Found = dac1.begin(addr1, &Wire); initstart = millis(); while (!dac1Found && (millis() - initstart < 2000)) { blinkdelay(200); dac1Found = dac1.begin(addr1, &Wire); } if (!dac1Found) { Serial.print("Failed to find dac1 at address "); Serial.println(addr1, HEX); } else { Serial.print("Initialized dac1 at address "); Serial.println(addr1, HEX); } // initialize the third DACC7578 dac2Found = dac2.begin(addr2, &Wire); initstart = millis(); while (!dac2Found && (millis() - initstart < 2000)) { blinkdelay(200); dac2Found = dac2.begin(addr2, &Wire); } if (!dac2Found) { Serial.print("Failed to find dac2 at address "); Serial.println(addr2, HEX); } else { Serial.print("Initialized dac2 at address "); Serial.println(addr2, HEX); } // update the channel-to-board mapping for (uint8_t channel = 0; channel < 24; channel++) { // shift each channel to the next board if the current board is not found if (!dac0Found && dacBoard[channel] == 0) { dacBoard[channel]++; } if (!dac1Found && dacBoard[channel] == 1) { dacBoard[channel]++; } if (!dac2Found && dacBoard[channel] == 2) { dacBoard[channel]++; } } // update the number of channels according to the number of DAC boards that were found nchannels = 8 * dac0Found + 8 * dac1Found + 8 * dac2Found; // set the frequency for each channel for (uint8_t channel = 0; channel < nchannels; channel++) { frequency[channel] = freqoffset + freqstepsize * channel; Serial.print("channel "); Serial.print(channel + 1); Serial.print(", board "); Serial.print(dacBoard[channel] + 1); Serial.print(", frequency "); Serial.print(frequency[channel]); Serial.println(" Hz"); } // The Arduino Leonardo (based on the ATmega32u4) defaults to an I2C clock speed of 100 kHz and supports a maximum clock speed of 400 kHz Wire.setClock(400000); // blink slowly for the rest of the time if (nchannels > 0) { ledSlow(); } } // setup void loop() { static uint32_t lastSample = micros(); static uint32_t lastFeedback = micros(); // this is needed to keep the LED blinking blink.update(); uint32_t currentTime = micros(); uint32_t deltaTime = currentTime - lastSample; if (deltaTime >= sampleTime) { for (uint8_t channel = 0; channel < nchannels; channel++) { // increment the phase according to the channels-specific frequency phase[channel] += 2.0 * M_PI * frequency[channel] * ((float)deltaTime / 1000000); if (phase[channel] >= 2.0 * M_PI) { phase[channel] -= 2.0 * M_PI; } // compute the new value float sineValue = offset + amplitude * cos(phase[channel]); // write the value to the corresponding channel of the corresponding dacBoard if (dacBoard[channel] == 0) { dac0.writeAndUpdateChannelValue(dacChannel[channel], (uint16_t)sineValue); } else if (dacBoard[channel] == 1) { dac1.writeAndUpdateChannelValue(dacChannel[channel], (uint16_t)sineValue); } else if (dacBoard[channel] == 2) { dac2.writeAndUpdateChannelValue(dacChannel[channel], (uint16_t)sineValue); } } lastSample = currentTime; if ((currentTime - lastFeedback) > feedbackTime) { Serial.println(deltaTime); lastFeedback = currentTime; } } // if deltaTime } // loop