ESP32 textile touch BLE gamecontroller

The controller script uses the default Unity Input system to query touch inputs coming from the ESP. Thus, the touch inputs of the ESP32 deliver analog values, they are not that stable to be used as analog values, therefore they are used as soimple triggers for a two axis interpolated interface. plus one fire button 🙂

Unity C# / setup

For all communication, the ESP needs to be connected to the main device via BLE and the following script needs to be added to your Unity project. There is some debugging stuff included, so it is best to use the Unity package below to have the proper setup if you are unsure.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ESP_CONTROLLER : MonoBehaviour
{

    public bool _tone_is = false;
    public bool _ttwo_is = false;
    public bool _ttri_is = false;
    public bool _tfour_is = false;

     public bool _tfive_is = false;

    public static bool FIRE = false;

    public float cursor_speed = .07f;
    public bool esp_is_connected = false;


    public static Vector2 CURSOR = Vector2.zero;

    public Image ui_debug_cursor;


    // Update is called once per frame
    void Update()
    {

        if (Input.GetJoystickNames().Length > 0)
        {
            esp_is_connected = true;
        }
        else
        {
            esp_is_connected = false;
        }

        // helpful function to print ids of all pressed buttons!
        /*
        for (int i = 0;i < 20; i++) {
         if(Input.GetKeyDown("joystick 1 button "+i)){
              print("joystick 1 button "+i);
         }
        }
        */

        _tone_is = Input.GetKey("joystick 1 button 0");
        _ttwo_is = Input.GetKey("joystick 1 button 1");
        _ttri_is = Input.GetKey("joystick 1 button 2");
        _tfour_is = Input.GetKey("joystick 1 button 3");
        _tfive_is = Input.GetKey("joystick 1 button 4");

        // this setup depends on your hardware wiring!
        // trigger down the button controls both x / y axis
        if (_tone_is) { CURSOR.x -= cursor_speed; }
        if (_ttwo_is) { CURSOR.x += cursor_speed; }
        if (_ttri_is) { CURSOR.y += cursor_speed; }
        if (_tfour_is) { CURSOR.y -= cursor_speed; }


        if(_tfive_is){FIRE=true;}else{FIRE=false;}

        // clamp cursor in relevant boundaries
        CURSOR.x = Mathf.Clamp(CURSOR.x, -1f, 1f);
        CURSOR.y = Mathf.Clamp(CURSOR.y, -1f, 1f);


        // return back to center if nothing pressed
        CURSOR *= .93f;

        // draw the debug dot - only for testing!
        Vector2 ncp = new Vector2(
            CURSOR.x * Screen.width * .5f + Screen.width * .5f,
            CURSOR.y * Screen.height * .5f + Screen.height * .5f
            );

        ui_debug_cursor.transform.position = ncp;

          if(FIRE){  
         ui_debug_cursor.transform.localScale = new Vector2(3f,3f);
          }else{
               ui_debug_cursor.transform.localScale = new Vector2(1f,1f);
          }
    }

}

Feel free to download the basic package for your unity project over here: zukunft.burg-halle.de/downloads/ESP32 _basic_controller_unity.unitypackage

FOr the Avatar Controller Project you will need some additional setup in Unity. An example pipeline from ESP to Animator is in this project:
http://zukunft.burg-halle.de/downloads/esp_avatar_controller.unitypackage

ESP32 code

For the ESP32 we will use the pretty stable BLE Gamepad library by LemmingDev https://github.com/lemmingDev/ESP32-BLE-Gamepad. We collect all touch input and send it via BLE as a default Gamepad. This allows us to connect our ESP to allmost every device – even Apple products 🙂

// ----------------------------------
// ------- TRXYS TOUCH HELPERS ------
// ----------------------------------

// these are some helpers to deal with the unstable values of the touchpins
   
  // simple lerp helper function
   
float return_lerp(float _s, int _target,int _time){
    
   _s = _s + (( float(_target) - _s)/float(_time));
   return _s;
      
}
   
class aTouch {
  private:
    bool prev_touch_state = false;
    byte pin;
    int smooth_time = 2;
    int trigger_threshold = 8;
    long ts = 0;
  public:
    int current_val = 0;
    int smoothed_val = 0;
    int diff_val = 0;
    bool is_triggered = false;
    bool on_pressed = false;
    bool on_released = false;
     bool is_holded = false;
    aTouch(byte pin) {
     this->pin = pin;
    }
       
   
    void readAndProcessInput() {
   
        // reset interaction states
        on_pressed = false;
        on_released = false;
           
         // directly read out values TWICE = BUGFIX for debouncing 
         current_val = touchRead(pin);
         delayMicroseconds(10);
         current_val = touchRead(pin);
   
        //calculate smoothed input values 
         smoothed_val = return_lerp(smoothed_val,current_val,smooth_time);
   
         // calc current differential sum of button
          diff_val =   smoothed_val - current_val;
   
          // check if there is a noticable difference input values
          if(  diff_val  > trigger_threshold){
   
              if(prev_touch_state == false){
                is_triggered = true;
                prev_touch_state = is_triggered;
                on_pressed = true;
                ts = millis(); // set timestamp for hold routine
             }
                
          }else if( diff_val < trigger_threshold*-.4){
   
              if(prev_touch_state == true){
                is_triggered = false;
                 prev_touch_state = is_triggered;
                 on_released = true;
              }
                   
          }
   
   
          // calculate timed holding function
             
          if( ts + 2500 < millis() && is_triggered){
   
                  
              is_holded = true;
          }else{
   
              is_holded = false;
           }
  
       delayMicroseconds(2);
     }
  
};


//------------------------------
// -----------------------------



 
aTouch touch1(2);
aTouch touch2(4);
aTouch touch3(15);
 
aTouch touch4(13);
aTouch touch5(12);
aTouch touch6(14);
 
aTouch touch7(27);
aTouch touch8(33);

 
#include <BleGamepad.h> 

BleGamepad bleGamepad("TRXY ONE ALLTOUCH", "Die Zukunft", 99);

void setup() 
{
  //Serial.begin(115200);
  //Serial.println("Starting BLE work!");
  bleGamepad.begin(12,2); 
  bleGamepad.setAutoReport(false);


}

void loop() 
{

   touch1.readAndProcessInput();
   touch2.readAndProcessInput();
   touch3.readAndProcessInput();
   touch4.readAndProcessInput();
   touch5.readAndProcessInput();
   touch6.readAndProcessInput();
   touch7.readAndProcessInput();
   touch8.readAndProcessInput();

  
  if(bleGamepad.isConnected()) 
  {
 
    
      // todo > make dynamic buffer range for all touchinputs!
      // map smoothed touchvalue to responsive range ( usually between 5 and 70)
      
      int one = int(map(touch1.smoothed_val,0,100,0,32767.*2.));
      int two = int(map(touch2.smoothed_val,0,100,0,32767.*2.));
      int tree = int(map(touch3.smoothed_val,0,100,0,32767.*2.));

      int four = int(map(touch4.smoothed_val,0,100,0,32767.*2.));
      int five = int(map(touch5.smoothed_val,0,100,0,32767.*2.));
      int six = int(map(touch6.smoothed_val,0,100,0,32767.*2.));

      int seven = int(map(touch7.smoothed_val,0,100,0,32767.*2.));
      int eight = int(map(touch8.smoothed_val,0,100,0,32767.*2.));

 
      bleGamepad.setX(one);
      bleGamepad.setY(two);
      bleGamepad.setZ(tree);

      bleGamepad.setRZ(four);
      bleGamepad.setRX(five);
      bleGamepad.setRY(six);
      
      bleGamepad.setSlider1(seven);
      bleGamepad.setSlider2(eight);


      if(touch1.is_triggered){ bleGamepad.press(BUTTON_1); }else{ bleGamepad.release(BUTTON_1); }
      if(touch2.is_triggered){ bleGamepad.press(BUTTON_2); }else{ bleGamepad.release(BUTTON_2); }
      if(touch3.is_triggered){ bleGamepad.press(BUTTON_3); }else{ bleGamepad.release(BUTTON_3); }
      if(touch4.is_triggered){ bleGamepad.press(BUTTON_4); }else{ bleGamepad.release(BUTTON_4); }
      if(touch5.is_triggered){ bleGamepad.press(BUTTON_5); }else{ bleGamepad.release(BUTTON_5); }
      if(touch6.is_triggered){ bleGamepad.press(BUTTON_6); }else{ bleGamepad.release(BUTTON_6); }
      if(touch7.is_triggered){ bleGamepad.press(BUTTON_7); }else{ bleGamepad.release(BUTTON_7); }
      if(touch8.is_triggered){ bleGamepad.press(BUTTON_8); }else{ bleGamepad.release(BUTTON_8); }

      bleGamepad.sendReport();
       
    
    delay(10);
 
  }
}