ESP32 tank KH controller

When I connect to the web portal of the ESP module, I toggle the on/off button, and it works.

From the code you gave me, I have modified it fot the fan, but I do not have a GPIO platform set, which I don’t have for the fan, I only have the PWM component.

What about the speed control?

The speed control is fine too.

I’m little bit confused now. What is ok / not ok with stirrer (excluding automations)?

In the web portal of the ESP module, everything works properly (on/off and speed), like in HA.

That is why I do not understand why it doesn’t work with the automation.

I have modified the code you gave me to make it fit with the stirrer fan :

fan:
# Stirrer Fan
  - platform: speed
    output: khcontrollerfan_pwm
    id: stirrer
    name: "Stirrer"
    on_turn_on:
      then:
        - output.turn_on: khcontrollerfan_pwm
    on_turn_off:
      then:
        - output.turn_off: khcontrollerfan_pwm
  
output:
# KHcontroller Fan
  - platform: ledc
    pin: GPIO19
    frequency: 25000Hz
    id: khcontrollerfan_pwm

For the fan, I do not have a platform: gpio output component, so I think why it doesn’t start, that is why I thought I might need a module to turn it on.

You just need patience.

So you have basic working setup:

output:
  - platform: gpio
    pin: GPIO32
    id: a_in1
  - platform: gpio
    pin: GPIO33
    id: a_in2
  - platform: ledc
    pin: GPIO27
    id: a_enable_pwm
    frequency: 1000
  - platform: ledc
    pin: GPIO19
    frequency: 25000Hz
    id: khcontrollerfan_pwm

You can build your whole automation with these outputs without of with additional esphome components. If you like to have fan components you can use them, but they are not needed anyhow.

It’s late here, my automation is switching off. Think what you need to automate according to those output pins. On/off/direction/speed.

You shouldn’t need it.
Speed zero should turn it off. Control your wiring.
According to your previous post I thought it was working.

Combining lot of thigs together might look efficient way, but it’s not. If you don’t resolve the problems one by one, it’s becoming a lottery.

I have read again the topic to find my mistake but I really don’t know if the wiring is the problem because I think I did as you told me to do :

Maybe I forgot about something?

I will try to find something, thanks for your help!

I don’t understand what you mean exactly…
If you only have minimum setup, does it work or not? Can you regulate the speed? Down to zero?

fan:
# Stirrer Fan
  - platform: speed
    output: khcontrollerfan_pwm
    id: stirrer
    name: "Stirrer"
    
  
output:
# KHcontroller Fan
  - platform: ledc
    pin: GPIO19
    frequency: 25000Hz
    id: khcontrollerfan_pwm

Yes it works under the web portal and Home Assistant. I can turn it on and off, regulate speed up and down.

So your hardware is working and wired correctly.

Those are the only components that you need to control one pump and stirrer.
Gpio outputs control on/off/direction and ledc outputs control speed.
You don’t need fan components (if you don’t want) to make automations. On your script (or other automation):

 - output.turn_on: khcontrollerfan_pwm
 - output.set_level:
     id: khcontrollerfan_pwm
     level: 10%

will start the stirrer at 10% speed
and:

 - output.turn_on: a_in1
 - output.turn_off: a_in2
 - output.set_level:
     id: a_enable_pwm
     level: 50%

will start the pump at 50% speed.
and:

 - output.turn_off: a_in1
 - delay: 1s
 - output.turn_on: a_in2

would change the direction of pump.

The problem is that even if everything works when I use them in web portal or in HA, once I try to use them inside an automation, it always freezes the logs at the moment the script have to start the stirrer fan. And I don’t understand why it freezes the script because as you said, everything is wired correctly.

The thing is, I have GPIO outputs for the pumps, and control the direction with LEDC component. But for the fan, I only have LEDC component, no GPIO output so I wonder if it is not the reason why the fan doesn’t start during the automation in ESPhome.

- output.turn_on: khcontrollerfan_pwm
- output.set_level:
     id: khcontrollerfan_pwm
     level: 10%

I have already tested those lines in the script, an it doesn’t start the fan… I have commented every lines in the script except for the stirrer fan to check and it is working! I will try with the whole script now.

And while it might kind of work, that is wrong way for that driver. Direction should be controlled with gpio output component.

Because fan doesn’t have direction control.

I think I know why the fan doesn’t start, I have this line in the script as logger.log :

  - logger.log: "Step 4: Starting Stirrer at '10%' speed"

This sentence why underlined by ESPHome because of the “%”, so I decided to add an apostrophe but I think it is no correct for the script.

Yep. You need to use %% in printf.

I changed the sign for plain text, it will be easier :sweat_smile:

I will try to complete the script, I saw that global variables give the possibility to “store” data inside the esp module. I think that’s what I need for the esp to be able to keep trace of how many mL the pump will be adding to the beaker.

Hi,

All little bit of news from the project.

I have stopped trying to make things work in ESPhome as I do not manage to get what I want, so I started coding in HA getting inspiration from other projects.

For now, I have managed to make everything work smooth, I have added a LM2596S module to power the ESP module in 3.3V to only have one power source.

The calibration part for the pump is over and working. I still need to calibrate the pH probe, but I will do it when the project will be almost finished, so the probe would remain in tank water sample.

The big part will be to automate the KH test. I have already started the basics, I still need to figure out how the data will be saved into HA for an automation to be able to run a calculation which will give me the KH value depending on the water sample and acid amount poured into the beaker. I will post an update of the project once it will be done.

Here is the yaml code (input_boolean, input_number, cards, …) :

#######################################################################################################
#                                               cards                                                 #
#######################################################################################################

type: vertical-stack
cards:
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: back
      - cards:
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_ph
                - entity: input_boolean.calib_watersamplepump
                - entity: input_boolean.calib_acidpump
              show_header_toggle: false
              title: KH Controller
              type: entities
            conditions:
              - entity: input_boolean.calib_ph
                state: "off"
              - entity: input_boolean.calib_acidpump
                state: "off"
              - entity: input_boolean.calib_watersamplepump
                state: "off"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                - entity: sensor.reef_ph
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_ph
                - entity: input_select.c_calib_ph4
                - entity: input_number.v_calib_mv4
                - entity: input_select.c_calib_ph7
                - entity: input_number.v_calib_mv7
                - entity: input_select.c_calib_ph9_10
                - entity: input_number.v_calib_mv9
              show_header_toggle: false
              title: Reef pH (KH Controller) Calib mode
              type: entities
            conditions:
              - entity: input_boolean.calib_ph
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_watersamplepump
                - entity: input_number.khcontroller_watersamplepump_calibration_time
                - entity: input_boolean.calib_watersamplepump_start
                - entity: input_number.khcontroller_watersamplepump_calibration_dosage
              show_header_toggle: false
              title: Calibration Water sample pump
              type: entities
            conditions:
              - entity: input_boolean.calib_watersamplepump
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_acidpump
                - entity: input_number.khcontroller_acidpump_calibration_time
                - entity: input_boolean.calib_acidpump_start
                - entity: input_number.khcontroller_acidpump_calibration_dosage
              show_header_toggle: false
              title: Calibration Acid pump
              type: entities
            conditions:
              - entity: input_boolean.calib_acidpump
                state: "on"
            type: conditional
        type: vertical-stack
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: template
      - cards:
          - card:
              entities:
                - entity: fan.reefdoser_kh
                - entity: input_boolean.r1p1
                - entity: input_boolean.calibr1p1on
              show_header_toggle: false
              title: KH - R1P1
              type: entities
            conditions:
              - entity: input_boolean.r1p1
                state: "off"
            title: Réveil - Aube
            type: conditional
          - card:
              entities:
                - entity: fan.reefdoser_kh
                - entity: input_boolean.r1p1
                - entity: input_number.reefdoser1_pump1_daily_mins
                - entity: input_select.reefdoser1_pump1_daily_freq
                - entity: input_number.reefdoser1_pump1_daily_dosage
                - entity: sensor.reefdoser1_pump1_dosage_split
                - entity: sensor.reefdoser1_pump1_daily_runtime
                - entity: sensor.reefdoser1_pump1_time_split
                - entity: sensor.reefdoser1
                - entity: input_boolean.calibr1p1on
              show_header_toggle: false
              title: Kh - R1P1
              type: entities
            conditions:
              - entity: input_boolean.r1p1
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: input_number.reefdoser1_pump1_calibration_time
                - entity: input_boolean.calibr1p1start
                - entity: input_number.reefdoser1_pump1_calibration_dosage
              show_header_toggle: false
              title: Calibration Kh - R1P1
              type: entities
            conditions:
              - entity: input_boolean.calibr1p1on
                state: "on"
            type: conditional
        type: vertical-stack
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: template
      - cards:
          - card:
              entities:
                - entity: fan.reefdoser_ca
                - entity: input_boolean.r1p2
                - entity: input_boolean.calibr1p2on
              show_header_toggle: false
              title: CA - R1P2
              type: entities
            conditions:
              - entity: input_boolean.r1p2
                state: "off"
            title: Réveil - Aube
            type: conditional
          - card:
              entities:
                - entity: fan.reefdoser_ca
                - entity: input_boolean.r1p2
                - entity: input_number.reefdoser1_pump2_daily_mins
                - entity: input_select.reefdoser1_pump2_daily_freq
                - entity: input_number.reefdoser1_pump2_daily_dosage
                - entity: sensor.reefdoser1_pump2_dosage_split
                - entity: sensor.reefdoser1_pump2_daily_runtime
                - entity: sensor.reefdoser1_pump2_time_split
                - entity: sensor.reefdoser2
                - entity: input_boolean.calibr1p2on
              show_header_toggle: false
              title: CA - R1P2
              type: entities
            conditions:
              - entity: input_boolean.r1p2
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: input_number.reefdoser1_pump2_calibration_time
                - entity: input_boolean.calibr1p2start
                - entity: input_number.reefdoser1_pump2_calibration_dosage
              show_header_toggle: false
              title: Calibration CA - R1P2
              type: entities
            conditions:
              - entity: input_boolean.calibr1p2on
                state: "on"
            type: conditional
        type: vertical-stack
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: template
      - cards:
          - card:
              entities:
                - entity: fan.reefdoser_mg
                - entity: input_boolean.r1p3
                - entity: input_boolean.calibr1p3on
              show_header_toggle: false
              title: MG - R1P3
              type: entities
            conditions:
              - entity: input_boolean.r1p3
                state: "off"
            title: Réveil - Aube
            type: conditional
          - card:
              entities:
                - entity: fan.reefdoser_mg
                - entity: input_boolean.r1p3
                - entity: input_number.reefdoser1_pump3_daily_mins
                - entity: input_select.reefdoser1_pump3_daily_freq
                - entity: input_number.reefdoser1_pump3_daily_dosage
                - entity: sensor.reefdoser1_pump3_dosage_split
                - entity: sensor.reefdoser1_pump3_daily_runtime
                - entity: sensor.reefdoser1_pump3_time_split
                - entity: sensor.reefdoser3
                - entity: input_boolean.calibr1p3on
              show_header_toggle: false
              title: MG - R1P3
              type: entities
            conditions:
              - entity: input_boolean.r1p3
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: input_number.reefdoser1_pump3_calibration_time
                - entity: input_boolean.calibr1p3start
                - entity: input_number.reefdoser1_pump3_calibration_dosage
              show_header_toggle: false
              title: Calibration MG - R1P3
              type: entities
            conditions:
              - entity: input_boolean.calibr1p3on
                state: "on"
            type: conditional
        type: vertical-stack
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: template
      - cards:
          - card:
              entities:
                - entity: fan.reefdoser_oligos
                - entity: input_boolean.r1p4
                - entity: input_boolean.calibr1p4on
              show_header_toggle: false
              title: Oligos - R1P4
              type: entities
            conditions:
              - entity: input_boolean.r1p4
                state: "off"
            title: Réveil - Aube
            type: conditional
          - card:
              entities:
                - entity: switch.reefdoser_oligos
                - entity: input_boolean.r1p4
                - entity: input_number.reefdoser1_pump4_daily_mins
                - entity: input_select.reefdoser1_pump4_daily_freq
                - entity: input_number.reefdoser1_pump4_daily_dosage
                - entity: sensor.reefdoser1_pump4_dosage_split
                - entity: sensor.reefdoser1_pump4_daily_runtime
                - entity: sensor.reefdoser1_pump4_time_split
                - entity: sensor.reefdoser4
                - entity: input_boolean.calibr1p4on
              show_header_toggle: false
              title: Oligos - R1P4
              type: entities
            conditions:
              - entity: input_boolean.r1p4
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: input_number.reefdoser1_pump4_calibration_time
                - entity: input_boolean.calibr1p4start
                - entity: input_number.reefdoser1_pump4_calibration_dosage
              show_header_toggle: false
              title: Calibration Oligos - R1P4
              type: entities
            conditions:
              - entity: input_boolean.calibr1p4on
                state: "on"
            type: conditional
        type: vertical-stack


###########################################################################################
#                                    Input Boolean                                        #
###########################################################################################

# KH tests auto
  kh_test:
    name: "KH test : Mode auto"
    icon: mdi:arrow-decision-auto-outline
# Calib pH
  calib_ph:
    name: "Calibration sonde pH"
    icon: mdi:tape-measure
# Water sample pump calibration 
  calib_watersamplepump:
    name: "Calibration pompe échantillon"
    icon: mdi:tape-measure
  calib_watersamplepump_start:
    name: "start calibration for 15 sec"
    icon: mdi:clock-start
# Acid pump calibration 
  calib_acidpump:
    name: "Calibration pompe acide"
    icon: mdi:tape-measure
  calib_acidpump_start:
    name: "start calibration for 15 sec"
    icon: mdi:clock-start


#############################################################################################
#                                      Input number                                         #
#############################################################################################

#Calibration sonde pH
  v_calib_mv4:
    name: Calibration mV à 4 
    icon: mdi:gauge
#    initial: 4.504
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box
  v_calib_mv7:
    name: Calibration mV à 7 
    icon: mdi:gauge
#    initial: 4.013
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box
  v_calib_mv9:
    name: Calibration mV à 9
    icon: mdi:gauge
  #  initial: 3.703
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box

#Calibration pompe doseuse 1
#    initial: 00
  khcontroller_watersamplepump_calibration_dosage:
    name: Résultat calibration en ml
    min: 1
    max: 100
    step: 0.5
    unit_of_measurement: ml
    mode: box
    icon: mdi:flask-outline
#    initial: 30
  khcontroller_watersamplepump_calibration_time:
    name: Durée calibration en sec
    min: 1
    max: 60
    step: 1
    unit_of_measurement: sec
    icon: mdi:timer-sand
#    initial: 30

# Quantité ajout échantillon eau de mer
  khcontroller_watersamplepump_dosage:
    name: Ajouts échantillon
    min: 1
    max: 500
    step: 1
    unit_of_measurement: ml
    icon: mdi:beaker

#Calibration pompe doseuse 2
#    initial: 00
  khcontroller_acidpump_calibration_dosage:
    name: Résultat calibration en ml
    min: 1
    max: 100
    step: 0.5
    unit_of_measurement: ml
    mode: box
    icon: mdi:flask-outline
#    initial: 30
  khcontroller_acidpump_calibration_time:
    name: Durée calibration en sec
    min: 1
    max: 60
    step: 1
    unit_of_measurement: sec
    icon: mdi:timer-sand
#    initial: 30

# Quantité ajout acide chlorhydrique
  khcontroller_acidepump_dosage:
    name: Ajouts acide
    min: 1
    max: 500
    step: 1
    unit_of_measurement: ml
    icon: mdi:beaker

######################################################################################
#                                 Input select                                       #
######################################################################################

#Calibration de la sonde pH
  c_calib_ph4:
    name: Calibration pH4
    icon: mdi:adjust
    options:
      - "4.00"
      - "4.01"
  c_calib_ph7:
    name: Calibration pH7
    icon: mdi:adjust
    options:
      - "6.86 "
      - "7.00"
      - "7.01"
  c_calib_ph9_10:
    name: Calibration pH9-10
    icon: mdi:adjust
    options:
      - "9.00"
      - "9.18"
      - "9.21"
      - "10.00"
      - "10.01"


#######################################################################################
#                                       Sensors                                       #
#######################################################################################

  - platform: template
    sensors:
# ph_meter ONLINE status sensors
      kh_controller:
        value_template: '{% if is_state("binary_sensor.192_168_1_100", "on") %}Online{% else %}offline{% endif %}'
        friendly_name: 'État KH_controller'
        icon_template: >-
          {% if is_state('binary_sensor.192_168_1_100', 'on') %}
            mdi:power-plug
          {% else %}
            mdi:power-plug-off
          {% endif %}
#Conversion de valeur mV à pH
      reef_ph:   
        friendly_name: "Valeur PH"
        icon_template: mdi:ph
        value_template: '{{ (states.input_select.c_calib_ph9_10.state | float) - ( (((states.input_number.v_calib_mv9.state | float)-(states.sensor.kh_controller_reef_ph_khcontroller.state | float))*((states.input_select.c_calib_ph7.state | float)-(states.input_select.c_calib_ph9_10.state | float))) / ((states.input_number.v_calib_mv7.state | float)-(states.input_number.v_calib_mv9.state | float)) ) | round(2) }}'
#      xc  (states.sensor.kh_controller_reef_ph_khcontroller.state | float)
#          (states.input_select.c_calib_ph4.state | float)
#      ya  (states.input_select.c_calib_ph7.state | float)
#      yb  (states.input_select.c_calib_ph9_10.state | float)
#          (states.input_number.v_calib_mv4.state | float)
#      xa  (states.input_number.v_calib_mv7.state | float)
#      xb  (states.input_number.v_calib_mv9.state | float)
#########################################################ph_meter


#############################################################################################
#                                           groups                                          #
#############################################################################################

  khcontroller:
    name: KH controller
    entities:
      - input_boolean.kh_test
      - input_boolean.calib_ph
      - input_number.v_calib_mv4
      - input_number.v_calib_mv7
      - input_number.v_calib_mv9
      - input_select.c_calib_ph4
      - input_select.c_calib_ph7
      - input_select.c_calib_ph9_10
      - sensor.kh_controller
      - sensor.reef_ph

  watersamplepump:
    name: Water Sample pump
    entities:
      - input_boolean.calib_watersamplepump
      - input_boolean.calib_watersamplepump_start
      - input_number.khcontroller_watersample_pump_calibration_dosage
      - input_number.khcontroller_watersamplepump_calibration_time
      - input_number.khcontroller_watersamplepump_dosage
      - sensor.khcontroller_watersamplepump_runtime
      - sensor.khcontroller_watersamplepump_dosage

  acidpump:
    name: Acid pump
    entities:
      - input_boolean.calib_acidpump
      - input_boolean.calib_acidpump_start
      - input_number.khcontroller_acidpump_calibration_dosage
      - input_number.khcontroller_acidpump_calibration_time
      - input_number.khcontroller_acidpump_dosage
      - sensor.khcontroller_acidpump_runtime
      - sensor.khcontroller_acidpump_dosage


##################################################################################
#                                  Automations                                   #
##################################################################################

- id: '1741700276041'
  alias: Calibration water sample pump
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_watersamplepump_start
    to: 'on'
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.calib_watersamplepump
      state: 'on'
  actions:
  - action: fan.turn_on
    data:
      percentage: 40
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  - delay: 00:00:{{ states.input_number.khcontroller_watersamplepump_calibration_time.state
      | int }}
  - action: fan.turn_off
    entity_id: fan.kh_controller_dosing_pump1_forward
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_watersamplepump_start
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: Fin calibration pompe water sample
      message: Reporter la qté delivrée par la pompe water sample
      data:
        push:
          thread-id: activité_reef-group
  mode: single
- id: '1741701819419'
  alias: Calibration acid pump
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_acidpump_start
    to: 'on'
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.calib_acidpump
      state: 'on'
  actions:
  - action: fan.turn_on
    data:
      percentage: 40
    target:
      entity_id: fan.kh_controller_dosing_pump2_forward
  - delay: 00:00:{{ states.input_number.khcontroller_acidpump_calibration_time.state
      | int }}
  - action: fan.turn_off
    entity_id: fan.kh_controller_dosing_pump2_forward
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_acidpump_start
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: Fin calibration pompe acide
      message: Reporter la qté delivrée par la pompe acide
      data:
        push:
          thread-id: activité_reef-group
  mode: single
- id: '1741703990586'
  alias: Reset Acid pump Calibration view
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.kh_test
    to: 'off'
  conditions: []
  actions:
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_acidpump
  mode: single
- id: '1741704088194'
  alias: Reset Water sample Calibration view
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.kh_test
    to: 'off'
  conditions: []
  actions:
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_watersamplepump
  mode: single
- id: bc2da7ca74cb4c39a14fc389b96ddae9
  alias: Notify kh_controller offline
  triggers:
  - trigger: state
    entity_id:
    - binary_sensor.192_168_1_100
    to: 'off'
  actions:
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: kh_controller offline
      message: Le KH Controller est hors ligne!
      data:
        push:
          thread-id: activité_reef-group

The KH value automation I started :

- id: '1739742697731'
  alias: Démarrer test KH
  description: ''
  triggers:
  - hours: '12'
    trigger: time_pattern
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.kh_test
      state: 'on'
  actions:
  - action: fan.turn_on
    data:
      percentage: 100
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
  - delay:
      hours: 0
      minutes: 0
      seconds: 15
      milliseconds: 0
  - action: fan.turn_off
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
    data: {}
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_on
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
    data:
      percentage: 40
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  - delay:
      hours: 0
      minutes: 0
      seconds: 10
      milliseconds: 0
  - action: homeassistant.update_entity
    metadata: {}
    data:
      entity_id:
      - sensor.reef_ph
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - action: fan.turn_on
    metadata: {}
    data:
      percentage: 10
    target:
      entity_id:
      - fan.kh_controller_stirrer
  - action: fan.turn_on
    metadata: {}
    data:
      percentage: 40
    target:
      entity_id: fan.kh_controller_dosing_pump2_forward
  - if:
    - condition: numeric_state
      entity_id: sensor.reef_ph
      below: 4.51
    then:
    - action: fan.turn_off
      target:
        entity_id: fan.kh_controller_dosing_pump2_forward
      data: {}
    - delay:
        hours: 0
        minutes: 0
        seconds: 10
        milliseconds: 0
    - action: fan.turn_off
      target:
        entity_id: fan.kh_controller_stirrer
      data: {}
  mode: single

And here are the print screens of the cards in lovelace :




The remaining things to do are :

  • save the amount of water sample poured into the beaker in an input_number.
  • save the amount of acid poured into the beaker in an input_number.
  • calculalte the KH value using : (acid_ml/water_sample_ml)*280 in a sensor.

I think I have finished the project. I just have to test it to see if it is acurate now.

The amount of water sample is determined with “input_number.khcontroller_watersamplepump_dosage”.

      khcontroller_watersamplepump_dosage:
        friendly_name: "KH Controller Volume échantillon ajouté"
        unique_id: khcontroller_volume_watersample
        value_template: "{{ states('input_number.khcontroller_watersamplepump_dosage') | float }}"
        unit_of_measurement: "mL"

The amount of citric acid is calculated with the following template & sensor :

The template :

# Calcul du temps de fonctionnement de la pompe acide du KH controller
      - unique_id: khcontroller_acidpump_runtime
        device_class: duration
        state: "{% set hours = states('sensor.khcontroller_acidpump_runtime') | float %} 
        {% set minutes = ((hours % 1) * 60) | int %}
        {{ '%02ih %02im'%(hours // 1, minutes) }}"
        name: Acid pump runtime

The sensor :

# Calcul de la durée de fonctionnement de la pompe acide
  - platform: history_stats
    name: khcontroller_acidpump_runtime
    entity_id: fan.kh_controller_dosing_pump2_forward
    state: "on"
    type: time
    start: "{{ now().replace(hour=0, minute=0, second=0) }}"
    end: "{{ now() }}"

The name of the sensor has to be the same in the template for the sensor to work.

And the you are able to calculate the amount of poured acid :

      khcontroller_acidpump_dosage:
        value_template: >
          {% set runtime = states('sensor.khcontroller_acidpump_runtime') | float(0) %}
          {% set calibration_dosage = states('input_number.khcontroller_acidpump_calibration_dosage') | float(0) %}
          {% set calibration_time = states('input_number.khcontroller_acidpump_calibration_time') | float(0) %}
          
          {{ (runtime * (calibration_dosage / calibration_time))*100 | round(2) }}
        unit_of_measurement: "mL"
        friendly_name: 'KH controller Qté ajout acide'

The result has to be multiplied by 100 to get the right value.

And for the KH value calculation, here is the calculation with a sensor :

# Affichage de la valeur dKH
      kh_controller_kh_value:
        value_template: >
          {% set water_dosage = states('sensor.khcontroller_watersamplepump_dosage') | float(0) %}
          {% set acid_dosage = states('sensor.khcontroller_acidpump_dosage') | float(0) %}

          {% if water_dosage > 0 %}
            {{ (acid_dosage / water_dosage) * 280 | round(2) }}
          {% else %}
            0
          {% endif %}
        unit_of_measurement: "°KH"
        friendly_name: 'Valeur KH'

Here is the final code for the lovelace card :

type: vertical-stack
cards:
  - type: custom:vertical-stack-in-card
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: back
      - cards:
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: sensor.kh_controller_kh_value
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_ph
                - entity: input_boolean.calib_watersamplepump
                - entity: input_boolean.calib_acidpump
              show_header_toggle: false
              title: KH Controller
              type: entities
            conditions:
              - entity: input_boolean.calib_ph
                state: "off"
              - entity: input_boolean.calib_acidpump
                state: "off"
              - entity: input_boolean.calib_watersamplepump
                state: "off"
              - entity: input_boolean.kh_test
                state: "off"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: sensor.kh_controller_kh_value
                - entity: input_boolean.kh_test
                - entity: input_number.khcontroller_watersamplepump_dosage
                  name: Dosage water sample
                - entity: sensor.khcontroller_watersamplepump_runtime
                  name: Durée ajout water sample
                - entity: sensor.khcontroller_watersamplepump_dosage
                  name: Quantité ajout water sample
                - entity: sensor.khcontroller_acidpump_runtime
                  name: Durée ajout acide
                - entity: sensor.khcontroller_acidpump_dosage
                  name: Quantité ajout acide
                - entity: input_boolean.calib_ph
                - entity: input_boolean.calib_watersamplepump
                - entity: input_boolean.calib_acidpump
              show_header_toggle: false
              title: KH Controller
              type: entities
            conditions:
              - entity: input_boolean.kh_test
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                - entity: sensor.reef_ph
                - entity: sensor.kh_controller_kh_value
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_ph
                - entity: input_select.c_calib_ph4
                - entity: input_number.v_calib_mv4
                - entity: input_select.c_calib_ph7
                - entity: input_number.v_calib_mv7
                - entity: input_select.c_calib_ph9_10
                - entity: input_number.v_calib_mv9
              show_header_toggle: false
              title: Reef pH (KH Controller) Calib mode
              type: entities
            conditions:
              - entity: input_boolean.calib_ph
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: sensor.kh_controller_kh_value
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_watersamplepump
                - entity: input_number.khcontroller_watersamplepump_calibration_time
                - entity: input_boolean.calib_watersamplepump_start
                - entity: input_number.khcontroller_watersamplepump_calibration_dosage
              show_header_toggle: false
              title: Calibration Water sample pump
              type: entities
            conditions:
              - entity: input_boolean.calib_watersamplepump
                state: "on"
            type: conditional
          - card:
              entities:
                - entity: sensor.kh_controller_reef_ph_khcontroller
                  name: Valeur mV
                - entity: sensor.reef_ph
                - entity: sensor.kh_controller_kh_value
                - entity: input_boolean.kh_test
                - entity: input_boolean.calib_acidpump
                - entity: input_number.khcontroller_acidpump_calibration_time
                - entity: input_boolean.calib_acidpump_start
                - entity: input_number.khcontroller_acidpump_calibration_dosage
              show_header_toggle: false
              title: Calibration Acid pump
              type: entities
            conditions:
              - entity: input_boolean.calib_acidpump
                state: "on"
            type: conditional
        type: vertical-stack

Maybe there will need improvements, of some adjustements when testing for accuracy, is there something more accurate than history_stats to get the pump runtime?

A little update, I changed the template & sensor for the acid pump runtime, to get something more accurate for KH calculation because the runtime did not reset after each start of the acid pump which caused the KH value to be wrong.

########################################################################################
#                                       Sensors                                        #
########################################################################################
  - platform: template
    sensors:
# ph_meter ONLINE status sensors
      kh_controller:
        value_template: '{% if is_state("binary_sensor.192_168_1_100", "on") %}Online{% else %}offline{% endif %}'
        friendly_name: 'État KH_controller'
        icon_template: >-
          {% if is_state('binary_sensor.192_168_1_100', 'on') %}
            mdi:power-plug
          {% else %}
            mdi:power-plug-off
          {% endif %}
#Conversion de valeur mV à pH
      reef_ph:   
        friendly_name: "Valeur PH"
        icon_template: mdi:ph
        value_template: '{{ (states.input_select.c_calib_ph9_10.state | float) - ( (((states.input_number.v_calib_mv9.state | float)-(states.sensor.kh_controller_reef_ph_khcontroller.state | float))*((states.input_select.c_calib_ph7.state | float)-(states.input_select.c_calib_ph9_10.state | float))) / ((states.input_number.v_calib_mv7.state | float)-(states.input_number.v_calib_mv9.state | float)) ) | round(2) }}'
#      xc  (states.sensor.kh_controller_reef_ph_khcontroller.state | float)
#          (states.input_select.c_calib_ph4.state | float)
#      ya  (states.input_select.c_calib_ph7.state | float)
#      yb  (states.input_select.c_calib_ph9_10.state | float)
#          (states.input_number.v_calib_mv4.state | float)
#      xa  (states.input_number.v_calib_mv7.state | float)
#      xb  (states.input_number.v_calib_mv9.state | float)
#########################################################ph_meter
# KH controller Runtime sensors
      khcontroller_watersamplepump_runtime:
        value_template: "{{ (float(states('input_number.khcontroller_watersamplepump_dosage')) / ((float(states('input_number.khcontroller_watersamplepump_calibration_dosage')))/(float(states('input_number.khcontroller_watersamplepump_calibration_time'))))) | round(2) }}"
        friendly_name: 'KH controller Durée ajout water sample'
        unit_of_measurement: 'sec'
      khcontroller_watersamplepump_dosage:
        friendly_name: "KH Controller Volume échantillon ajouté"
        unique_id: khcontroller_volume_watersample
        value_template: "{{ states('input_number.khcontroller_watersamplepump_dosage') | float }}"
        unit_of_measurement: "mL"

      khcontroller_acidpump_dosage:
        value_template: >
          {% set runtime = states('sensor.khcontroller_acidpump_runtime_bis') | float(0) %}
          {% set calibration_dosage = states('input_number.khcontroller_acidpump_calibration_dosage') | float(0) %}
          {% set calibration_time = states('input_number.khcontroller_acidpump_calibration_time') | float(0) %}
          
          {{ (runtime * (calibration_dosage / calibration_time)) | round(2) }}
        unit_of_measurement: "mL"
        friendly_name: 'KH controller Qté ajout acide'

      khcontroller_acidpump_runtime:
        value_template: "{{ states('input_number.khcontroller_acidpump_runtime') | float }}"
        friendly_name: 'KH Controller Durée ajout acide'
        unit_of_measurement: "s"
        unique_id: khcontroller_acidpump_runtime

# Affichage de la valeur dKH
      kh_controller_kh_value:
        value_template: >
          {% set water_dosage = states('sensor.khcontroller_watersamplepump_dosage') | float(0) %}
          {% set acid_dosage = states('sensor.khcontroller_acidpump_dosage') | float(0) %}

          {% if water_dosage > 0 %}
            {{ (acid_dosage / water_dosage) * 280 | round(2) }}
          {% else %}
            0
          {% endif %}
        unit_of_measurement: "°KH"
        friendly_name: 'Valeur KH'

########################################################################################
#                                     Input Select                                     #
########################################################################################
#Calibration de la sonde pH
  c_calib_ph4:
    name: Calibration pH4
    icon: mdi:adjust
    options:
      - "4.00"
      - "4.01"
  c_calib_ph7:
    name: Calibration pH7
    icon: mdi:adjust
    options:
      - "6.86 "
      - "7.00"
      - "7.01"
  c_calib_ph9_10:
    name: Calibration pH9-10
    icon: mdi:adjust
    options:
      - "9.00"
      - "9.18"
      - "9.21"
      - "10.00"
      - "10.01"

########################################################################################
#                                     Input Number                                     #
########################################################################################
#Calibration sonde pH
  v_calib_mv4:
    name: Calibration mV à 4 
    icon: mdi:gauge
#    initial: 4.504
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box
  v_calib_mv7:
    name: Calibration mV à 7 
    icon: mdi:gauge
#    initial: 4.013
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box
  v_calib_mv9:
    name: Calibration mV à 9
    icon: mdi:gauge
  #  initial: 3.703
    min: 3.500
    max: 5.000
    step: 0.001
    mode: box

#Calibration pompe doseuse 1
#    initial: 00
  khcontroller_watersamplepump_calibration_dosage:
    name: Résultat calibration en ml
    min: 1
    max: 100
    step: 0.5
    unit_of_measurement: ml
    mode: box
    icon: mdi:flask-outline
#    initial: 30
  khcontroller_watersamplepump_calibration_time:
    name: Durée calibration en sec
    min: 1
    max: 60
    step: 1
    unit_of_measurement: sec
    icon: mdi:timer-sand
#    initial: 30

# Quantité ajout échantillon eau de mer
  khcontroller_watersamplepump_dosage:
    name: Ajouts échantillon
    min: 1
    max: 500
    step: 1
    unit_of_measurement: ml
    icon: mdi:beaker

#Calibration pompe doseuse 2
#    initial: 00
  khcontroller_acidpump_calibration_dosage:
    name: Résultat calibration en ml
    min: 1
    max: 100
    step: 0.5
    unit_of_measurement: ml
    mode: box
    icon: mdi:flask-outline
#    initial: 30
  khcontroller_acidpump_calibration_time:
    name: Durée calibration en sec
    min: 1
    max: 60
    step: 1
    unit_of_measurement: sec
    icon: mdi:timer-sand
#    initial: 30

# Quantité ajout acide chlorhydrique
  khcontroller_acidepump_dosage:
    name: Ajouts acide
    min: 1
    max: 500
    step: 1
    unit_of_measurement: ml
    icon: mdi:beaker

# Calcul du temps de fonctionnement de la pompe acide chlorhydrique
  khcontroller_acidpump_runtime:
    name: "Acid Pump Runtime"
    min: 0
    max: 86400  # 24h en secondes
    step: 1
    unit_of_measurement: "s"

########################################################################################
#                                    Input Datetime                                    #
########################################################################################

#Calculer la durée de fonctionnement de la pompe d'acide chlorhydrique
  khcontroller_acidpump_start_time:
    name: "Start Time"
    has_time: true
    has_date: true

  khcontroller_acidpump_end_time:
    name: "End Time"
    has_time: true
    has_date: true

########################################################################################
#                                    Input Booleans                                    #
########################################################################################
# KH tests auto
  kh_test:
    name: "KH test : Mode auto"
    icon: mdi:arrow-decision-auto-outline
# Calib pH
  calib_ph:
    name: "Calibration sonde pH"
    icon: mdi:tape-measure
# Water sample pump calibration 
  calib_watersamplepump:
    name: "Calibration pompe échantillon"
    icon: mdi:tape-measure
  calib_watersamplepump_start:
    name: "start calibration for 15 sec"
    icon: mdi:clock-start
# Acid pump calibration 
  calib_acidpump:
    name: "Calibration pompe acide"
    icon: mdi:tape-measure
  calib_acidpump_start:
    name: "start calibration for 15 sec"
    icon: mdi:clock-start

########################################################################################
#                                         Groups                                       #
########################################################################################

  khcontroller:
    name: KH controller
    entities:
      - input_boolean.kh_test
      - input_boolean.calib_ph
      - input_number.v_calib_mv4
      - input_number.v_calib_mv7
      - input_number.v_calib_mv9
      - input_select.c_calib_ph4
      - input_select.c_calib_ph7
      - input_select.c_calib_ph9_10
      - sensor.kh_controller
      - sensor.reef_ph
      - sensor.kh_controller_kh_value

  watersamplepump:
    name: Water Sample pump
    entities:
      - input_boolean.calib_watersamplepump
      - input_boolean.calib_watersamplepump_start
      - input_number.khcontroller_watersample_pump_calibration_dosage
      - input_number.khcontroller_watersamplepump_calibration_time
      - input_number.khcontroller_watersamplepump_dosage
      - sensor.khcontroller_watersamplepump_runtime
      - sensor.khcontroller_watersamplepump_dosage

  acidpump:
    name: Acid pump
    entities:
      - input_boolean.calib_acidpump
      - input_boolean.calib_acidpump_start
      - input_number.khcontroller_acidpump_calibration_dosage
      - input_number.khcontroller_acidpump_calibration_time
      - input_number.khcontroller_acidpump_dosage
      - input_number.khcontroller_acidpump_runtime
      - input_datetime.khcontroller_acidpump_start_time
      - input_datetime.khcontroller_acidpump_end_time
      - sensor.khcontroller_acidpump_runtime
      - sensor.khcontroller_acidpump_dosage

So I did some changes too for the automations :

- id: '1741700276041'
  alias: Calibration water sample pump
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_watersamplepump_start
    to: 'on'
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.calib_watersamplepump
      state: 'on'
  actions:
  - action: fan.turn_on
    data:
      percentage: 40
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  - delay: 00:00:{{ states.input_number.khcontroller_watersamplepump_calibration_time.state
      | int }}
  - action: fan.turn_off
    entity_id: fan.kh_controller_dosing_pump1_forward
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_watersamplepump_start
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: Fin calibration pompe water sample
      message: Reporter la qté delivrée par la pompe water sample
      data:
        push:
          thread-id: activité_reef-group
  mode: single
- id: '1741701819419'
  alias: Calibration acid pump
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_acidpump_start
    to: 'on'
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.calib_acidpump
      state: 'on'
  actions:
  - action: fan.turn_on
    data:
      percentage: 40
    target:
      entity_id: fan.kh_controller_dosing_pump2_forward
  - delay: 00:00:{{ states.input_number.khcontroller_acidpump_calibration_time.state
      | int }}
  - action: fan.turn_off
    entity_id: fan.kh_controller_dosing_pump2_forward
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_acidpump_start
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: Fin calibration pompe acide
      message: Reporter la qté delivrée par la pompe acide
      data:
        push:
          thread-id: activité_reef-group
  mode: single
- id: '1741703990586'
  alias: Reset Acid pump Calibration view
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_acidpump
    to: 'off'
  conditions: []
  actions:
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_acidpump
  mode: single
- id: '1741704088194'
  alias: Reset Water sample Calibration view
  description: ''
  triggers:
  - trigger: state
    entity_id: input_boolean.calib_watersamplepump
    to: 'off'
  conditions: []
  actions:
  - action: input_boolean.turn_off
    entity_id: input_boolean.calib_watersamplepump
  mode: single
- id: '1741718444219'
  alias: KH Controller 01 - Vidange du bécher
  description: ''
  triggers:
  - hours: '12'
    trigger: time_pattern
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.kh_test
      state: 'on'
  actions:
  - action: fan.turn_on
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
    data:
      percentage: 100
  - delay:
      hours: 0
      minutes: 0
      seconds: 10
      milliseconds: 0
  - action: fan.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_on
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
    data:
      percentage: 40
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_off
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
    data: {}
  - if:
    - condition: state
      entity_id: fan.kh_controller_dosing_pump1_reverse
      state: 'off'
      for:
        hours: 0
        minutes: 0
        seconds: 5
    - condition: state
      entity_id: fan.kh_controller_dosing_pump1_forward
      state: 'off'
      for:
        hours: 0
        minutes: 0
        seconds: 5
    then:
    - action: input_boolean.turn_on
      metadata: {}
      data: {}
      target:
        entity_id: input_boolean.kh_test
  mode: single
- id: '1741718826967'
  alias: KH Controller 03 - Post test
  description: ''
  triggers:
  - trigger: state
    entity_id:
    - input_boolean.kh_test
    from: 'on'
    to: 'off'
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.kh_test
      state: 'on'
  actions:
  - action: fan.turn_on
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
    data:
      percentage: 100
  - delay:
      hours: 0
      minutes: 0
      seconds: 15
      milliseconds: 0
  - action: fan.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: fan.kh_controller_dosing_pump1_reverse
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_on
    metadata: {}
    data:
      percentage: 100
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - action: fan.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  mode: single
- id: '1741787106802'
  alias: KH Controller 04 - Durée pompe acide alimente input_datetime
  description: ''
  triggers:
  - trigger: state
    entity_id: fan.kh_controller_dosing_pump2_forward
    from: 'off'
    to: 'on'
  - trigger: state
    entity_id: fan.kh_controller_dosing_pump2_forward
    from: 'on'
    to: 'off'
  actions:
  - choose:
    - conditions:
      - condition: state
        entity_id: fan.kh_controller_dosing_pump2_forward
        state: 'on'
      sequence:
      - action: input_datetime.set_datetime
        target:
          entity_id: input_datetime.khcontroller_acidpump_start_time
        data:
          timestamp: '{{ now().timestamp() }}'
    - conditions:
      - condition: state
        entity_id: fan.kh_controller_dosing_pump2_forward
        state: 'off'
      sequence:
      - action: input_datetime.set_datetime
        target:
          entity_id: input_datetime.khcontroller_acidpump_end_time
        data:
          timestamp: '{{ now().timestamp() }}'
  mode: single
- id: '1741794735158'
  alias: KH Controller 05 - Temps de fonctionnement pompe acide
  description: ''
  triggers:
  - trigger: state
    entity_id:
    - input_datetime.khcontroller_acidpump_start_time
    - input_datetime.khcontroller_acidpump_end_time
  actions:
  - variables:
      start_time: '{{ states(''input_datetime.khcontroller_acidpump_start_time'')
        }}'
      end_time: '{{ states(''input_datetime.khcontroller_acidpump_end_time'') }}'
  - action: input_number.set_value
    target:
      entity_id: input_number.khcontroller_acidpump_runtime
    data:
      value: "{% set start = as_timestamp(start_time) %} {% set end = as_timestamp(end_time)
        %} {% if start and end and end > start %}\n  {{ end - start }}\n{% else %}\n
        \ 0\n{% endif %}"
  mode: single
- id: bc2da7ca74cb4c39a14fc389b96ddae9
  alias: Notify kh_controller offline
  triggers:
  - trigger: state
    entity_id:
    - binary_sensor.192_168_1_100
    to: 'off'
  actions:
  - action: notify.mobile_app_sm_s901b_seifer
    data:
      title: kh_controller offline
      message: Le KH Controller est hors ligne!
      data:
        push:
          thread-id: activité_reef-group
- id: '1739742697731'
  alias: KH Controller 02 - Démarrer test KH
  description: ''
  triggers:
  - trigger: state
    entity_id:
    - input_boolean.kh_test
    from: 'off'
    to: 'on'
    for:
      hours: 0
      minutes: 0
      seconds: 20
  - value_template: '{{ 4.49 <= states(''sensor.reef_ph'') | float <= 4.51 }}'
    trigger: template
  conditions:
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.kh_controller
      state: Online
    - condition: state
      entity_id: input_boolean.kh_test
      state: 'on'
  actions:
  - action: input_number.set_value
    target:
      entity_id: input_number.khcontroller_acidpump_runtime
    data:
      value: 0
  - action: fan.turn_on
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
    data:
      percentage: 40
  - delay:
      seconds: '{{ states(''sensor.khcontroller_watersamplepump_runtime'') | int }}'
  - action: fan.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: fan.kh_controller_dosing_pump1_forward
  - delay:
      hours: 0
      minutes: 0
      seconds: 10
      milliseconds: 0
  - action: homeassistant.update_entity
    metadata: {}
    data:
      entity_id:
      - sensor.reef_ph
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - action: fan.turn_on
    metadata: {}
    data:
      percentage: 10
    target:
      entity_id:
      - fan.kh_controller_stirrer
  - action: fan.turn_on
    metadata: {}
    data:
      percentage: 33
    target:
      entity_id: fan.kh_controller_dosing_pump2_forward
  - choose:
    - conditions:
      - condition: state
        entity_id: input_boolean.kh_test
        state: 'on'
      sequence:
      - target:
          entity_id: fan.kh_controller_dosing_pump2_forward
        action: fan.turn_on
        data: {}
    - conditions:
      - condition: template
        value_template: '{{ 4.49 <= states(''sensor.reef_ph'') | float <= 4.51 }}'
      sequence:
      - target:
          entity_id: fan.kh_controller_dosing_pump2_forward
        action: fan.turn_off
        data: {}
  mode: single

I created a pre scene where the beaker get empty and the tube filled up (still to adjust for real use), a scene where the test is running, and a post scene where the beaker is emptied and refilled again with water sample of the tank to let the pH probe remain in water.
And some other automations to reset the runtime of the acid pump, another one which save the start and end time into an input_datetime then in an input_number, so the runtime value can be used to determine the acid amound poured into the beaker, then the sensor show that runtime.
I also change the KH test automation to use the input_number of water sample value instead, so if the amount of water sample change in the lovelace card, the automation will adapt automatically.