Motorized Slide Linear Potentiometer

Hi guys, I’m in the process of making a motorized linear fader, but I’m having some problems measuring the potentiometer. When I choose a position, the value is varying a lot. To the point of not being useful because when I map from 0 to 100, it varies +2 / -2.

I’ve read and the max I found was that to improve the potentiometer reading, I should use a resistor and a capacitor. But I do not know the values nor the location.

I have not yet decided where I will do the final coding. If it will be in esphome or arduino. I’m undecided with this because I still can’t get the potenciometer to the position I want.

If you can help me, thank you very much. I have a little bit of knowledge, but I do not know electronic components to filter or improve the project.


The points I want in this project are:

  • An accessory to connect to Home Assistant.
  • An entity input_number from 0 - 100, to receive the measuring of the potentiometer and also to input the level for the motor to go to the selected level.
  • Knob Touch.

Dream Features:

  • An entity to control the Variable Tactile Feedback on the Linear Slide to feel the levels on the knob. (Ex: 6 will create the virtual “creases” on this levels 0% - 20% - 40% - 60% - 80% - 100% ).
    (Ex: 11 will create the virtual “creases” on this levels 0% - 10% - 20% - 30% - 40% - 50% - 60% - 70% - 80% - 90% - 100% ).

The hardware:

  • Motorized Slide Linear Potentiometer Touch Sensitive 10k rsa0n11m9
  • H-Bridge DC Stepper Motor-Driver Board L9110S
  • ESP32 CH340C Type-C
  • (Prototyping) Breadboard 830 with Power Supply MB102
  • (Not Yet) 4-Digit 7-Segment Display TM1637

Schematic:


The code I have today on the ESPHome:

esphome:
  name: slider
  friendly_name: Slider

esp32:
  board: esp32dev
  # framework:
  #   type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ota:
  password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Slider Fallback Hotspot"
    password: !secret wifi_password

captive_portal:

globals:
 - id: new_position
   type: float
   restore_value: no

number:
  - platform: template
    name: HA Input Percentage
    id: ha_input_percentage
    min_value: 0
    max_value: 100
    step: 1
    optimistic: true

sensor:
  - platform: adc
    id: analog_read_fader
    pin: A0
    name: "Analog Read Fader"
    update_interval: 0.005s
    accuracy_decimals: 3
    attenuation: 11db
    # filters:
    #   - median:
    #       window_size: 20
    #       send_every: 1
    #   - calibrate_linear:
    #       - 0.14200 -> 0
    #       - 3.13400 -> 100

  - platform: template
    name: "Percentage Fader"
    id: percentage_fader
    lambda: |-
      return (id(analog_read_fader).state - 0.142) * (100 - 0) / (3.134 - 0.142) + 0;
    update_interval: 0.005s
    accuracy_decimals: 0

esp32_touch:
  setup_mode: false

binary_sensor:
  - platform: esp32_touch
    id: touch_fader
    name: "Touch Fader"
    pin: GPIO33
    threshold: 330

output:
  - platform: ledc
    id: pinA
    pin: GPIO22
  - platform: ledc
    id: pinB
    pin: GPIO23

fan:
  - platform: hbridge
    id: motor_fader
    name: "Motor Fader"
    pin_a: pinA
    pin_b: pinB
    decay_mode: slow

The code I have today on the Arduino:

#define FILTER_LEN  5

uint32_t AN_Pot1_Buffer[FILTER_LEN] = {0};
int AN_Pot1_i = 0;
int AN_Pot1_Raw = 0;
int AN_Pot1_Filtered = 0;

// Motor Pins
const int pwmA = 22;
const int pwmB = 23;

bool motor_release_state = false;  // to handle motor release


// Fader Pin and Variables
const int fader = A0;
const int touchPin = A5;

int fader_pos = 0;
float filter_amt = 0.75;
float speed = 1.0;
const int touchThreshold = 15;
int baseline;


// float step = 40.92;
// int saved_positions[] = { 230 };
// int current_saved_position = 1 ;

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

  delay(1000);

  pinMode(touchPin, INPUT);

  pinMode (pwmA, OUTPUT);
  pinMode (pwmB, OUTPUT);
  analogWriteFrequency(100);
  analogWrite(pwmA, 0); 
  analogWrite(pwmB, 0);

  baselineTouch();

  // for (uint8_t i=0; i< num_buttons; i++){
  //   buttons[i].attach( button_pins[i], INPUT_PULLUP);
  // }
}

int last_fader_pos = fader_pos;


void loop() {
  // for (uint8_t i=0; i< num_buttons; i++){
  //   buttons[i].update();
  //   if( buttons[i].fell()) {
  //     current_saved_position = i;
  //     go_to_position(saved_positions[current_saved_position]);
  //   }
  // }

  if( isTouched() ){
    Serial.println("Touched");
    // motor_release_state = true;
    motor_release_state = false;

    go_to_position(25);
    // delay(1000);
    // go_to_position(75);
    // delay(1000);
    // go_to_position(0);
    // delay(1000);
    // go_to_position(100);

    // analogWrite(pwmA, 0);
    // analogWrite(pwmB, 0);
    // delay(60);
  }
  else{
    // Serial.println("NOT Touched");
    motor_release_state = false;
    // go_to_position(saved_positions[current_saved_position]);
  }
  
  fader_pos = int( (filter_amt * last_fader_pos) + ( (1.0-filter_amt) * int(analogRead(fader) )) );

  // int mappedFader = map(fader_pos, 3, 4095, 0, 100);
  // int mappedLast_fader_pos = map(last_fader_pos, 3, 4095, 0, 100);
  // Serial.println("-------------------------------------------");
  // Serial.print("Fader: ");
  // Serial.println(fader_pos);
  // Serial.print("Fader Mapped: ");
  // Serial.println(mappedFader);
  // Serial.print("last_fader_pos: ");
  // Serial.println(last_fader_pos);
  // Serial.print("last_fader_pos Mapped: ");
  // Serial.println(mappedLast_fader_pos);

  AN_Pot1_Raw = analogRead(fader);
  AN_Pot1_Filtered = readADC_Avg(AN_Pot1_Raw);

  int mappedAN_Pot1_Filtered = map(AN_Pot1_Filtered, 0, 4095, 0, 100);


  // Serial.print(AN_Pot1_Raw);        // Print Raw Reading
  // Serial.print(',');                // Comma Separator
  Serial.print(AN_Pot1_Filtered); // Print Filtered Output
  Serial.print(" | "); // Print Filtered Output
  Serial.print(mappedAN_Pot1_Filtered); // Print Filtered Output

  if (abs(AN_Pot1_Filtered - last_fader_pos) > 1) {
    // if (motor_release_state==false){
    //   go_to_position(saved_positions[current_saved_position]);
    // }
    last_fader_pos = AN_Pot1_Filtered;
  }
  Serial.println("");
}

void go_to_position(int new_position_percentage) {

  int new_position = map(new_position_percentage, 0, 100, 0, 4095);
  // int new_position = new_position_percentage * step;
  // int new_position_next = (new_position_percentage + 1) * step;

  // new_position = new_position + (step/2);
  // if ( new_position > 4095 ) {
  //   new_position = 4095;
  // }

  Serial.print("!! new_position: ");
  Serial.println(new_position);

  int fader_pos_raw = analogRead(fader);
  fader_pos = readADC_Avg(fader_pos_raw);

  // fader_pos = int(analogRead(fader));
  int last_fader = fader_pos;
  Serial.print("@@ fader_pos: ");
  Serial.println(fader_pos);

  // Serial.print("## new_position_next: ");
  // Serial.println(new_position_next);

  Serial.print("$$ while: ");
  Serial.println(abs(fader_pos - new_position));

  while (!(abs(fader_pos - new_position) <= 10)) {
  // while (new_position <= fader_pos && fader_pos < new_position_next) {
    // Serial.print("!! new_position: ");
    // Serial.println(new_position);
    // Serial.print("%% last_fader: ");
    // Serial.println(last_fader);
    // Serial.print("@@ fader_pos: ");
    // Serial.println(fader_pos);
    // Serial.print("## new_position_next: ");
    // Serial.println(new_position_next);

    Serial.print("Fader Inicio: ");
    Serial.println(fader_pos);

    Serial.print("$$ Diff: ");
    Serial.println(abs(fader_pos - new_position));
    Serial.print("$$ WHILE: ");
    Serial.println(!(abs(fader_pos - new_position) <= 15));
    
    if ( fader_pos > new_position ) {
      // Serial.println("fader_pos MAIOR");
      // speed = 2.25 * abs(fader_pos - new_position) / 1024 + 0.2;
      // speed = constrain(speed, -1.0, 1.0);
      //   if (speed > 0.0) {
      analogWrite(pwmA, 150);
      analogWrite(pwmB, 0);
      // delay(30);
      // analogWrite(pwmA, 0);
      // }
    }

    if ( fader_pos < new_position ) {
      // Serial.println("new_position MAIOR");
      // speed = 2.25 * abs(fader_pos - new_position) / 1024 - 0.2;
      // speed = constrain(speed, -1.0, 1.0);
      //   if (speed > 0.0) {
      analogWrite(pwmA, 0);
      analogWrite(pwmB, 150);
      // delay(30);
      // analogWrite(pwmB, 0);
        // }
    }

    // delay(200);

    // analogWrite(pwmA, 0);
    // analogWrite(pwmB, 0);
  
    // delay(50);

    last_fader = fader_pos;

    fader_pos_raw = analogRead(fader);
    fader_pos = readADC_Avg(fader_pos_raw);
   
    // fader_pos = analogRead(fader);
    Serial.print("Fader: ");
    Serial.println(fader_pos);

    if (abs(fader_pos - new_position) <= 10) {
      Serial.println("BREAAAK");
      analogWrite(pwmA, 0);
      analogWrite(pwmB, 0);
    }
    // Serial.println("-------------------------------------------- ");

  }

  analogWrite(pwmA, 0);
  analogWrite(pwmB, 0);
}

void baselineTouch() {
  baseline = touchRead(touchPin);
}

bool isTouched() {
  // Serial.print("isTouched(): ");
  // Serial.println(readTouch() < baseline - touchThreshold);

  // Serial.print("baseline: ");
  // Serial.println(baseline);

  // Serial.print("touchThreshold: ");
  // Serial.println(touchThreshold);

  return (readTouch() < baseline - touchThreshold);
}

int readTouch() {
  // Serial.print("readTouch(): ");
  // Serial.println(touchRead(touchPin));
  return touchRead(touchPin);
}

uint32_t readADC_Avg(int ADC_Raw) {
  int i = 0;
  uint32_t Sum = 0;
  
  AN_Pot1_Buffer[AN_Pot1_i++] = ADC_Raw;
  if(AN_Pot1_i == FILTER_LEN)
  {
    AN_Pot1_i = 0;
  }
  for(i=0; i<FILTER_LEN; i++)
  {
    Sum += AN_Pot1_Buffer[i];
  }
  // Serial.println(Sum/FILTER_LEN);
  return (Sum/FILTER_LEN);
}

The readings I got from HA and ESPHome. Period of 5 min. Analog readings from 0.142 to 3.134. The knob was on 75%ish.

2 Likes

Did you get it to work?
I am actually looking in this project myself so I am curious about your results.

I am working on it. I changed a few things.

I stopped trying to read the potentiometer directly from the ADC of the ESP32, I started using the ADS1115, an external ADC.
It was the best decion I had :rofl::rofl:. The variations I had, they almost disappeared. The variation is +/- 0.002. Now it doesn’t effect anymore when converting to 0-100.

5 min reading:

I decided to go with ESPHome.


Things that are working:

  • Knob Touch
  • Analog Reading of the potentiometer
  • Percentage Reading (analog reading conversion)
  • Potentiometer Calibration (Calibrates automatically on boot, and also the option to ask to calibrate. The calibration works by turning the motor forward, it measures to get the max. Turn the motor to reverse and measure to get the min. The max and min are used as parameters for the 0-100 conversion)
  • Inputs in HA to choose a percentage (position) in the potentiometer (I’m still suffering with the code to make the knob stay in the position I chose.)
  • Motor (I can only turn the motor on and off, change direction, change speed and change frequency)

I will polish the code and share it here.

I am suffering with the motor settings, to make it go very slowly and without getting bumps. I’m using 12v on the motor. I’m messing with the frequency and speed to see if I find the perfect setting for 2 speeds:

  • 1 fast no so fast, but smooth for when the knob is far from the chosen point.
  • 1 more precise for when it is very close to the chosen point.

I already managed to make the knob go to the chosen place using the ESPHome Script, but these motor settings need to improve to be at least acceptable. I managed to do with the motor going very slowly, but getting bumps. I could only get the motor to move at low speed with low frequency. But the low frequency gives the bumps.


1 Like

Wow nice to hear! I am curious towards the code.
I was thinking to calculate the difference between the goal-actual knob stand. And let the motorspeed depend on that.

I dont want it as fan, my h-bridge works a bit different than esphome expects. With 1pwm and 2 up and down boolean. Anways its working somewhat now.

I like your calibrate style and add it later on.

I struggled with the frequency a bit. And I think I ended around 100hz now. It goes slow enough for me, and its not that bumpy.

This is what I have now, with a bit debug logging

# >=============== Config ===============>
substitutions:
  location: "server-room"
  desc: "Test"
  device_name: "DevKit Black 2"
  device_host: "dev-kit-black-2"
  device_id: "dev_kit_black_2"
# >================ Base ================>
logger:
  level: DEBUG
  logs:
    number: ERROR
    sensor: ERROR
    text_sensor: ERROR
    switch: ERROR
    binary_sensor: ERROR
api:
  encryption:
    key: !secret api_encryption_key
ota:
  safe_mode: true
  password: !secret ota_password
  reboot_timeout: 1min
  num_attempts: 20
time:
  - platform: sntp
    id: sntp_time
    servers:
      - !secret sntp_server
# >============== Internet ==============>
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  ap:
    ssid: Fallback ${device_name}
    password: !secret wifi_fallback_password
# >=============== DevKit ===============>
esphome:
  name: ${device_host}
  friendly_name: ${device_name}
  on_boot:
    then:
      - script.execute: on_boot

esp32:
  board: nodemcu-32s
status_led:
  pin: GPIO2
# >=============== Sensor ===============>

globals:
  - id: new_position
    type: float
    restore_value: no

sensor:
  - platform: adc
    # internal: true
    pin: 34
    update_interval: 50ms
    attenuation: auto
    name: "Fader voltage"
    id: analog_read_fader
    accuracy_decimals: 3
    filters:
      - delta: 0.01
    on_value:
      then:
        - lambda: |-
            id(fader_percentage).publish_state(x);
        - if:
            condition:
              binary_sensor.is_on: fader_touch
            then:
              - lambda: |-
                  ESP_LOGD("=====>", "Move slider to: %1.2f%, delta: %1.3f, setting volume", id(volume).state, id(delta).state);
                  char buf[200];
                  sprintf(buf, "↭ Moved by hand: %2.0f", x);
                  id(text).publish_state(buf);
              - number.set:
                  id: volume
                  value: !lambda return id(fader_percentage).state;
  - platform: template
    name: "Volume percentage"
    id: volume_percentage
    update_interval: never
    accuracy_decimals: 0
  - platform: template
    name: "Fader percentage"
    id: fader_percentage
    filters:
      - calibrate_polynomial:
          degree: 2
          datapoints:
            - 0.16 -> 0
            - 0.21 -> 25
            - 0.30 -> 50
            - 0.39 -> 60
            - 0.50 -> 70
            - 0.75 -> 80
            - 1.37 -> 90
            - 3.30 -> 100
    update_interval: never
    accuracy_decimals: 0
    on_value_range:
      - above: 0.85
        then:
          - script.stop: moveSlider
          - script.execute: stopSlider
      - below: 0.15
        then:
          - script.stop: moveSlider
          - script.execute: stopSlider

  - platform: template
    name: "Fader deta"
    id: delta
    lambda: |-
      float goal = id(volume).state;
      float value = id(fader_percentage).state;
      return goal - value;
    update_interval: never
    accuracy_decimals: 0
    on_value:
      then:
        - number.set:
            id: speed
            value: !lambda |-
              // return 0.68;
              return map(abs(id(delta).state),0,100,0.1,0.3);

number:
  - platform: template
    name: "Motor speed"
    optimistic: true
    min_value: 0.1
    max_value: 0.8
    step: 0.01
    mode: slider
    id: speed
    on_value:
      then:
        output.set_level:
          id: motor_enable
          level: !lambda return x;
  - platform: template
    name: "Volume (goal)"
    optimistic: true
    min_value: 0
    max_value: 100
    step: 5
    mode: slider
    id: volume
    on_value:
      then:
        if:
          condition:
            binary_sensor.is_off: fader_touch
          then:
            - script.execute: set_volume

script:
  - id: on_boot
    then:
      - number.set:
          id: speed
          value: 0.28
      - number.set:
          id: volume
          value: 0.8
      - script.execute: set_volume

  - id: set_volume
    mode: single
    then:
      - lambda: |-
          id(delta).update();
          ESP_LOGD("=====>", "Set volume to: %1.2f%, delta: %1.3f, moving slider", id(volume).state, id(delta).state);
      - script.execute: moveSlider

  - id: moveSlider
    mode: single
    then:
      - while:
          condition:
            and:
              - not:
                  - for:
                      time: 1000ms
                      condition:
                        switch.is_on: motor_moving
              - lambda: |-
                  id(delta).update();
                  return abs(id(delta).state) >= 10;
              - binary_sensor.is_off: fader_touch
          then:
            # - lambda: |-
            #     ESP_LOGD("=====>", "Delta: %1.2f Volume goal: %1.2f => %1.2f slider now", id(delta).state, id(volume).state, id(fader_percentage).state);
            - if:
                condition:
                  lambda: "return id(delta).state > 0;"
                then:
                  - script.execute: moveSliderUp
                else:
                  - script.execute: moveSliderDown
            - delay: 50ms

      - lambda: |-
          ESP_LOGD("=====>", "Goal reached %1.2f", abs(id(delta).state) );
      - script.execute: stopSlider

  - id: moveSliderUp
    then:
      - switch.turn_on: motor_moving
      - switch.turn_on: motor_up_pin
      - switch.turn_off: motor_down_pin
      - lambda: |-
          char buf[200];
          sprintf(buf, "↑↑ Delta: %2.0f", id(delta).state);
          id(text).publish_state(buf);

  - id: moveSliderDown
    then:
      - switch.turn_on: motor_moving
      - switch.turn_on: motor_down_pin
      - switch.turn_off: motor_up_pin
      - lambda: |-
          char buf[200];
          sprintf(buf, "↓↓ Delta: %2.0f", id(delta).state);
          id(text).publish_state(buf);
  - id: stopSlider
    then:
      - switch.turn_off: motor_moving
      - switch.turn_off: motor_up_pin
      - switch.turn_off: motor_down_pin
      - lambda: |-
          char buf[200];
          sprintf(buf, "↭ Delta: %2.0f", id(delta).state);
          id(text).publish_state(buf);
          ESP_LOGD("=====>", "Stopped");

text_sensor:
  - platform: template
    name: "Action"
    id: text
    lambda: |-
      return {"On startup"};
    update_interval: never

esp32_touch:
  setup_mode: false

binary_sensor:
  - platform: esp32_touch
    id: fader_touch
    name: "Fader touch"
    pin: 32
    threshold: 330

output: # PWM for the motor
  - platform: ledc
    pin: 27
    frequency: 100 Hz
    id: motor_enable

switch:
  - platform: template
    name: "Motor 3 moving"
    id: motor_moving
    icon: "mdi:motion-play-outline"
    optimistic: true
  - platform: gpio
    id: motor_up_pin
    pin: 26
    name: "Motor 1 up"
    icon: "mdi:arrow-up-bold"
  - platform: gpio
    id: motor_down_pin
    pin: 25
    name: "Motor 2 down"
    icon: "mdi:arrow-down-bold"

I managed to get the pointer to go to the desired place. Finally :tada:

I drove myself almost crazy changing the frequency, speed, the way the code delays and brakes the motor. After this madness, I reached a point where it can stop in the right place with a step of 1 in 0 -100. The speed is excellent. It would be perfect if it were a little faster. It would be more visually pleasing.

I’ve polished the code, and I’m waiting for some buttons to build my switch box. I’m going to use 8 buttons and 2 faders to control the main and secondary lights in my room. The 2 lights being CCTRGB, 2 buttons for Cold Light, 2 buttons for Warm Light, 4 buttons for scenes and 2 faders for brightness.


Some things are commented out in the code, because I was using it to debug the operation. I ended up creating an entity that changed the motor’s frequency, an entity to see a counter from inside the script.



esphome:
  name: interruptor-quarto-victor
  friendly_name: Interruptor Quarto Victor
  on_boot:
    priority: 800
    then:
      - delay: 15 sec
      - switch.turn_on: calibrateFaderPrincipalSwitch

esp32:
  board: esp32dev

# Enable logging
logger:
  logs:
    sensor: NONE

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ota:
  password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  safe_mode: true
  reboot_timeout: 1min
  num_attempts: 20

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Interruptor Fallback Hotspot"
    password: !secret wifi_password

captive_portal:

globals:
 - id: maxAnalogReadFaderPrincipal
   type: float
   restore_value: no
   initial_value: '0.0'
 - id: minAnalogReadFaderPrincipal
   type: float
   restore_value: no
   initial_value: '0.0'
 - id: finalCounterFaderPrincipal
   type: int
   restore_value: no
   initial_value: '0'
 - id: frequencyMotorFaderPrincipal
   type: int
   restore_value: no
   initial_value: '100'

text_sensor:
  - platform: template
    name: "Fader 1 - Max Analog Read"
    lambda: !lambda |-
      std::string valor = std::to_string(id(maxAnalogReadFaderPrincipal));
      return valor;
    update_interval: 1s
    icon: "mdi:arrow-up-thick"
    entity_category: "diagnostic"
    
  - platform: template
    name: "Fader 1 - Min Analog Read"
    lambda: !lambda |-
      std::string valor = std::to_string(id(minAnalogReadFaderPrincipal));
      return valor;
    update_interval: 1s
    icon: "mdi:arrow-down-thick"
    entity_category: "diagnostic"

  # Debug Loop Position Counter
  # - platform: template
  #   name: "Fader 1 - Final Counter"
  #   lambda: !lambda |-
  #     std::string valor = std::to_string(id(finalCounterFaderPrincipal));
  #     return valor;
  #   update_interval: 1s
  #   icon: "mdi:counter"
  #   entity_category: "diagnostic"

  # Debug Frequency Motor
  # - platform: template
  #   name: "Fader 1 - Frequency Motor"
  #   lambda: !lambda |-
  #     std::string valor = std::to_string(id(frequencyMotorFaderPrincipal));
  #     return valor;
  #   update_interval: 50ms
  #   icon: "mdi:cosine-wave"
  #   entity_category: "diagnostic"

number:
  - platform: template
    name: Fader 1 - Input Position
    id: inputPositionFaderPrincipal
    min_value: 0
    max_value: 100
    step: 1
    optimistic: true
    mode: box
    icon: "mdi:percent"

  # Debug Frequency Motor
  # - platform: template
  #   name: Fader 1 - Input Frequency Motor
  #   id: inputFrequencyMotorFaderPrincipal
  #   min_value: 0
  #   max_value: 30000
  #   step: 1
  #   optimistic: true
  #   icon: "mdi:percent"
  #   on_value:
  #     then:
  #       - globals.set:
  #           id: frequencyMotorFaderPrincipal
  #           value: !lambda "return x;"
  #       - output.ledc.set_frequency:
  #           id: pinA
  #           frequency: !lambda "return x;"
  #       - output.ledc.set_frequency:
  #           id: pinB
  #           frequency: !lambda "return x;"

esp32_touch:
  setup_mode: false

binary_sensor:
  - platform: esp32_touch
    id: touchFaderPrincipal
    name: "Fader 1 - Touch"
    pin: GPIO33
    threshold: 330
    icon: "mdi:gesture-tap"
 
switch:
  - platform: template
    name: "Fader 1 - Go To Position"
    id: goToPositionFaderPrincipal
    optimistic: true
    icon: "mdi:location-enter"
    lambda: |-
      return id(goToPositionFaderPrincipal).state;
    on_turn_on:
      then:
        - script.execute: goToPositionFaderPrincipalScript
    on_turn_off:
      then:
        - script.stop: goToPositionFaderPrincipalScript

  - platform: template
    name: "Fader 1 - Calibrate Fader"
    id: calibrateFaderPrincipalSwitch
    optimistic: true
    entity_category: "config"
    icon: "mdi:tape-measure"
    lambda: |-
      return id(calibrateFaderPrincipalSwitch).state;
    on_turn_on:
      then:
        - script.execute: calibrateFaderPrincipalScript
    on_turn_off:
      then:
        - script.stop: calibrateFaderPrincipalScript

output:
  - platform: ledc
    id: pinA
    pin: GPIO16
  - platform: ledc
    id: pinB
    pin: GPIO17

fan:
  - platform: hbridge
    id: motorFaderPrincipal
    name: "Fader 1 - Motor"
    pin_a: pinA
    pin_b: pinB
    decay_mode: slow
    icon: "mdi:engine"
    internal: true

i2c:
  sda: 22
  scl: 21
  scan: true
  id: bus_a

ads1115:
  - address: 0x48

sensor:
  - platform: ads1115
    multiplexer: 'A0_GND'
    gain: 6.144
    name: "Fader 1 - Analog Read"
    update_interval: 5ms
    id: analogReadFaderPrincipal
    filters:
      - median:
          window_size: 5
          send_every: 1
    accuracy_decimals: 3
    entity_category: "diagnostic"

    # Map from minAnalogReadFaderPrincipal - maxAnalogReadFaderPrincipal to 0 - 100 with corrections. In this case 1%.
  - platform: template
    name: "Fader 1 - Percentage"
    id: percentageFaderPrincipal
    update_interval: 5ms
    accuracy_decimals: 0
    icon: "mdi:percent"
    lambda: |-
      int percentage = (id(analogReadFaderPrincipal).state - (id(minAnalogReadFaderPrincipal) + ((id(minAnalogReadFaderPrincipal) / 100) * 1))) * (100 - 0) / (((id(maxAnalogReadFaderPrincipal) - ((id(maxAnalogReadFaderPrincipal) / 100) * 1)) - 0.00) - (id(minAnalogReadFaderPrincipal) + ((id(minAnalogReadFaderPrincipal) / 100) * 1))) + 0;

      if (percentage < 0) {
        return 0;
      }

      if (percentage > 100) {
        return 100;
      }

      return percentage;

  - platform: wifi_signal
    name: "Sinal WiFi"
    id: wifi_signal_db
    update_interval: 20s
    entity_category: "diagnostic"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"

  - platform: uptime
    update_interval: 10s
    name: Uptime

script:
  - id: calibrateFaderPrincipalScript
    then:
      - logger.log: "!! Starting Fader Calibration  !!"
      - globals.set:
          id: frequencyMotorFaderPrincipal
          value: "1000"
      - output.ledc.set_frequency:
          id: pinA
          frequency: 1000Hz
      - output.ledc.set_frequency:
          id: pinB
          frequency: 1000Hz
      - logger.log: "!! Calibrating Fader !!"
      - fan.turn_on:
          id: motorFaderPrincipal
          speed: 55
          direction: forward
      - delay: 3 sec
      - fan.turn_off: motorFaderPrincipal
      - delay: 3 sec
      - globals.set:
          id: maxAnalogReadFaderPrincipal
          value: !lambda "return id(analogReadFaderPrincipal).state;"
      - fan.turn_on:
          id: motorFaderPrincipal
          speed: 55
          direction: reverse
      - delay: 3 sec
      - fan.turn_off: motorFaderPrincipal
      - delay: 3 sec
      - globals.set:
          id: minAnalogReadFaderPrincipal
          value: !lambda "return id(analogReadFaderPrincipal).state;"
      - logger.log: "!! Fader Calibration Completed !!"
      - switch.turn_off: calibrateFaderPrincipalSwitch

  - id: goToPositionFaderPrincipalScript
    then:
      - wait_until:
          condition:
            - lambda: |-
                int inputPositionFaderPrincipalFormatted = std::floor(id(inputPositionFaderPrincipal).state);
                int inputPositionFaderPrincipalFormattedPlus = inputPositionFaderPrincipalFormatted + 10;
                int inputPositionFaderPrincipalFormattedMinus = inputPositionFaderPrincipalFormatted - 10;
                int percentageFaderPrincipalFormatted = std::floor(id(percentageFaderPrincipal).state);

                if (inputPositionFaderPrincipalFormatted == percentageFaderPrincipalFormatted) {
                  auto call = id(motorFaderPrincipal).brake();
                  call.perform();

                  delay(100);

                  id(finalCounterFaderPrincipal) = id(finalCounterFaderPrincipal) + 1;
                      
                  if (id(finalCounterFaderPrincipal) >= 20){
                    id(finalCounterFaderPrincipal) = 0;
                    return true;
                  } else {
                    return false;
                  }
                }

                if (inputPositionFaderPrincipalFormatted < percentageFaderPrincipalFormatted and inputPositionFaderPrincipalFormattedPlus < percentageFaderPrincipalFormatted) {
                  ESP_LOGD("custom", "---------------------------------------------------------");
                  ESP_LOGD("custom", "!!! DECREASING !!!");
                  ESP_LOGD("custom", "inputPositionFaderPrincipal: %d", inputPositionFaderPrincipalFormatted);
                  ESP_LOGD("custom", "percentageFaderPrincipal: %d", percentageFaderPrincipalFormatted);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedPlus: %d", inputPositionFaderPrincipalFormattedPlus);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedMinus: %d", inputPositionFaderPrincipalFormattedMinus);
                  ESP_LOGD("custom", "---------------------------------------------------------");

                  id(frequencyMotorFaderPrincipal) = 25;
                  int freq = id(frequencyMotorFaderPrincipal);
                  id(pinA).update_frequency(freq);
                  id(pinB).update_frequency(freq);

                  auto call = id(motorFaderPrincipal).turn_on();
                  call.set_speed(5);
                  call.set_direction(FanDirection::REVERSE);
                  call.perform();

                  id(finalCounterFaderPrincipal) = 0;

                  return false;
                }
                if (inputPositionFaderPrincipalFormatted < percentageFaderPrincipalFormatted and inputPositionFaderPrincipalFormattedPlus > percentageFaderPrincipalFormatted) {
                  ESP_LOGD("custom", "---------------------------------------------------------");
                  ESP_LOGD("custom", "!!! DECREASING AND GETTING CLOSER !!!");
                  ESP_LOGD("custom", "inputPositionFaderPrincipal: %d", inputPositionFaderPrincipalFormatted);
                  ESP_LOGD("custom", "percentageFaderPrincipal: %d", percentageFaderPrincipalFormatted);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedPlus: %d", inputPositionFaderPrincipalFormattedPlus);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedMinus: %d", inputPositionFaderPrincipalFormattedMinus);
                  ESP_LOGD("custom", "---------------------------------------------------------");

                  id(frequencyMotorFaderPrincipal) = 20;
                  int freq = id(frequencyMotorFaderPrincipal);
                  id(pinA).update_frequency(freq);
                  id(pinB).update_frequency(freq);

                  auto call = id(motorFaderPrincipal).turn_on();
                  call.set_speed(3);
                  call.set_direction(FanDirection::REVERSE);
                  call.perform();

                  id(finalCounterFaderPrincipal) = 0;

                  delay(25);

                  auto call2 = id(motorFaderPrincipal).brake();
                  call2.perform();

                  return false;
                }

                if (inputPositionFaderPrincipalFormatted > percentageFaderPrincipalFormatted and inputPositionFaderPrincipalFormattedMinus > percentageFaderPrincipalFormatted) {
                  ESP_LOGD("custom", "---------------------------------------------------------");
                  ESP_LOGD("custom", "!!! INCREASING !!!");
                  ESP_LOGD("custom", "inputPositionFaderPrincipal: %d", inputPositionFaderPrincipalFormatted);
                  ESP_LOGD("custom", "percentageFaderPrincipal: %d", percentageFaderPrincipalFormatted);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedPlus: %d", inputPositionFaderPrincipalFormattedPlus);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedMinus: %d", inputPositionFaderPrincipalFormattedMinus);
                  ESP_LOGD("custom", "---------------------------------------------------------");

                  id(frequencyMotorFaderPrincipal) = 25;
                  int freq = id(frequencyMotorFaderPrincipal);
                  id(pinA).update_frequency(freq);
                  id(pinB).update_frequency(freq);

                  auto call = id(motorFaderPrincipal).turn_on();
                  call.set_speed(5);
                  call.set_direction(FanDirection::FORWARD);
                  call.perform();

                  id(finalCounterFaderPrincipal) = 0;

                  return false;
                }
                if (inputPositionFaderPrincipalFormatted > percentageFaderPrincipalFormatted and inputPositionFaderPrincipalFormattedMinus < percentageFaderPrincipalFormatted) {
                  ESP_LOGD("custom", "---------------------------------------------------------");
                  ESP_LOGD("custom", "!!! INCREASING AND GETTING CLOSER !!!");
                  ESP_LOGD("custom", "inputPositionFaderPrincipal: %d", inputPositionFaderPrincipalFormatted);
                  ESP_LOGD("custom", "percentageFaderPrincipal: %d", percentageFaderPrincipalFormatted);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedPlus: %d", inputPositionFaderPrincipalFormattedPlus);
                  ESP_LOGD("custom", "inputPositionFaderPrincipalFormattedMinus: %d", inputPositionFaderPrincipalFormattedMinus);
                  ESP_LOGD("custom", "---------------------------------------------------------");

                  id(frequencyMotorFaderPrincipal) = 20;
                  int freq = id(frequencyMotorFaderPrincipal);
                  id(pinA).update_frequency(freq);
                  id(pinB).update_frequency(freq);

                  auto call = id(motorFaderPrincipal).turn_on();
                  call.set_speed(3);
                  call.set_direction(FanDirection::FORWARD);
                  call.perform();

                  id(finalCounterFaderPrincipal) = 0;

                  delay(25);

                  auto call2 = id(motorFaderPrincipal).brake();
                  call2.perform();

                  return false;
                }
      - fan.turn_off: motorFaderPrincipal
      - switch.turn_off: goToPositionFaderPrincipal

            ```

I also didn’t like the motor appearing as a fan. I ended up disabling it in HA, I left it internal to esphome just to make it easier for me to code the script :laughing:


I haven’t linked the fader to any lights yet. In the next few days I’ll set this up and see if it creates any problems, and I’ll also set up an automation to only send the selected number when I release the touch.

I’ll also make the connections for the second fader and make sure they don’t interfere with each other.


I can already see that this is going to be very complex. Only with an open source miracle we will be able to get to this. I’ve almost gone full madness just by scripting it to the desired position. :joy: :joy: :joy:


When the project is more visually acceptable, I’ll post a picture here. For now, it’s on a protoboard and all the hardware over my table :joy: :joy:

1 Like

Hi Vitinho

How did this project turn out? I am considering making it myself to control volume on my Sonos.

I’m also very interested in how this turned out. Did you make any progress?
I hope to make a sound control board for my living room with faders and knobs that control my receiver and tv…