Arduino/HW

Hardware

This page lists or contains several projects I realized using small devices such as RPi, Arduino and other micro-controllers or just plain (simple) HW.

Several (most) projects are dedicated to controlling some aspects of the X32 digital mixing console I own, and are listed below, with a link to their description, source code and implementation example.

Other projects are just for fun or because people asked me to help with implementing some of their ideas or needs.

In all cases, source code is provided, and in many cases, I provide project construction example, but you are of course welcome to use you imagination to make your own version.

©Patrick-Gilles Maillot - donnations accepted: paypal.me/PMaillot 

X32 Deck Lamp

I wanted to install a light for the X32 Deck, but did not find the one I was looking for at local places; and the ones I found on the internet seemed a little too expensive for what they seemed to be... so I looked for an alternative and found this LED (3W) desk light at Ikea. Cheap enough to play with and with enough goose neck length (2 feet / 60cm) to go with the X32. I also liked the smaller diameter (about 8mm) of the spring, much lighter than the XLR goose neck flexes I ordered on the internet.

Home with my LED lamp, I realized it works on 3.3 Volts when the X32 provides 12 Volts! :-<

I still looked for and ordered and 4 pins XLR to see it it would fit at the other end of the light. Good (almost) fit. Once I removed (using a Dremel tool) the plastic ending, I was left with a 9.5mm diameter fileted tube. The Neutrik 4-pin XLR top can take this, with the little help of a drill bit. The two (XLR top and lamp footing) are now secured with 2component epoxy glue.

I ordered a small 3W LED lamp that worked on 12V. When it arrived, I found it was connected to a buck power converter (12Vac->whatever volt) that fit on a tiny PCB, but still too big to go either in the top part of the lamp or in the XLR itself. Fortunately that PCB had extra components I did not need, I removed the 4 diodes that are used to convert the AC to 12V DC before the power converter. I also cut the PCB to its minimal size (a mere 12x8 mm), I also removed the XLR pins parts that are used for soldering the wires so they became flush with the XLR plug itself, freeing room inside the XLR cavity.

With flex wires in place, the PCB fit (barely) in the top cavity of the XLR, and I could screw back the XLR jack/pins in place after testing and making sure there were no shorts.

I'm quite happy with result!, a lightweight desk lamp that accepts 4 different useful levels of intensity (not too bad for a buck power converter) giving some dimming function.

And because it's really lightweight, it won't put too much stress on the X32 lamp XLR connector.

Overall cost: 12€ for the Ikea lamp, 4.5€ for the 4pin Neutrik XLR, and 3.2€ for the 12V LED bulb and its converter. And 3h or work.

Note: I noted the X32 when being switched OFF, will send a 12V pulse to the lamp, even if the lamp setting is on OFF. This means it's better to remove the lamp BEFORE switching OFF the console, as it's highly recommended to disconnect output XLRs or switch OFF speakers before switching OFF the console. Nothing bad, but a little annoying.

MIDI stomp box

MIDI stomp box for controlling X32 parameters

There's been a few demands in the X32 and XAir forums for a way to control some parameters using a stomp box (a simple foot pedal) and possible MIDI as a way to send information. I have been looking at different options and decided to use one of these tiny Arduino devices; In my case I opted for a cheap and simple, yet USB programmable Arduino Leonardo device. This is a Chinese made board, using an Atmega32U4 chip at 16MHz, equivalent to the SparkFun pro-micro board - (board Schematics).

After installing the necessary software to play/program the board, I looked at the few examples available and used the MIDI example program as a basis for the job. Before programing, I wanted to ensure the board would work as a pedal, on battery; Typical batteries used in foot pedals are 9V, and powering a 5V board from 9V is possible on the pro-micro but that would mead using the on-board LDO, which would waist in heat about 1/2 of the battery power in going from 9V to 5V. A better option was to use a buck DC/DC converter with an efficiency above 85%. Again a small Chinese made board was used for that. The battery itself is a rechargeable one, with a capacity of 800mA; quite enough for the job.

By the way the pedal works fine when USB is plugged in (no need for a battery in that case).

As this is a pedal board, I have decided to 3D print the pedal enclosure, went through one or two prototypes, and came out with a simple model, using the online TinkerCad software (main box, bottom, battery door).

Back to software:

The source-code is quite simple and presented below; In my case, I want to send SYSEX messages each time I release the foot switch, and I want a LED to blink when the SYSEX message is sent to MIDI out.

MIDI stomp box Source Code

/*  Creation: 11 Dec 2016  ©-2017 Patrick-Gilles Maillot    MIDI-X32: a Stomp pedal for sending SYSEX MIDI messages to an X32 digital mixer from Behringer  (or any other MIDI devive).
  The program waits for a HW pin to becon=me active (when the user presses a switch). This event  is then used to generate the sending of the SYSEX MIDI array. 
  This project started from an Arduino project/sketch file: MIDI note player / created 13 Jun 2006
  The circuit for MIDI out:    Leonardo digital pin 1 connected to MIDI jack pin 5    MIDI jack pin 2 connected to ground    MIDI jack pin 4 connected to +5V through 220-ohm resistor  Attach a MIDI cable to the jack, then to a MIDI synth, and play music.
  The circuit for the switch:    Pushbutton attached to Leonardo digital pin pin 2 and to ground*/
unsigned char S_array[] = {                         // MIDI SYSEX or command array data  0xF0, 0x7F, 0x02, 0x06, 0x02, 0xF7};//const int S_size = sizeof(S_array) / sizeof(char);  // MIDI SYSEX or command array sizeconst int B_pin = 2;                                // Switch pin (in)const int L_pin = 7;                               // LED pin (out)int       D_delay = 100;                             // Debounce time; increase if neededint       S_state;                                  // state of the switchint       L_state;                                  // state of the LED

void setup() {  // called once at program start  //  Set MIDI baud rate:  Serial1.begin(31250);  // Set pins directions  pinMode(B_pin, INPUT_PULLUP);  pinMode(L_pin, OUTPUT);  S_state = digitalRead(B_pin);  digitalWrite(L_pin, HIGH);}
void loop() {  // start main loop  // read the state of the switch, check for state change:  S_state = digitalRead(B_pin);  // wait a few milliseconds  delay(D_delay);  // stop LED if needed  if (L_state) {    digitalWrite(L_pin, HIGH);    L_state = 0;  }
  // only send MIDI data if the new button state is LOW  if ((digitalRead(B_pin) == 1) && (S_state == 0)) {    // LED on    digitalWrite(L_pin, LOW);    L_state = 1;    // play all bytes from SYSEX data array:    for (int i = 0; i < S_size; i ++) {      Serial1.write(S_array[i]);    }  } else {    // wait a little to save CPU power    delay(10);  }}

To program a different SYSEX sequence, just connect the pedal to USB on a PC and flash the program with a different SYSEX byte array.

It is also quite simple to add a choice of SYSEX strings if one decides to have several strings selected based on a switch or a selector that would be read prior the foot switch being activated.

Below some pictures of the project, and a small video of the system is also available, visit this Youtube link.

Components used
The 3D printed box
USB powered
All wired (battery removed)
The wiring I used

X32 MIDI Tap Tempo

X32 MIDI Tap Tempo Stompbox

Using the stompbox described above as Tap Tempo for X32 or Xair devices:

This is quite easy: Just replace the software :-).

There will be two foot switch press actions required: The first one to initialize a timer, and the second one to get the time difference between the two consecutive switch actions.

This time delta is used as a value sent part of a SYSEX MIDI data that contains the OSC command for setting the delay value of an existing effect in X32 or XAir.

A small video illustrates the tap tempo function, how it changes X32 data for delay effects, and the program source is shown below:

X32 MIDI Tap Tempo Source Code

/*  Creation: 5 Aug 2017  ©-2017 Patrick-Gilles Maillot    TtapMIDI-X32: a Stomp pedal for sending Tempo TAP delay as SYSEX MIDI messages to an X32 digital mixer from  Behringer (or any compatible MIDI devive).
  The program waits for a HW pin to becon=me active (when the user presses a switch). These events  are used to generate a delay sent as SYSEX MIDI array to the connected device. 
  This project started from an Arduino project/sketch file: MIDI note player / created 13 Jun 2006
  The circuit for MIDI out:    Leonardo digital pin 1 connected to MIDI jack pin 5    MIDI jack pin 2 connected to ground    MIDI jack pin 4 connected to +5V through 220-ohm resistor  Attach a MIDI cable to the jack, then to a MIDI synth, and play music.
  The circuit for the switch:    Pushbutton attached to Leonardo digital pin pin 2 and to ground*/
unsigned char S_array[] = {                         // MIDI SYSEX or command array data  0xF0, 0x00, 0x20, 0x32, 0x32,   0x2F, 0x66, 0x78, 0x2F,  0x30,  0x2F, 0x70, 0x61, 0x72, 0x2F,   0x30, 0x32,  0x20,  0x33, 0x30, 0x30, 0x30,  0xF7};//const int     S_size = sizeof(S_array) / sizeof(char);  // MIDI SYSEX or command array sizeconst int     B_pin = 2;                                // Switch pin (in)const int     L_pin = 7;                                // LED pin (out)int           D_delay = 20;                             // Debounce time; increase if neededint           S_state;                                  // state of the switchint           L_state;                                  // state of the LEDunsigned long Time1;                                    // Start timeunsigned long Dtime;                                    // Delta timeint           Tcount;                                   // Indicate first or secont press for timer calculation
void setup() {  // called once at program start  //  Set MIDI baud rate:  Serial1.begin(31250);  // Set pins directions  pinMode(B_pin, INPUT_PULLUP);  pinMode(L_pin, OUTPUT);  S_state = digitalRead(B_pin);  digitalWrite(L_pin, HIGH);  Time1 = Dtime = Tcount = 0;}
void loop() {  // start main loop  // read the state of the switch, check for state change:  S_state = digitalRead(B_pin);  // wait a few milliseconds  delay(D_delay);  // stop LED if needed  if (L_state) {    digitalWrite(L_pin, HIGH);    L_state = 0;  }  //  // only send MIDI data if the new button state is LOW  if ((digitalRead(B_pin) == 1) && (S_state == 0)) {    if (Tcount == 0) {      // first time press: Get time      Time1 = millis();      Tcount = 1;    } else {      // second time press: calculate time difference and send data      // calculate Dtime in milliseconds      Dtime = millis() - Time1;      // Dtime as valid X32 data 0 to 3000 ms      if (Dtime < 1) Dtime = 0;      if (Dtime > 3000) Dtime = 3000;      // set value of FX number in SYSEX buffer      S_array[9] = 0x32;      // set value of Ftime into SYSEX buffer      S_array[18] = 0x30 + Dtime / 1000;      S_array[19] = 0x30 + (Dtime - (Dtime/1000) * 1000) / 100;      S_array[20] = 0x30 + (Dtime - (Dtime/100) * 100) / 10;      S_array[21] = 0x30 + (Dtime - (Dtime/10) * 10);      // reset time press counter      Tcount = 0;      // play all bytes from SYSEX data array:      for (int i = 0; i < S_size; i ++) {        Serial1.write(S_array[i]);      }      // LED on      digitalWrite(L_pin, LOW);      L_state = 1;    }  }}

Changing the FX number for X32 or Xair is easy: just replace the byte data value assigned to S_array[9]. The default/arbitrary value is FX #2, or 0x32. Use 0x31 for FX #1, 0x33 for FX #3, etc.

Usage: It's a matter of taste, but it is also possible to generate a new Dtime value at each press of the footswitch.

To achieve this, just delete the Tcount variable, its initialization in the setup()section and the if statement that uses it.

Within the loop() section, replace the line Tcount = 0; by Time1 = millis();

This way the first press will typically be bogus or greater than 3 seconds, but each subsequent press will provide a valid tap tempo value corresponding to your foot rythm.

X32 Encoder

X32 audio scrub/jog and transport for X-Live

Spot the difference?

Replacing the X32 "phone holder" by a Transport and Audio scrub/jog control device for X-Live

I had in mind for a long time the idea of removing the useless phone holder and use that space for additional X32 Controls. But what to choose or implement?

I was glad Behringer finally came out with the XLive board, adding a really great feature and a new set of capabilities to the desk.

This was the trigger for starting [at last] this project. My goal is to provide a set of programmable keys, pretty much like the User Assign Section, except I want to be able set my own colors! :) I also want it to provide a transport section for the XLive board, something I tested with a small Windows application (see X32Jog4Xlive above) and showing it can be done.

I kept a jog/shuttle encode I savaged from a 15 years old Philips VCR remote control. Turns out this is an ALPS encoder such as this one (below) still available from Aliexpress, but other similar devices can be adapted too, such as this one I will try too.

This device combines two functions:

Shuttle (to perform audio scrubbing)

-1- it provides an encoder which gives CW and CCW rotation information from the centre axis

Jog (to set specific action in addition to shuttle)

-2- it provides an indication of the position of the outer ring axis, coded on 5 bits (+/- 15 positions plus center), which is mounted on a spring so it automatically returns to its center position

I started to work on the actual device after ordering a WIFI enable/capable Arduino (a device known as NodeMCU V3, that combines the ease of use of Arduino and an ESP8266 WIFI device), as shown below. Such device is typically used in IoT developments.

The characteristics are impressive for such a small device:

16MB flash, 80MHz (or 160MHz) clock, 32bits processor, 17 GPIOs, enough RAM and... integrated WIFI.

It also comes with the ease of use of the Arduino ecosystem, which makes it (very) easy to use for someone who codes in C :).

My idea of the device is therefore a combination of the Jog/Shuttle encoder, the nodeMCU processor, and a series of 8 push buttons I want to be able to program actions for and assign colors to. My idea for this was initially to find buttons with incorporated LEDs. I did find some, but they came with several issues: each color was fixed (and therefore not programmable), and they would require more electronics and IO pins I would have at hand after using some GPIOs for the jog/shuttle and the keypad itself. The 8 separate keys also were a problem by themselves, as I would have to use a de-multiplexer to limit the number of IOs.

I went for a one-wire, analog keypad approach which is documented in several web pages, and there's a dedicated library to manage the resulting device. It is connected to the analog input which I didn't use, so this is a good thing as it doesn't cost me any GPIOs. I've had trouble with the onewirekeypad library, and couldn't get a reliable keypad to work (i.e. just repetitively give me the key I pressed) and ended up writing my own small key selection routine based on the analog voltage I measured. The rest of the library is actually quite good at de-bouncing the signal and managing key events, so I'm keeping this part!

As for the LEDs, I am using WS2811 based chips/modules that are commonly found in LED strips where each LED can be programmed individually. Again, the Arduino ecosystem is great as there are libraries to manage so-called neopixels. In my case I just need 8 of them!. I got single chip devices from Aliexpress I then cut to their strict minimum size and wired together as a string of 8 LEDs. As a result, a single GPIO is used to control the 8 LEDs, providing the capability to animate, change color and intensity of any of the LEDs, using the FastLed library.

Next was to figure out how to combine keys and LEDs so the color of LEDs would be visible with the keys. IF properly isolated, each LED could light its respective key, as long as there is transparent material to let the light shine through. I found these simple and cheap keys that would fit perfect for what I wanted. The plastic enclosure would have to be "special" in order to isolate each key and its associated LED from the other.

The enclosure I designed is made of two parts (bottom and top). The bottom (V2) or (V3) is quite simple and secures the nodeMCU processor board so its power socket can be plugged in with no problems. The top (V2) or (V3) part is more complex as it must: -1- support the keypad and LEDs, properly isolated, -2- support the ALPS jog/shuttle, -3- be low enough to still fit under the Desksaver protective hardcover (a must for X32 protection). I tested several prototypes before arriving at at design that agrees with the constraints above.

The difference between V2 and V3 is the thickness of the enclosure;2mm and 1.5mm respectively. Pictures here are from V2 enclosure.

General view of the enclosure
The nodeMCU module secured in place in the bottom part of the enclosure
Securing the Jog/Shuttle with the top
Preparing LEDs from single modules; but another solution is worth trying too; order placed... waiting for the chips to arrive (chose the IP30, 144LED/m type)
Keypad with LEDs
Testing LED lighting through the Keypad key caps, animated-1, animated-2, animated-3.I still need to add pictograms between the keys and their clear caps!

Prototype

Below are a few pictures of the current prototype; I am still moderately happy with the analog keypad which is a little capricious sometimes: I probably need to adjust the 5 resistor values to better distribute the analog output of each key over the 3.3V supply.

Last Checks before closing the device
The finished prototype still fits under a Decksaver! (V3 version of the enclosure makes this even easier)
Fully attached and communicating with X32; Note the USB power cable on the right.

The hardware is quite simple and a schematic file is available from my website.

Time to write and test some software :) I selected the following functionality for the 8 keys of the device's keypad:

Depending on the state of key #1, the device is in audio scrub mode or wind mode

As the device is WIFI connected to the X32, it is possible to get X32 OSC notifications, while this complicates SW development, it enables for example to manage the device's LEDs brightness in sync with the X32 one as the user rotates the brightness knob. This is shown in a short video: animated-3.

The software uses number of libraries: WiFiUdp.h, OnewireKeypad.h, Encoder.h, and FastLED.h.

The source code for the prototype is given below. Make sure you set your WIFI SSID, password and the X32 IP address in their respective variables.

X32Encoder Source Code

/////////////////////////////////////////////////////////////////////////////////////////////////                                    X32Encoder://                            ©2018 Patrck-Gilles Maillot/////////////////////////////////////////////////////////////////////////////////////////////////// This project aims at providing a JOG/SHUTTLE encoder device to supplement the X32/M32// family of digital audio mixers, especially when equiped with th X-Live extension board// which provides 32 channels digital recording capabilities.//// The proposed device includes a central encode for audio scrubbing; an outer multiposition// jog enables selecting 32 different positions around two center values. The devices also// includes 8 separate keys with individual color LEDs// Each Key can set a specific function either for changing functionality of the device or// can be used for sending a specific OSC command to the X32/M32.//// The device connects to X32/M32 using WIFI, and continuously accepts X32/M32 OSC data. It// is built around a NodeMCU/ESP8266 Arduino device.////// In the functions below, a lot of calls to Serial.printf() have been left (commented) so// one can follow what happens or debug the code when performing changes (once uncommented)//// History: Ver 0.1 - initial release June 9th 2018////// Note://   Make sure you set WIFI setup values correctly below (ssid, paswword, etc.)///////////////////////////////////////////////////////////////////////////////////////////////////// The Keypad://     key 1 - toggle: sets the device in audio scrubb or standard shuttle mode (default)//     key 2 - press:  sends STOP//     key 3 - press:  sends PLAY/PAUSE//     key 4 - press:  sends RECORD////     key 5 - press:  sends Add Marker//     key 6 - press:  sends Delete Marker//     key 7 - toggle:  No-Op//     key 8 - press:  jump to SETUP/CARD screen on X32/M32////// This program runs and has been tested on a nodeMCU/ESP8266 v3 device//// [GP]IOs used:// A0: Keypress (using analog IO)// D3: LEDs Control// D1, D2: Inner rotary encoder used for scrubbing// D7, D6, D5, D9: Outer encoder used for shuttle// D4 (board blue LED)// D8 (pull down only)// D10 (TX)/////////////////////////////////////////////////////////////////////////////////////////////////// Wifi#include <ESP8266WiFi.h>#include <WiFiUdp.h>//const char* ssid = "xxxxxxxxx";const char* password = "xxxxxxxxx";const char* X32IP = "192.168.1.62";/////////////////////////////////////////////////////////////////////////////////////////////////// Keypad: We use the OnewireKeypad design, but with a simplified way of reading our 8 keys// Indeed I never got the library to return consistent data, compared to my simple test routine.// Keypad has 8 keys, and are read as a one wire analog keypad.#include <OnewireKeypad.h>// Define Library ://  The library is defined with following two commands, just follow the formula in this//  comment and you are good to go.//  OnewireKeypad//  Keypad(Serial, Char values, #Rows, #Cols, Arduino Pin, Row_resistor, Columns_resistor)char KEYS[] = { };                    // empty array as we use our own way of identifying keysint KeySate[8];                       // array to keep keys states#define KEY_HOLD_Time         100     // time to consider key held down#define KEY_BOUNCE_Time       400     // debounce time for keypad keysOnewireKeypad <Print, 8> Keypad(Serial, KEYS, 4, 2, A0, 2200, 680);/////////////////////////////////////////////////////////////////////////////////////////////////// Macro to send UDP messages#define send_udp(message, len) \  Udp.beginPacket(X32IP, 10023); \  Udp.write(message, len); \  Udp.endPacket();//// Print a float with a 2 digits precision;char str[16];#define flt(f) \  sprintf(str, "%d.%02d", (int)f, (int)(f < 0.0 ? -1 : 1) * (int)(f*100)%100);/////////////////////////////////////////////////////////////////////////////////////////////////// WIFI and X32 communications related dataWiFiUDP Udp;unsigned int localUdpPort = 10023;        // X32 port to listen onchar r_buf[255];                          // buffer for incoming X32/M32 OSC packetschar s_buf[128];                          // buffer for sending OSC packetsint  s_len, r_len;                        // send & receive buffer data lengthschar s_info[8] = "/info";                 // info bufferchar s_remt[12] = "/xremote";             // remote bufferchar s_ustate[20] = "/-stat/urec/state";  // get X-Live statechar s_uetime[20] = "/-stat/urec/etime";  // get X-Live positionchar s_uempos[20] = "/-urec/markerpos";   // get X-Live marker current indexunion {  float ff;  int   ii;  char  cc[4];} endian;                                 // used for endian conversionsunsigned long XremoteMillis;              // timer for /xremote operationint STOP = 0;int PAUSE = 1;int ONE = 1;int PLAY = 2;int RECORD = 3;int SETUP = 3;int CARD = 6;/////////////////////////////////////////////////////////////////////////////////////////////////// Encoder requires low latency interrupt response.  Available CPU// time does NOT necessarily prove or guarantee correct performance.// If another library, like NewSoftSerial, is disabling interrupts// for lengthy periods of time, Encoder can be prevented from// properly counting the intput signals while interrupt are disabled.//// This optional setting causes Encoder to use more optimized code,// but the downside is a conflict if any other part of your sketch// or any other library you're using requires attachInterrupt().// It must be defined before Encoder.h is included.//#define ENCODER_DO_NOT_USE_INTERRUPTS#define ENCODER_OPTIMIZE_INTERRUPTS#include <Encoder.h>//// Encoder Pinout:// Pin 1: D1 / GPIO 5// Pin 2: Ground// Pin 3: D2 / GPIO 4Encoder myEnc( D1, D2); // this is the inner part of the encoder, used for audio scrubbinglong oldPosition = 0;   // old and new positionslong newPosition;// Encoder (outer ring) settings:// SH_table is used for the forward or backward displacement in the audio file when audio // scrubbing is OFF// SC_table is used to define the acceleration factor given to each step of audio scrubbing // (when ON)// rotation index [-5, 5, -4, 4, -2, 2, -3, 3, -6, 6, -7, 7, -1, 1, center-, center+};int SH_table[16] = { -30000, 30000, -10000, 10000, -2000, 2000, -5000, 5000, -60000, 60000,                      -300000, 300000, -1000, 1000, 0, 0 };float SC_table[16] = { 1. / 6, 32.0, 1. / 5, 16.0, 1. / 3, 4.0, 1. / 4, 8.0, 1. / 7, 64.0,                        1. / 8, 128.0, 1. / 2, 2.0, 1.0, 1.0 };#define PAUSE_DTime     25  // the delta_time (per encoder tick) in ms when in PAUSE#define PLAY_DTime    1000  // the delta_time (per encoder tick) in ms when in PLAY/////////////////////////////////////////////////////////////////////////////////////////////////#define FASTLED_INTERNAL#include <FastLED.h>// LEDs// LED control on D3 / GPIO 0// color codes for HSV can be found at https://github.com/FastLED/FastLED/wiki/Pixel-reference#chsv// S: typicall 255; Avoid setting a V value above 128 (1/2 full) as it consumes a lot of power#define NUM_LEDS 8          // we have 8 keys -> each with their own LED#define DATA_PIN 3          // LED control will be driven from Data pin 3CRGB KeyLeds[NUM_LEDS];     // LED data arrayint Cleds[NUM_LEDS];        // used to store the color (Hue value) assigned to each LEDint V_value;                // used to set/update the max luminosity, according to X32 settings/////////////////////////////////////////////////////////////////////////////////////////////////////                         ------------- Local Routines -------------///////////////////////////////////////////////////////////////////////////////////////////////////// XE_getk(): read keypad (analog IO) after debouncing has been validated by onwirekeypad.h// functionality; the actual values set here depend on the resistors (and their precision) used// in the keypad; Adjustments of the values in the tests below may be needed.int XE_getk() {  int k;  if ((k = Keypad.Key_State() == 3)) {    // not pressed = 0, pressed = 1, released = 2,  held = 3    k = analogRead(A0);//    Serial.printf("analog key: %d\n", k);    if (k > 800)      return 52;   // '4'    if (k > 400)      return 56;   // '8'    if (k > 220)      return 51;   // '3'    if (k > 160)      return 55;   // '7'    if (k > 135)      return 50;   // '2'    if (k > 120)      return 54;   // '6'    if (k > 99)      return 49;    // '1'    if (k > 20)      return 53;    // '5'  }  return 0;             // no char}/////////////////////////////////////////////////////////////////////////////////////////////////// Xsprint(): build UDP packet buffer with OSC data, returns the current/resulting// buffer lengthint Xsprint(char *bd, int index, char format, char* bs) {  int i;  // check format  switch (format) {  case 's':    // string : copy characters one at a time until a 0 is found    if (bs) {      strcpy(bd + index, bs);      index += (int) strlen(bs) + 1;    } else {      bd[index++] = 0;    }    // align to 4 bytes boundary if needed    while (index & 3) bd[index++] = 0;    break;  case 'f':  case 'i':    // float or int : copy the 4 bytes of float or int in big-endian order    i = 4;    while (i > 0) bd[index++] = (char) (((char*) bs)[--i]);    break;  default:    // don't copy anything    break;  }  return index;}/////////////////////////////////////////////////////////////////////////////////////////////////// Xfprint(): build UDP packet buffer with OSC data including a specific text,// returns the current/resulting buffer lengthint Xfprint(char *bd, int index, char* text, char format, char* bs) {  // first copy text  strcpy(bd + index, text);  index += (int) strlen(text) + 1;  // align to 4 bytes boundary if needed  while (index & 3) bd[index++] = 0;  // then set format, keeping #4 alignment  bd[index++] = ',';  bd[index++] = format;  bd[index++] = 0;  bd[index++] = 0;  // based on format, set value  return Xsprint(bd, index, format, bs);}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_scrub_action(): Perform audio scrubbing on X-Live:// get the current position and card state. Based on play or pause, set a delta that will// be applied, after a multiplication factor is applied based on the outer ring position// represented by the parameter passed to the function// The scrub action is defined by a change in position of the rotary encoder (inner one)void XE_scrub_action(int k) {  int len, delta_time, numdo1, numdo2;  int i, l;  //  // Scrub action?  newPosition = myEnc.read();//  Serial.printf("KeySate[0]: %d\n", KeySate[0]);  if (newPosition != oldPosition) {//    Serial.printf("position = %d\n", newPosition);    // The scrub wheel has been moved...    send_udp(s_ustate, 20);    numdo1 = 0;    do { // Receive incoming UDP packets: interested in ...state...      numdo1 += 1;      if (r_len = Udp.parsePacket()) {        len = Udp.read(r_buf, 255);//        Serial.printf("r_len scrub len = %d\n", len);        if (strcmp(r_buf, s_ustate) == 0) {//          Serial.printf("got state\n");          // Only interested in the case where status is play or ppause          for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);          if (endian.ii & 3) {            // Set delta_time according to play state            delta_time = PAUSE_DTime;                     // pause            if (endian.ii & 2) delta_time = PLAY_DTime;   // play            // Apply Shuffle data to enable 1ms to 150 min precision!//            flt(SC_table[s_i]); Serial.printf("jog index = %d, factor: %s\n", k, str);            delta_time = (int) ((float) delta_time * SC_table[k]);//            flt(delta_time); Serial.printf("delta_time: %s, ", str);            // We're in play or ppause, let's read our current position            send_udp(s_uetime, 20);            numdo2 = 0;            do { // Receive incoming UDP packets: interested in ...etime...              numdo2 += 1;              if (r_len = Udp.parsePacket()) {                len = Udp.read(r_buf, 255);                if (strcmp(r_buf, s_uetime) == 0) {                  // Read data from X32 into etim                  for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);                  // Move FW or BW depending on encoder movements                  if (newPosition < oldPosition) {                    endian.ii -= delta_time;          // Scrub backwards                    if (endian.ii < 0) endian.ii = 0; // Ensure we can get to 00:00:00:00                  } else if (newPosition > oldPosition) {                    endian.ii += delta_time;          // Scrub forward                  }                  endian.ii += 1;                  // Set new time/position                  s_len = Xfprint(s_buf, 0, (char *)"/-action/setposition", 'i', endian.cc);                  send_udp(s_buf, s_len);                  oldPosition = newPosition;                  return;                } else {                  r_len = 0;                }              }            } while ((r_len == 0) && (numdo2 < 20));          }        } else {          r_len = 0;        }      }    } while ((r_len == 0) && (numdo1 < 20));  }  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_schuttle_action(): apply a FF or RW to X-Live session while in pause mode (only),// based on the amount of twist applied to the shuttle (outer ring of the encoder).// The amount is contained in SH_table, and is set based on the parameter passed to// the function.void XE_schuttle_action(int k) {  int len, numdo1, numdo2;  int i, l;//  // The shuttle wheel has been moved...  send_udp(s_ustate, 20);//  Serial.printf("Shuttle\n");  numdo1 = 0;  do { // Receive incoming UDP packets: interested in ...state...    numdo1 += 1;    r_len = Udp.parsePacket();//    Serial.printf("Shuttle =  %d\n", r_len);    if (r_len > 0) {      len = Udp.read(r_buf, 255);//      Serial.printf("Shuttle len = %d\n", len);      if (strcmp(r_buf, s_ustate) == 0) {        // Only interested in the case where status is play or ppause        for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);        if (endian.ii & 3) {          // Set delta_time according to shuttle position          if (endian.ii == 1) {      // only valid in pause;//            flt(delta_time); Serial.printf("delta_time: %s, ", str);            // We're in ppause, let's read our current position            send_udp(s_uetime, 20);            numdo2 = 0;            do { // Receive incoming UDP packets: interested in ...etime...              numdo2 += 1;              if (r_len = Udp.parsePacket()) {                len = Udp.read(r_buf, 255);                if (strcmp(r_buf, s_uetime) == 0) {                  // Read data from X32 into etim                  for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);                  // Move FW or BW depending on shuttle data                  endian.ii += SH_table[k];                  endian.ii += 1;                  // Set new time/position                  s_len = Xfprint(s_buf, 0, (char *)"/-action/setposition", 'i', endian.cc);                  send_udp(s_buf, s_len);                  return;                } else {                  r_len = 0;                }              }            } while ((r_len == 0) && (numdo2 < 20));          }        }      } else {        r_len = 0;      }    }  } while ((r_len == 0) && (numdo1 < 20));  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_osc_check(): This function is called to check/test X32 generated OSC commands. This// enables syncing with X32 events (such as LED brightness below as a first option.void XE_osc_check() {  int len, i, l;  //  // check for general X32 messages we can be interested in  if (r_len = Udp.parsePacket()) {//    Serial.printf("r_len light =  %d\n", r_len);    len = Udp.read(r_buf, 255);    if (strcmp(r_buf, "/-prefs/ledbright") == 0) {      // got a change brightness command, update V_value accordingly      //      for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);      V_value = 32 + (int) (endian.ff * 164);      for (i = 0; i < NUM_LEDS; i++) {        if (KeySate[i] == 1) {          KeyLeds[i] = CHSV(Cleds[i], 255, V_value);//          Serial.printf("Key %d, bright = %d\n", i, V_value);        } else {          KeyLeds[i] = CHSV(Cleds[i], 255, 0);        }      }      FastLED.show();     // update LEDs    } else    //    // check for Transport commands from X32 on X-Live, update X32Encoder state acordingly    if(strcmp(r_buf, "/-stat/urec/state") == 0) {      for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);      switch(endian.ii) {        case 0: // STOP          XE_X32Xlive_STOP(0);          break;        case 1: // PAUSE          XE_X32Xlive_PAUSE();          break;        case 2: // PLAY          XE_X32Xlive_PLAY;          break;        case 3: // REC          XE_X32Xlive_REC(0);          break;      }    }  }  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_del_marker(): Delete X-Live Marker at the current X-Live card marker positionvoid XE_del_marker() {  int i, l, len;  //  // Request current marker position...  send_udp(s_uempos, 20);//  Serial.printf("Marker index %s\n", s_uempos);  do { // Receive incoming UDP packets: interested in ...markerpos...    r_len = Udp.parsePacket();    if (r_len > 0) {      len = Udp.read(r_buf, 255);//      Serial.printf("rec len = %d\n", len);      if (strcmp(r_buf, s_uempos) == 0) {        for (i = 4, l = 24; i > 0; l++) endian.cc[--i] = *(r_buf + l);        // Only interested in "/-urec/markerpos" messages//        Serial.printf("Marker index = %d\n", endian.ii);        if (endian.ii > 0) {          s_len = Xfprint(s_buf, 0, (char *)"/-action/delmarker", 'i', endian.cc);          send_udp(s_buf, s_len);        }        return;      } else {        r_len = 0;      }    }  } while (r_len == 0);  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_key_toggle(): manage Key toggle states (for those keys you decide to set as toggle// versus push.void XE_key_toggle(int i) {  KeySate[i] ^= 1;       // change state  // assign color and light  if (KeySate[i] == 1) {    KeyLeds[i] = CHSV(Cleds[i], 255, V_value);  } else {    KeyLeds[i] = CHSV(Cleds[i], 255, 0);  }  FastLED.show();   // update LEDs  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_key_blink(): manage Key blink (for those keys you decide to use as push// versus toggle.void XE_key_blink(int i) {  // no change in key state  // assign color and light  KeyLeds[i] = CHSV(Cleds[i], 255, V_value);  FastLED.show();   // update LEDs  delay (200);  KeyLeds[i] = CHSV(Cleds[i], 255, 0);  FastLED.show();   // update LEDs  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_key_on(): manage Key light on (for those keys you decide to use as push// versus toggle.void XE_key_on(int i) {  // no change in key state  // set color and light  KeyLeds[i] = CHSV(Cleds[i], 255, V_value);  FastLED.show();   // update LEDs  return;}/////////////////////////////////////////////////////////////////////////////////////////////////// XE_key_off(): manage Key light off (for those keys you decide to use as push// versus toggle.void XE_key_off(int i) {  // no change in key state  // reset color and light  KeyLeds[i] = CHSV(Cleds[i], 255, 0);  FastLED.show();   // update LEDs  return;}////void XE_X32Xlive_STOP(int local) {    XE_key_on(1);  if (local) {    s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&STOP);    send_udp(s_buf, s_len);  }  if (KeySate[2] == 1) XE_key_toggle(2);    // reset PLAY if needed  if (KeySate[3] == 1) XE_key_toggle(3);    // reset RECORD if needed  delay(150);  XE_key_off(1);}//void XE_X32Xlive_REC(int local) {  if (KeySate[3] == 0) {    if (local) {      s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&RECORD);      send_udp(s_buf, s_len);    }    XE_key_toggle(3);  }}//void XE_X32Xlive_PLAY() {  if ((KeySate[2] != 1) && (KeySate[3] == 0)) { // no action while recording    s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&PLAY);  }  send_udp(s_buf, s_len);  delay(1);  XE_key_toggle(2);}//void XE_X32Xlive_PAUSE() {  if ((KeySate[2] == 1) && (KeySate[3] == 0)) { // no action while recording    s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&PAUSE);  }  send_udp(s_buf, s_len);  delay(1);  XE_key_toggle(2);}/////////////////////////////////////////////////////////////////////////////////////////////////////              -------------------- Setup (Called once) -----------------/////////////////////////////////////////////////////////////////////////////////////////////////void setup() {  int i;  //  Serial.begin(115200);  //  // WIFI setup  Serial.printf("\nConnecting to %s ", ssid);  WiFi.begin(ssid, password);  while (WiFi.status() != WL_CONNECTED) {    delay(500);    Serial.print(".");  }  Serial.printf(" connected!\n");  Udp.begin(localUdpPort);  Serial.printf("Now listening to X32 at IP %s, UDP port %d\n", X32IP, localUdpPort);  //  // initialize sending /xremote every 9 seconds  send_udp(s_remt, 12);  XremoteMillis = millis();  //  // Encoder setup  // An example of jog (outer ring) device pinout found at  //            http://g7nbp.blogspot.fr/2008/12/jogging-and-shuttling-with-arduino.html  // Using these inputs:  pinMode(D6, INPUT_PULLUP); // GPIO12   -> Connector Pin 4  pinMode(D7, INPUT_PULLUP); // GPIO13   -> Connector Pin 5  pinMode(D5, INPUT_PULLUP); // GPIO14   -> Connector Pin 3  pinMode(D9, INPUT_PULLUP); // GPIO3/RX -> Connector Pin 1  //                            Ground   -> Connector Pin 2  //  values table  //  index           0  1   2  3   4  5   6  7   8  9   A  B   C  D     E        F  // rotation index [-5, 5, -4, 4, -2, 2, -3, 3, -6, 6, -7, 7, -1, 1, center-, center+};  //  //  // LEDs: Declare type  FastLED.addLeds < WS2811, DATA_PIN, RGB > (KeyLeds, NUM_LEDS);  // Assign a color to each LED  Cleds[0] = 0;  Cleds[1] = 32;  Cleds[2] = 64;  Cleds[3] = 96;  Cleds[4] = 128;  Cleds[5] = 160;  Cleds[5] = 192;  Cleds[7] = 224;  for (i = 0; i < NUM_LEDS; i++) {    KeyLeds[i] = CHSV(Cleds[i], 255, 0); // all LEDs black at startup    KeySate[i] = 0;                      // all keys to state = 0  }  V_value = 128;    // average value. 0 = min, 192 = absolute Max (TBD)  FastLED.show();   // update LEDs  //  //  // Keypad  // organization of keys               Approx analog values read for 3.27v VCC  //      1     2     3     4           500   199   125   92  //  //      5     6     7     8           1000  243   142   99  Keypad.SetHoldTime(KEY_HOLD_Time);            // Key held time in ms  Keypad.SetDebounceTime(KEY_BOUNCE_Time);      // Key Debounce time in ms}///////////////////////////////////////////////////////////////////////////////////////////////////////              -------------------- loop (Called always) -----------------/////////////////////////////////////////////////////////////////////////////////////////////////void loop() {  int s_i, key;  int b3, b2, b1, b0;  unsigned long currentMillis;  //  //  // Check is a /xremote OSC command needs to be sent to X32  currentMillis = millis();  if ((currentMillis - XremoteMillis) > 9000) {//    Serial.printf("Sending Remote\n");    send_udp(s_remt, 12);    XremoteMillis = currentMillis;  }  // Keypad update?  // Update LEDs according to status  // also set specific action when a key is pressed. This can be:  //    - simple toggling of KeyState so it can be used somewhere else  //    - take into account key press to send a command to X32 (blink LED too)  if ((key = XE_getk())) {//    Serial.printf("Key = %d\n", key);    switch (key - 49) {     // Action based on key pressed    case 0:                 // Set device encode in scrub or wind (ff or rew) mode      XE_key_toggle(0);      break;    case 1:                 // Set X-Live in STOP state      XE_X32Xlive_STOP(1);      break;    case 2:                 // Toggle X-Live between Paused and Play states      if(KeySate[3] == 0) { // no action while recording        if (KeySate[2] == 1) {          s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&PAUSE);        } else {          s_len = Xfprint(s_buf, 0, s_ustate, 'i', (char *)&PLAY);        }        send_udp(s_buf, s_len);        delay(1);        XE_key_toggle(2);      }      break;    case 3:                 // Set X-Live in Record state      XE_X32Xlive_REC(1);      break;    case 4:                 // Add Marker at current position (during Pause, Play or Record)      XE_key_on(4);      s_len = Xfprint(s_buf, 0, (char *)"/-action/addmarker", 'i', (char *)&ONE);      send_udp(s_buf, s_len);      delay(150);      XE_key_off(4);      break;    case 5:                 // Delete Marker at current position (during Pause)      XE_key_on(5);      XE_del_marker();      delay(100);      XE_key_off(5);      break;    case 6:                 // No-OP for now... :)      XE_key_toggle(6);      break;    case 7:                 // Jump to Setup/Card screen      XE_key_on(7);      s_len = Xfprint(s_buf, 0, (char *)"/-stat/screen/screen", 'i', (char *)&SETUP);      send_udp(s_buf, s_len);      s_len = Xfprint(s_buf, 0, (char *)"/-stat/screen/SETUP/page", 'i', (char *)&CARD);      send_udp(s_buf, s_len);      delay(150);      XE_key_off(7);      break;    }  }  //  // get shuffle index  b3 = digitalRead(D6);  b2 = digitalRead(D9);  b1 = digitalRead(D5);  b0 = digitalRead(D7);  s_i = b3 << 3 | b2 << 2 | b1 << 1 | b0;  // Choose how to manage the shuttle/scrub encode, depending on Key 0 state  // OFF -> suttle only, used for FFW and FRW, based on the shuttle position  // ON  -> scrub using the central jog , accelerated of slowed based on the outer shuttle  if (KeySate[0] == 1) {    XE_scrub_action(s_i);  } else {    // case where KeySate[0] = 0    if (SH_table[s_i]) {      XE_schuttle_action(s_i);    }  }  // Another OSC command?  XE_osc_check();  //  delay(10);  // end of loop}

Note: Interestingly, I found this device after making mine; nice piece of gear and the company has other programmable sets of keys that can be of interest too. My small device as described above is a lot cheaper and was fun to build. I learned a lot in the process, and I can program it in any ways I need.

X32 FX MIDI control box

(a project based on an idea from Chris K.)

Here's an example of a possible modification/extension of the design used for the MIDI stompbox above. How to control the 4 FX returns mutes from an X32, using MIDI. The idea is to provide 6 foot switches (simple push-ON ones) to control the following:

The schematic is quite similar to what's used for the MIDI stompbox, keeping the component number to the strict minimum. I also use 4 LED chips that are linked in series to provide the state of FX1 to FX4 above the foot switches. These LED chips are similar to what I used in the X32Encoder project above.

A small video shows the device in action.

The software is shown below:

X32 FX MIDI control box Source Code

/*  Creation: 23 Oct. 2018  ©-2018 Patrick-Gilles Maillot    A project to manage 6 separate footswitches.   Each switch (S1...S6) generates a SYSEX to MIDI, and is accompagnied with  a color LED    Created on the basis of TtapMIDI-X32: a Stomp pedal project for sending Tempo TAP delay as SYSEX MIDI 
  The program waits for a HW pin to becon=me active (when the user presses a switch). These events  are used to generate a delay sent as SYSEX MIDI array to the connected device. 
  The circuit for MIDI out:    Leonardo digital pin 1 connected to MIDI jack pin 5    MIDI jack pin 2 connected to ground    MIDI jack pin 4 connected to +5V through 220-ohm resistor  Attach a MIDI cable to the jack, then to a MIDI synth, and play music.
  The circuit for the switch:    Pushbutton attached to Leonardo digital pin pin 2 and to ground*///#define SMAX            6        // number of supported footswitches#define LMAX            4        // number of footswitch LEDs
#define FASTLED_INTERNAL#include <FastLED.h>// LEDs// LED control on D8// color codes for HSV can be found at https://github.com/FastLED/FastLED/wiki/Pixel-reference#chsv// S: typicall 255; Avoid setting a V value above 128 (1/2 full) as it consumes a lot of power#define LED_CTRL_PIN 8          // LED control will be driven from Data pin 8CRGB   C_Leds[LMAX];            // LED color data arrayint    A_Leds[LMAX];            // used to store the color (Hue value [0..255]) assigned to each LEDint    V_value;                 // used to set/update the max luminosity//unsigned char S0_array[] = {    // MIDI SYSEX for /fxrtn/01/mix/on ON  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x31,                   // /01..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x4E,                   // <sp>ON  0xF7                           // SYSEX tail};const int     S0_size = sizeof(S0_array) / sizeof(char);  // MIDI SYSEX or command S0 array size//unsigned char S1_array[] = {    // MIDI SYSEX for /fxrtn/02/mix/on ON  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x32,                   // /02..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x4E,                   // <sp>ON  0xF7                          // SYSEX tail};const int     S1_size = sizeof(S1_array) / sizeof(char);  // MIDI SYSEX or command S1 array size//unsigned char S2_array[] = {    // MIDI SYSEX for /fxrtn/03/mix/on ON  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x33,                   // /03..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x4E,                   // <sp>ON  0xF7                          // SYSEX tail};const int     S2_size = sizeof(S2_array) / sizeof(char);  // MIDI SYSEX or command S2 array size//unsigned char S3_array[] = {    // MIDI SYSEX for /fxrtn/04/mix/on ON  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x34,                   // /04..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x4E,                   // <sp>ON  0xF7                          // SYSEX tail};const int     S3_size = sizeof(S3_array) / sizeof(char);  // MIDI SYSEX or command S3 array size//unsigned char S4_array[] = {    // MIDI SYSEX for /fxrtn/[01…04]/mix/on OFF  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x31,                   // /01..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x46, 0x46,             // <sp>OFF  0xF7,                         // SYSEX tail  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x32,                   // /02..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x46, 0x46,             // <sp>OFF  0xF7,                         // SYSEX tail  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x33,                   // /03..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x46, 0x46,             // <sp>OFF  0xF7,                         // SYSEX tail  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x34,                   // /04..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x46, 0x46,             // <sp>OFF  0xF7                          // SYSEX tail};const int     S4_size = sizeof(S4_array) / sizeof(char);  // MIDI SYSEX or command S0 array size//unsigned char S5_array[] = {    // MIDI SYSEX for /fxrtn/04/mix/on OFF  0xF0, 0x00, 0x20, 0x32, 0x32, // SYSEX header  0x2F, 0x66, 0x78, 0x72, 0x74, 0x6E, // /fxrtn..  0x2F, 0x30, 0x34,                   // /04..  0x2F, 0x6D, 0x69, 0x78,             // /mix..  0x2F, 0x6F, 0x6E,                   // /on..  0x20, 0x4F, 0x46, 0x46,             // <sp>OFF  0xF7                          // SYSEX tail};const int     S5_size = sizeof(S5_array) / sizeof(char);  // MIDI SYSEX or command S5 array size
int    S_pin[SMAX];         // array of I/O pin number associated to each switch lineint    S_read[SMAX];        // array of input readings from digital input linesint    S_state[SMAX];       // array of current switches' statesint    S_check[SMAX];       // array of switch state changesint    S_change[SMAX];      // array of current switches' change status [0/1]int    D_delay;             // general debounce delay (typically between 20 and 50(ms))
void setup() {    // set LEDs    FastLED.addLeds < WS2811, LED_CTRL_PIN, RGB > (C_Leds, LMAX);    // assign IO pin numbers to switches according to schematics    S_pin[0] = 2;    S_pin[1] = 3;    S_pin[2] = 4;    S_pin[3] = 5;    S_pin[4] = 6;    S_pin[5] = 7;    // assign debounce delay    D_delay = 30;                               // typical values are less than 50ms    // set LED brightness value    V_value = 127;                              // mid-intensity //   pinMode(LED_CTRL_PIN, OUTPUT);    for (int i = 0; i < SMAX; i++) {        pinMode(S_pin[i], INPUT_PULLUP);        S_state[i] = 1;                         // 1 by default as IN is PULLUP        S_read[i] = 1;                          // 1 by default as IN is PULLUP        S_change[i] = 0;                        // all change states to OFF        S_check[i] = 0;                         // all check states to OFF    }    for (int i = 0; i < LMAX; i++) {        A_Leds[i] = i * 96;                     // whatever these are (green, red,...)        C_Leds[i] = CHSV(A_Leds[i], 255, 0);    // all LEDs black at startup    }    FastLED.show();                             // resets all LEds at startup     // set MIDI baud rate:    Serial1.begin(31250);}//void loop() {    // scan for changes and get time of state change(s)    for (int i = 0; i < SMAX; i++) {        if ((S_change[i] == 0) && (S_check[i] == 0)) { // only consider switches that are 'ready'            S_read[i] = digitalRead(S_pin[i]);            if (S_state[i] ^ S_read[i]) {              // consider all transitions                S_check[i] = millis();            }        }    }    // done for all switches, still stable after debounce time?    for (int i = 0; i < SMAX; i++) {        if ((S_change[i] == 0) && (S_check[i] != 0)) {            // only for switches to consider            if ((millis() - S_check[i]) > D_delay) {                S_read[i] = digitalRead(S_pin[i]);   // still stable ?                if (S_state[i] ^ S_read[i]) {                    S_change[i] = 1;                 // consider switch has changed state                    S_check[i] = 0;                  // prevent further checks               }                S_state[i] = S_read[i];              // remember state (pressing ON is 0)            }        }    }    // at this point we know which switches have been pressed    // S4 mutes all effecs (set state)    if ((S_change[4] == 1) && (S_state[4] == 0)) {   // only for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S4_size; i ++) {          Serial1.write(S4_array[i]);        }        // done; reset switch state        S_change[4] = 0;        //update LEDs (reset all)        C_Leds[0] = CHSV(A_Leds[0], 255, 0);        C_Leds[1] = CHSV(A_Leds[1], 255, 0);        C_Leds[2] = CHSV(A_Leds[2], 255, 0);        C_Leds[3] = CHSV(A_Leds[3], 255, 0);        FastLED.show();    } else {          S_change[4] = 0;        }    // S0 unmutes Fxrtn/1 (set state)    if ((S_change[0] == 1) && (S_state[0] == 0)) {   // only for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S0_size; i ++) {          Serial1.write(S0_array[i]);        }        // done; reset switch state        S_change[0] = 0;        //update LEDs (set LED 0)        C_Leds[0] = CHSV(A_Leds[0], 255, V_value);        FastLED.show();    }  else {          S_change[0] = 0;        }    // S1 unmutes Fxrtn/2 (set state)    if ((S_change[1] == 1) && (S_state[1] == 0)) {   // only for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S1_size; i ++) {          Serial1.write(S1_array[i]);        }        // done; reset switch state        S_change[1] = 0;        //update LEDs (set LED 1)        C_Leds[1] = CHSV(A_Leds[1], 255, V_value);        FastLED.show();    } else {          S_change[1] = 0;        }    // S2 unmutes Fxrtn/3 (set state)    if ((S_change[2] == 1) && (S_state[2] == 0)) {   // only for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S2_size; i ++) {          Serial1.write(S2_array[i]);        }        // done; reset switch state        S_change[2] = 0;        //update LEDs {set LED 2)        C_Leds[2] = CHSV(A_Leds[2], 255, V_value);        FastLED.show();    } else {          S_change[2] = 0;        }    // S3 unmutes Fxrtn/4 (set state)    if ((S_change[3] == 1) && (S_state[3] == 0)) {   // only for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S3_size; i ++) {          Serial1.write(S3_array[i]);        }        // done; reset switch state        S_change[3] = 0;        //update LEDs (set LED 3)        C_Leds[3] = CHSV(A_Leds[3], 255, V_value);        FastLED.show();    } else {          S_change[3] = 0;        }    // S5 unmutes Fxrtn/4 (as long as switch pressed)    if ((S_change[5] == 1) && (S_state[5] == 0)) {  // for OFF->ON trasition        // send corresponding SYSEX        for (int i = 0; i < S3_size; i ++) {          Serial1.write(S3_array[i]);        }        // done; reset switch state        S_change[5] = 0;        //update LEDs (set LED 3 to Color5)        C_Leds[3] = CHSV(128, 255, V_value);        // a special color for the momentary        FastLED.show();                             // switch    }    if ((S_change[5] == 1) && (S_state[5] == 1)) {  // for ON->OFF trasition        // send corresponding SYSEX        for (int i = 0; i < S5_size; i ++) {          Serial1.write(S5_array[i]);        }       // done; reset switch state        S_change[5] = 0;        //update LEDs (reset LED 3)        C_Leds[3] = CHSV(A_Leds[3], 255, 0);        FastLED.show();    }}

Maybe you'd prefer to control the four FX as stereo effects, in which case you'll keep the links as set by the init state of the X32, and the 4 effects will be as follows: FX1: fxrtn 1&2, FX2: fxrtn 3&4, FX3: fxrtn 5&6, FX4: fxrtn 7&8. A few changes to the code above and we're done!

Home Automation - Lights control

I was asked to build a system that would enable controlling the lights of an apartment from a mobile phone. There are numerous solutions, web based for some of them (Alexa, Google Home, etc.) or other commercial systems which may or may not connect to the web. It was OK for me to use WIFI, but why should someone accept to send personal data to the web, a web operator for just putting on or off lights at home? This doesn't feel right to me. So I looked at solutions that would enable a relay to function, based on a WIFI connected device (and the WIFI being limited to your home only, no operator involved), and controlled by a mobile phone, through WIFI, and therefore not getting your mobile operator involved.

I initially developed the SW using a nodeMCU (ESP8266) device. But that is too big, (and expensive) for the little thing I'm asking from the controlling device. A generic ESP-01s is quite enough! The device runs on 5V, and I need to control the mains line. A tiny, yet 700mA capable 5V power supply provides the DC power for the ESP01. A relay is used to open or close a contact in series with one of the mains live line. Interestingly, you can find the ESP and relay in one bundle.

WARNING:

If you undergo this project, remember to apply strict security. 230V can be very dangerous, and really hurts.

The principle is as follows: A self-contained device is connected to the mains, and contains an ESP is connected to your home WIFI, when getting the order to set lights on, it closes the relay, thus passing 230VAC to the lights that are connected to the output of the device. When receiving the order to switch off the lights, the ESP opens the relay, and the mains is disconnected from the output. This is shown in a schematics below (left column), photos of the components on the right:

So far, so good... A few wires to add, a little bit of Arduino SW and we'll be done? No... This needs to be controlled from a mobile phone. And this implies Android code which is a more difficult to develop than Arduino code, but thankfully, there's MIT App Inventor. This is a site managed by MIT that enables just about anyone with a little bit of coding practice to write Android (or other mobile device) applications, very easily. One still need to have some programing knowledge, but it makes things a lot easier.

 The Mobile Application runs as follows:

The app gathers the IP address of the ESP device(s) and keeps it in a file. An Icon is used for sending an "ON" order, and another one for sending an "OFF" order. All this runs using a simple UDP link between the phone and the ESP. But how to get the IP address of the ESP, without having to reprogram it (using USB between a computer and the ESP) each time? Well, upon reset we set the ESP to be a WIFI access point. The mobile phone can be used to connect to that access point in order to provide the SSID and password of the home network. These two information will stay in the ESP at the time we cancel the access point and ask it to connect to the home network. The user can then set the mobile phone to connect to the home network and use the mobile application to send a broadcast message on the network to get back the IP address of the device. The IP is then kept as the address for the mobile phone app to send orders to the ESP.

The Mobile Phone application supports two separate devices. More can be added of course. To install the Android app, just copy it to a folder in the mobile phone and click on the file. This will prompt for installing the application as any other Android app, not requiring any authorization to access anything from your phone.

The ESP application SW is provided below:

Home Automation - Lights control Source Code

/* *  ©2019 - Patrick-Gilles Maillot *  This program sets up a simple Access Point server. *    From there, the user can provide the SSID and Password of his/her home network *  The systems (as long as power is applied) will then run in standard Station mode *  to manage UDP to control (ON/OFF) devices such as lights *  The server will manage devices (a GPIO pin) depending on the request *    http://server_ip/L/off will set lights off, *    http://server_ip/L/on will set lights on *  server_ip is the IP address of the ESP8266 module, will be *  printed to Serial when the module is connected. */#define RELAY 0//#include <ESP8266WiFi.h>#include <WiFiUdp.h>//#include <WiFiClient.h>#include <ESP8266WebServer.h>#include <ESP8266HTTPClient.h>char        ssid_str[32];         // string for incoming home network ssidchar        pass_str[32];         // string for incoming home network passwordint         AP_on;                // specify if AP mode is on (used to request network creds)int         Is_new;               // a flag to prevent response to broadcast if already usedIPAddress   ip;                   // UDP mode IP addressWiFiUDP     Udp;                  // UDP instanceint         localUdpPort = 10001; // UDP port fixed at 10001char        ipbuf[32];            // IP buffer for printing IP address (debug)char        r_buf[256];           // UDP read bufferint         r_len;                // UDP data lengthint         m = 0;                // m is an incremented variable to number received messages (debug)////// Macro to send UDP messages to sender#define send_udp(message, len)                         \    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); \  Udp.write(message, len);                           \ Udp.endPacket();// //// TCP web server and client to request home network credentials ESP8266WebServer server(80);   // Instanciating server at port 80 WiFiClient client;// Client instance /* Go to http://192.168.4.1 in a web browser  * connected to this access point to see it.  */// Display webpage to request credentialsvoid webpage() { server.send(200, "text/html",   "<html><body><form name='frm' method='post'>\        <input type='text' style='width:320px;height:50px;' name='x'><input type='submit' value='SSID Name'><p> \        <input type='text' style='width:320px;height:50px;' name='y'><input type='submit' value='Pass'> \        </form></body></html>");}// Read answers from webpage above void response() { if (server.hasArg("x") && (server.arg("x").length() > 0)) {  // TODO check that it's not longer than 31 characters  server.arg("x").getBytes((unsigned char*) ssid_str, 32);  server.arg("y").getBytes((unsigned char*) pass_str, 32);  server.send(200, "text/html",    "<html><body><h1>Successful</h1> \     <a href='/'>Home</a></body></html>");  AP_on = 0; } else {  server.send(400, "text/html",    "<html><body><h1>HTTP Error 400</h1>\    <p>Bad request. Please enter a value.</p></body></html>"); }}
////// Project Setup section:// A first part sets the Access Point modevoid setup() { Serial.begin(115200); Serial.println(); Serial.println("Configuring access point..."); WiFi.softAP("LiGhTs", "12345678"); // Set AP mode AP_on = 1; IPAddress myIP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(myIP); server.on("/", HTTP_GET, webpage); server.on("/", HTTP_POST, response); server.begin(); Serial.println("HTTP server started"); // // // Manage ESP full reset by reading home network credentials as we are in AP mode while (AP_on) {  server.handleClient(); } // Done! Time to restart the ESP. // This time (and from now on) we want to be in standard wifi mode WiFi.disconnect(true); delay(200); WiFi.softAPdisconnect(); delay(200); // Set ESP to Station mode (standard WIFI appliance) WiFi.mode(WIFI_STA); delay(200); // Not a good idea to display credentials...  Serial.println("credentials to Home Network received"); // set Is_new flag to 'new' device Is_new = 1; // Connect to WiFi network Serial.printf("\nConnecting to %s Home Network as standard appliance",   ssid_str); WiFi.begin(ssid_str, pass_str); while (WiFi.status() != WL_CONNECTED) {  delay(500);  Serial.print("."); } // Acknowledge connection ip = WiFi.localIP(); sprintf(ipbuf, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); Serial.printf(" done!\n"); Serial.printf("UDP ready, IP: %s, port %d\n", ipbuf, localUdpPort); Udp.begin(localUdpPort); delay(500); pinMode(RELAY, OUTPUT);}////void loop() { // In the loop() section, we only manage UDP requests // and answer them based on current state of the device if (Udp.parsePacket() > 0) {  r_len = Udp.read(r_buf, 255);  r_buf[r_len] = 0;  Serial.printf("mes# %d r'ved, len = %d, message: %s\n", m++, r_len, r_buf);  if (strcmp(r_buf, "/L/getip") == 0) {   if (Is_new) {    send_udp("P-lights", 8);   } else {    Serial.printf("-> Ignore broadcast request\n");   }  } else if (strcmp(r_buf, "/L/off") == 0) {   Is_new = 0;   // set GPIO 0 & 2 to high (OFF)   digitalWrite(RELAY, HIGH);   Serial.printf("lights OFF\n");  } else if (strcmp(r_buf, "/L/on") == 0) {   Is_new = 0;   // set GPIO 0 & 2 to low (ON)   digitalWrite(RELAY, LOW);   Serial.printf("lights ON\n");  }  r_len = 0; } else {  // No need to use too much CPU resources,  // a request every 10ms is quite enough!   delay(10); }}

A small document explains how to use the final device, i.e. the different steps and actions to perform to setup one or more ESP devices controlled form a single mobile application. You can then just click on the green or red icon respective of the relay you want to action.