How do you read a potentiometer to get the position of a valve?

After spending hours of searching I’m finally ready to give up and ask for help.

I can’t find how to use the variable voltage given out by a potentiometer to tell ESPHome the position of a valve (or even cover).

Here’s the device I’m trying to control:

I have a motorised two position valve that is rotated with a simple DC motor through a wormdrive gearbox. There is a potentiometer connected to the valve’s rotating core. When you apply 12v the core rotates until it hits the end of motion and stalls. The potentiometer outputs 0.5v at this point. When you apply the reverse voltage, the core rotates until it stops and gives out 4.8v.

Every example and help page I’ve found so far only documents how to use some form of binary end stop switch to detect the end position of either a cover or valve. How can I use an analogue voltage given out by a potentiometer to determine the position?

Once I get it sorted I’ll post the full code here for others, as I don’t think there’s a single example of this on the internet yet.

Cheers.

With ADC component ? You’ll have to modify input voltage a bit, though. ESP accepts up to 3.3V if i’m not mistaken, while you have 4.8V, so a resistor voltage divider in between and you’re done.

1 Like

Better than that, I’ll just supply the potentiometer with 3.3v from the ESP instead of the current 5v :slight_smile:

And yeah I found the ADC component in my searching, but I can’t work out how to integrate it into either a cover or valve integration. Could you give me some help? It doesn’t need to be 100% functional code, just a rough example should help me on my way.

I think with Template Cover — ESPHome

Or Template Valve — ESPHome

I think on_value: of the ADC sensor you publish the position to a valve or cover.

You process the ADC sensor a bit with a lambda or filter to get it between 0 and 1.

So kind of a combo of what Nick and Pavel said.

Can anyone give me an example of how that code would look? Even just example code blocks pasted together in the right order might be enough to set me on my journey.

At the moment I’m struggling to work out how to get the various parts to talk to eachother.

Hello,

maybe something like:

#measure the voltage
sensor:
  - platform: adc
    pin: GPIOXX
    name: "potentiometer"
    id: potentiometer
  - platform: adc
    sensor: potentiometer
    name: “valve percent”
    unit_of_measurement: “%”
    filters:
      - calibrate_linear:
          - 0.5 -> 0.00
          - 3.1 -> 100.00
  
#binary sensor is open or closed based on threshold value
binary_sensor:
  - platform: analog_threshold
    name: "valve"
    id: valve
    sensor_id: potentiometer
    threshold: 2.5

valve:
  - platform: template
    name: "Valve"
    lambda: |-
      if (id(valve).state) =1 {
        return VALVE_OPEN;
      } else {
        return VALVE_CLOSED;
      }
     

And I am looking for any suggestion and improvement, because I am looking for the same kind of solution, but not sure the code above is correct.
Will it show as a valve?

I think it would be similar to this.

sensor:
  - platform: adc
    id: adc_raw
    name: "ADC Raw"    
    pin: GPIOXX
    update_interval: 100ms
    internal: true
  - platform: copy
    source_id: adc_raw
    id: adc_processed
    name: "ADC Processed"   
    internal: false
    filters:
      - median: # Smooth noise.
          window_size: 10
          send_every: 10
          send_first_at: 10
      - calibrate_linear:
          - 0.5 -> 0.00
          - 3.1 -> 1.0
      - lambda: |
          if (x > 1) return 1; 
          else if (x < 0) return 0;
          else return x;
    on_value:
      - valve.template.publish:
          id: my_valve
          position: !lambda 'return x;'
          
valve:
  - platform: template
    name: "Template Valve"
    id: my_valve
    has_position: true
    lambda: |-
      if (id(adc_processed).state == 1) {
        return VALVE_OPEN;
      } else {
        return VALVE_CLOSED;
      }
    open_action:
      - switch.turn_on: open_valve_switch
    close_action:
      - switch.turn_on: close_valve_switch
    stop_action:
      - switch.turn_on: stop_valve_switch
    optimistic: true

If you don’t really need valve position, just open/closed, you could do it more like Ettore suggested.

1 Like

Yeah I only need the valve to be either open or closed. I’ll have a go at combining the Ettore method with your, not sure how I’ll get on though.

If you have any hints on combining the methods, I’m all ears.

Then you can try like this:

sensor:
  - platform: adc
    id: adc_raw
    name: "ADC Raw"
    pin: GPIOXX
    update_interval: 100ms
    internal: true
  - platform: copy
    source_id: adc_raw
    id: adc_processed_percent
    name: "ADC Processed Percent"
    internal: false
    filters:
      - median:  # Smooth noise
          window_size: 10
          send_every: 10
          send_first_at: 10
      - calibrate_linear:  # Map raw input to percentage
          - 0.5 -> 0.0
          - 3.1 -> 100.0
      - lambda: |-
          if (x > 100) return 100.0;
          else if (x < 0) return 0.0;
          else return x;

valve:
  - platform: template
    name: "Template Valve"
    id: my_valve
    lambda: |-
      if (id(adc_processed_percent).state >= 100) {
        return VALVE_OPEN;
      } else {
        return VALVE_CLOSED;
      }
    optimistic: true

There are a few different ways to skin the cat. Technically you could skip the whole copy sensor and processing all together, but practically with ADC you probably need it.

Awesome! Thank you for that. And any hints on getting this template to turn the valve via a LN298 motor driver?

In case it helps, this is the Arduino sketch I’m currently using to control the valve with the motor driver. You press one button and it opens, you press another button and it closes.

// Define the motor control pins
#define MOTOR_PIN1  9    // Motor driver input 1
#define MOTOR_PIN2  10   // Motor driver input 2
#define MOTOR_PWM   11   // Motor PWM (speed control)

// Define the button pins
#define BUTTON_PIN1 2    // Button 1 (Position 1)
#define BUTTON_PIN2 3    // Button 2 (Position 2)

// Define the potentiometer pin
#define POT_PIN A0       // Potentiometer to read valve position

// Define the target potentiometer values for the two positions
#define POSITION1 50    // Potentiometer value for position 1 (adjust as needed)
#define POSITION2 930    // Potentiometer value for position 2 (adjust as needed)
//910 is perfect spot for middle outlet flow
//850 is point that end connection is sealed
//113 is when middle is unsealed

int currentPos = 0;       // Current position of the potentiometer
int targetPos = 0;        // Target position based on button input
bool moving = false;      // Motor movement state

void setup() {
  // Initialize motor pins
  pinMode(MOTOR_PIN1, OUTPUT);
  pinMode(MOTOR_PIN2, OUTPUT);
  pinMode(MOTOR_PWM, OUTPUT);
  
  // Initialize button pins
  pinMode(BUTTON_PIN1, INPUT_PULLUP);  // Using internal pull-up resistors
  pinMode(BUTTON_PIN2, INPUT_PULLUP);
  
  // Start serial communication for debugging
  Serial.begin(9600);
}

void loop() {
  // Read the current potentiometer value
  currentPos = analogRead(POT_PIN);
  Serial.print("Current Position: ");
  Serial.println(currentPos);

  // Read button states
  if (digitalRead(BUTTON_PIN1) == LOW) {
    targetPos = POSITION1;
    moving = true;
    Serial.print("button one pressed");
  }
  if (digitalRead(BUTTON_PIN2) == LOW) {
    targetPos = POSITION2;
    moving = true;
    Serial.print("button two pressed");
  }

  // Move the motor to the target position
  if (moving) {
    moveToPosition(targetPos);
  }
}

// Function to move motor to target position
void moveToPosition(int target) {
  int error = target - currentPos; // Calculate the error

  // If error is small enough, stop moving
  if (abs(error) < 20) {
    stopMotor();
    moving = false;
    Serial.println("Position reached!");
  } else {
    // If target is greater than current, rotate motor forward
    if (error > 0) {
      digitalWrite(MOTOR_PIN1, HIGH);
      digitalWrite(MOTOR_PIN2, LOW);
      analogWrite(MOTOR_PWM, 255);  // Full speed (adjust as needed)
      Serial.println("motor forwards");
    } 
    // If target is less than current, rotate motor backward
    else {
      digitalWrite(MOTOR_PIN1, LOW);
      digitalWrite(MOTOR_PIN2, HIGH);
      analogWrite(MOTOR_PWM, 255);  // Full speed (adjust as needed)
      Serial.println("motor backwards");
    }
  }
}

// Function to stop the motor
void stopMotor() {
  analogWrite(MOTOR_PWM, 0);  // Stop motor
  digitalWrite(MOTOR_PIN1, LOW);
  digitalWrite(MOTOR_PIN2, LOW);
}