Peristaltic Pump / Autodoser upgrade / conversion to Home Assistant Control Project

@cowboy
I use HA for my house but view is capacity it give me envy to use it for my aquarium to make lot of automation.
I stop my tank few month ago because with two child and my work it don’t let me time to manage it. But I start it again ;-).
At it’s best time, picture of the previous tank:


Actually:

4 Likes

In action on phone. Pump in fonction on HA in video

1 Like

Wow…beautiful tank! I look forward to your next tank then. And I look forward to seeing how you ultimately incorporate more aquarium controller functions into your HA instance. Let me know if you need any help / guidance with your implementation.

Come May 2020, I will have been using HA as an aquarium controller (and home controller) for 3 years now, and am super happy with it. And imagine you will be too.

BTW, I’m going to post a link about your development / build back on the main aquarium controller thread ( https://community.home-assistant.io/t/going-to-next-level-of-aquarium-automation-whos-with-me/ ) as well as the Facebook Home Assistant group, just to keep everyone who’s not following this thread up to date. :slight_smile:

I modified code because I missing a “(”. It work now.
I will add choice for calibration time because I have pumps with 87ml/min and 7ml/min.

1 Like

OK this finally works correctly.
It include, calibration, choice of calibration time, instructions by notification via HA app, notification if pump is outline
The code is :

###################################################################################
#                                  Customize Gobal                                #
###################################################################################
homeassistant:
  customize_glob:
    "input_boolean.r2d*":
      icon: mdi:calendar-clock
    "input_boolean.r1d*":
      icon: mdi:calendar-clock
    "sensor.reefdoser*_pump*_daily_runtime":
      icon: mdi:history
    "sensor.reefdoser*_pump*_dosage_split":
      icon: mdi:beaker
    "sensor.reefdoser*_pump*_time_split":
      icon: mdi:update
    "input_number.reefdoser*_pump*_daily_mins":
      icon: mdi:clock
    "input_select.reefdoser1_pump*_daily_freq":
      icon: mdi:progress-clock

#switch:
#  - platform: mqtt
#    name: "Reefdoser1 Pump1"
#    state_topic:  "homeassistant/stat/Reefdoser1/POWER1"
#    command_topic: "homeassistant/cmnd/Reefdoser1/POWER1"
#    qos: 1
#    payload_on: "ON"
#    payload_off: "OFF"
#    retain: True

###################################################################################
#                                  input_boolean                                  #
###################################################################################
input_boolean:
  r1p1:
    name: "Kh (R1P1) : Mode auto"
    icon: mdi:arrow-decision-auto-outline
# Pump calibration 
  calibr1p1on:
    name: "Mode calibration"
    icon: mdi:tape-measure
  calibr1p1start:
    name: "Lancer la calibration"
    icon: mdi:clock-start
###################################################################################
#                                  sensor                                         #
###################################################################################
sensor:
  - platform: template
    sensors:
# Reefdosers ONLINE status sensors
      reefdoser1:
        value_template: '{% if is_state("device_tracker.reefdoser1", "home") %}Online{% else %}offline{% endif %}'
        friendly_name: 'État Reefdoser1'
        icon_template: >-
          {% if is_state('device_tracker.reefdoser1', 'home') %}
            mdi:power-plug
          {% else %}
            mdi:power-plug-off
          {% endif %}
# Reefdoser1 Runtime sensors
      reefdoser1_pump1_daily_runtime:
        value_template: "{{ (float(states('input_number.reefdoser1_pump1_daily_dosage')) / ((float(states('input_number.reefdoser1_pump1_calibration_dosage')))/(float(states('input_number.reefdoser1_pump1_calibration_time'))))) | round(2) }}"
        friendly_name: 'R1P1 Durée ajout quotidien'
        unit_of_measurement: 'sec'
      reefdoser1_pump1_dosage_split:
        value_template: "{{ (float( states('input_number.reefdoser1_pump1_daily_dosage')) / float(states('input_select.reefdoser1_pump1_daily_freq'))) | round(2) }} "
        friendly_name: 'R1P1 Qté ajout fractioné'
        unit_of_measurement: 'ml'
      reefdoser1_pump1_time_split:
        value_template: "{{ (float(states('sensor.reefdoser1_pump1_daily_runtime')) / float(states('input_select.reefdoser1_pump1_daily_freq'))) | round(2) }}"
        friendly_name: 'R1P1 Durée ajout fractioné'
        unit_of_measurement: 'sec'
###################################################################################
#                                  device_tracker:                                #
###################################################################################
device_tracker:
  - platform: ping
    hosts:
      reefdoser1: 192.168.1.49
###################################################################################
#                                  input_numbers                                  #
###################################################################################
input_number:
  reefdoser1_pump1_daily_dosage:
    name: Ajouts quotidiens
    min: 1
    max: 500
    step: 1
    unit_of_measurement: ml
    icon: mdi:beaker
#    initial: 30
  reefdoser1_pump1_daily_mins:
    name: Décal. min. /H. réf.
    min: 00
    max: 59
    step: 1
    mode: box
#    initial: 00
    unit_of_measurement: mins
  reefdoser1_pump1_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
    unit_of_measurement: mins
  reefdoser1_pump1_calibration_time:
    name: Durée calibration en sec
    min: 1
    max: 60
    step: 1
    unit_of_measurement: sec
    icon: mdi:timer-sand
#    initial: 30
#    initial: 30
###################################################################################
#                                  input_select                                   #
###################################################################################
input_select:
  reefdoser1_pump1_daily_freq:
    name: Nbr d'ajouts quotidiens
    options:
      - "1"
      - "2"
      - "3"
      - "4"
      - "6"
      - "8"
      - "12"
      - "24"
    icon: mdi:target
###################################################################################
#                                  automations                                    #
###################################################################################
automation:
# Reefdoser1 Section
# R1P1 Subsection Automations

- alias: Reefdoser1 Pump1 at the specified frequency
  trigger:
    - platform: time_pattern
      hours: "/1"
      seconds: "00"
  condition:
    condition: and
    conditions:
      - condition: template
        value_template: "{{ now().hour % ( 24 / float(states('input_select.reefdoser1_pump1_daily_freq')))|int == 0 }}"
      - condition: template
        value_template: "{% if now().minute | int ==  states.input_number.reefdoser1_pump1_daily_mins.state | int %}true{% endif %}"
      - condition: state
        entity_id: sensor.reefdoser1
        state: 'Online'
      - condition: state
        entity_id: input_boolean.r1p1
        state: 'on'
  action:
    - service: switch.turn_on
      entity_id: switch.pompe_doseuse_kh
    - delay: '00:00:{{ states.sensor.reefdoser1_pump1_time_split.state | int }}'
    - service: switch.turn_off
      entity_id: switch.pompe_doseuse_kh
# Notify offline     
- alias: Notify reefdoser1 offline 
  trigger:
    platform: state
    entity_id: device_tracker.reefdoser1
    to: 'not_home'
  action:
    service: notify.mobile_app_iphone_x_raphael
    data:
      title: "Reefdoser1 offline"
      message: "les pompes reefdoser1 sont hors ligne et non fonctinelles "
      data:
        push:
          thread-id: "activité_reef-group"
# Calibration start     
- alias: Notify ready calibration R1P1
  trigger:
    platform: state
    entity_id: input_boolean.Calibr1p1on 
    to: 'on'
  action:
    service: notify.mobile_app_iphone_x_raphael
    data:
      title: "Calibration R1P1 - Kh"
      message: "Vérifiez le tuyau ; plein et sans bulles d'air. Choisisez la durée calibration. Placez le recipient doseur en sortie. Enclanchez calibration start"
      data:
        push:
          thread-id: "activité_reef-group"
# Calibration view reset with auto statut
# Allow to mask calibration card in lovelace if off
- alias: Reset calibration view
  trigger:
    platform: state
    entity_id: input_boolean.r1p1
    to: 'off'
  action:
    - service: input_boolean.turn_off
      entity_id: input_boolean.calibr1p1on
# Calibration view reset with auto statut
# Allow to mask calibration card in lovelace if off
- alias: Calibration pump
  trigger:
    platform: state
    entity_id: input_boolean.calibr1p1start 
    to: 'on'
  condition:
    condition: and
    conditions:
      - condition: state  
        entity_id: sensor.reefdoser1
        state: 'Online'
      - condition: state
        entity_id: input_boolean.calibr1p1on
        state: 'on'
  action:
    - service: switch.turn_on
      entity_id: switch.pompe_doseuse_kh
    - delay: '00:00:{{ states.input_number.reefdoser1_pump1_calibration_time.state | int }}' 
    - service: switch.turn_off
      entity_id: switch.pompe_doseuse_kh
    - service: input_boolean.turn_off
      entity_id: input_boolean.calibr1p1start
    - service: notify.mobile_app_iphone_x_raphael
      data:
        title: "Fin calibration"
        message: "Reporter la qté delivrée par la pompe"
        data:
          push:
            thread-id: "activité_reef-group"
###################################################################################
#                                  group                                          #
###################################################################################
group:
  aqua_view:
    view: yes
    name: Pompes Doseuses
    entities:
    - group.r1p1

  r1p1:
    name: R1P1 - Kh
    control: hidden
    entities:
    - switch.pompe_doseuse_kh
    - input_boolean.r1p1
    - input_number.reefdoser1_pump1_daily_mins
    - input_select.reefdoser1_pump1_daily_freq
    - input_number.reefdoser1_pump1_daily_dosage
    - sensor.reefdoser1_pump1_dosage_split
    - sensor.reefdoser1_pump1_daily_runtime
    - sensor.reefdoser1_pump1_time_split
###################################################################################
#                             Dynamic  Card in lovelace                           #
###################################################################################
#cards:
#  - card:
#      entities:
#        - entity: switch.pompe_doseuse_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: switch.pompe_doseuse_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

Final look when all is developed :

1 Like

Remaining problem is accuracy of real delivery time. This doesn’t be a problem for long-time delivery but for short. If you do 1 sec delivery, this could be 2 secs.
Any idea to improve it?

I need to use history-stat to evaluate distributed qty to add notification when flask will be empty. Any idea how to proceed ?

I suspect part of the problem is related to the fact that we’re using RaspberryPi’s (layered with Linux & HA) to try to pull off very precise timings, rather than a micro controller like an arduinio or ESP chip to conduct those timings.

In my case, I also know there’s Ethernet and WiFi that will add further latency, I knew that going into this.

But as you correctly point out, I also knew that I wasn’t trying to dose microlevels tuned for a 200 litre aquarium, but rather a 2000 litre system. So if the dose is off by 1 second or even 2, it’s not at all an issue. The smallest unit I dose per day is Iron - at 5ml a day, which requires running a total of 12 seconds a day. The worst variance I’ve seen with Iron is instead of 5ml a day, it’s 4 to 6ml a day. But over a week, it seems to average out. Alkalinity, Mag and Ca are dosed out at levels of 68+ml a day, so a variance of even 2ml, I don’t even care about, really.

But this also brings me to the next point which might bring you better performance without having to switch to a micro controller setup would be to swap out the pumps for slower pumps. Mine pump at 25ml a minute, where your’s run at 87ml per minute. Hence, at that faster rate, your pumps will have a larger range of inaccuracy than mine. Get some 12-25ml per minute pumps and you’ll have more accurate daily dose levels. And use the 87ml per minute pumps for your brand new continuous water change setup either for your main tank or your quarantine / hospital tanks.*

In summary, think either of these options could be an approach to solve this issue:

  1. Migrate your timing control over to an ESP chip. Use HA to deliver the new “timing values” to the ESP chip, and have some code on the ESP chip that actually manages the execution of those On/Off switch timings instead of the Pi.
  2. or replace the pumps with slower static flow rates to reduce the error window size
  3. or replace the pumps with variable rate pumps that you could not just choose the time they run, but also the speed they run with something like PWM, for maximum flexibility and precision dosing.
  • – This is a great way to setup and maintain quarantine tanks without filtration - I’ve done this with great success since 2006. In fact, I didn’t get a chance to test out your calibration function yet, because on Friday I discovered I had a fish with a viral infection which needed to be quarantined. Using a dry (not cycled) 10 litre aquarium as quarantine, I sat it up with my spare doser I was going to try your calibration function on, as the continuous water change and dropped my sick fish in there, without cycling the tank over time and no cycling bacteria added. The AutoDoser driven continuous water change totally manages ammonia levels & I can have HomeAssistant automatically increase the prescribed water change amount if ammonia levels go above a certain value point.

I haven’t implemented this, so this is only a high level description of how to do it, or how I’d go about it. It is untested, so your milage may vary.

In short you just need to create an input_number.liquid_containair_name and set that to the total volume of your container. You could declare the default value in your YAML or give yourself an interface to change and set this value in case you change container size.

I’m thinking I’d use a script function next to take that total container volume value you set it to another variable you can use as your ‘amount_remaining’ value. I’d use this script primarily whenever you refill the container back to full and need to reset the tracking counter back to the filled volume of the container.

Use another sensor and template function to subtract the value of your total ml dosed that day from your amount_remaining value. The amount in that sensor value will contain your remaining liquid in ml.

Then have an automation trigger when that amount_remaining value reaches a certain preset level that you want to trigger an alert / reminder to mixup and replace your liquid supplement in that container.

Are you satisfied by you Seneye sensor ?
I already order 8ml/min pumps. I will maybe do a dedicated raspberry with physic connection if my tests are concluding.

What did you use to have “total time yesterday” in sec ? I try with History-stat but minimum value is 0,001 hour. I already work on alert for empty flask, but my two mistake is:

  • Which counter use (counter, history-stat, maybe other…)
  • How to reset the value ?

I’m very satisfied with the Seneye for two things:
1.) True Ammonia (NH3) monitoring
2.) pH Monitoring
Added bonus is the light measurement function, but that’s one of those things I’ve used maybe 1 x a year, at most. Even less since the conversion to LEDs (from T-5’s).

In fact, I have 3 x Seneye sensors - One for my main system, and two Seneye’s for my baby clownfish fry rearing tanks & my quarantine tank management.

I’m openly critical about their business practices tho. Mostly, that decision of theirs too implement a “don’t report values” flag in the Version 2 firmware, which triggers at the end of 30 days to force one to buy / replace new slides. Since my quarantine process is usually 10 weeks long, I would stretch a slide out to 5-6 weeks of usage so I only needed 2x slides for an entire quarantined specimen (5 weeks x 2 = 10 weeks). There was never any guarantee that the slide would remain calibrated after 4 weeks, but I found it the stray from calibration was very slow and gradual. But when they forced the 30 day limit, that forced me to buy 3 slides for 10 weeks - that pissed me off. There was no value add in doing that to their customers, instead it was just to increase their revenue stream. In economic theory, we have a term for that: “Rent-seeking”. But in a commercial sense, I call it “punishing the loyal customer”. And typically, I tend to stay away from, and not recomend, companies that do this practice.

Further, I think they could have totally gobbled up a much larger market share if they’d just gone with an OpenSource approach and strategy from the very beginning. I’ve heard from many people their hesitation with buying one wasn’t the renewable cost of the slides, but the requirement to place a Windows based computer running 24/7 near their aquarium - OR - buying the 200+ euro Seneye Web Server.

My solution for the first few years was to run Seneye Connect Application on a Windows laptop / Virtual machine and plug my Seneye Sensors into RaspberryPi’s which ran VirtualHere clients on the Pi’s and a VirtualHere Server on the Windows computer as an USB over Ethernet bridge.

Years after their launch, they finally opened up a little bit. But it almost seems like a “compliance” action…perhaps from a Public Government owned Aquarium that was their customer and where regulation requiring the use of OpenSource software for government IT systems … like Germany… which might have forced this act (speculation on my part). But it was provided with no support and very little documentation, and didn’t work straight out of the box. I tried to make it work on my own, but failed and finally put it in the pile marked “requires way more time” to tackle one day.

Fortunately, @mcclown (bows in his direction, singing praises to him) solved all those usability problems for us, with his Seneye component for Home Assistant and now we have native support on RaspberryPi’s and we can use slides longer than the 30 days at our own risk. But from what I understand, he also banged his head on the keyboard for a long time to make it work.

Check eBay and Amazon for second hand units if you want to get one at a lower price. A lot of people leave the hobby and you can usually pick these up in fairly good shape at a cheaper price than the list price. Mine are about 5 years old and can attest to the build quality.

https://community.home-assistant.io/t/adding-support-for-seneye-aquarium-pond-sensors-removing-the-need-for-a-seneye-web-server/

As per the “total time yesterday”, I think this is what you are looking for?

From sensors.yaml:

- platform: history_stats_in_seconds
  name: Reefdoser1 Pump1 Dosage Today
  entity_id: switch.reefdoser1_pump1
  state: 'on'
  start: '{{ as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0)) }}'
  end: '{{ as_timestamp(now()) }}'
  unit_of_measurement: 's'

- platform: history_stats_in_seconds
  name: Reefdoser1 Pump1 Dosage Yesterday
  entity_id: switch.reefdoser1_pump1
  state: 'on'
  end: '{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}'
  unit_of_measurement: 's'
  duration:
    days: 1

- platform: template
  sensors:
    reefdoser1_pump1_litres_today:
#        value_template: "{{ ((float(states('sensor.reefdoser1_pump1_dosage_today')) * 3600 ) * 0.4166666666667 ) | round(2) }}"
      value_template: "{{ (float(states('sensor.reefdoser1_pump1_dosage_today'))  * 0.4166666666667 ) | round(2) }}"
      unit_of_measurement: 'ml'
      friendly_name: 'Reefdoser1 Pump1 Millilitres Today'
    reefdoser1_pump1_litres_yesterday:
      value_template: "{{ (float(states('sensor.reefdoser1_pump1_dosage_yesterday')) * 0.4166666666667 ) | round(2) }}"
      unit_of_measurement: 'ml'
      friendly_name: 'Reefdoser1 Pump1 Millilitres Yesterday'

    reefdoser1_pump1_daily_runtime:
      value_template: "{{ (float(states('input_number.reefdoser1_pump1_daily_dosage')) / 0.4166666666667 ) | round(2) }}"
      friendly_name: 'R1D1 Total Dosing Time'
      unit_of_measurement: 'sec'
    reefdoser1_pump1_dosage_split:
      value_template: "{{ (float( states('input_number.reefdoser1_pump1_daily_dosage')) / float(states('input_select.reefdoser1_pump1_daily_freq'))) | round(2) }} "
      friendly_name: 'R1D1 Individual Dosage Amount'
      unit_of_measurement: 'ml'
    reefdoser1_pump1_time_split:
      value_template: "{{ (float(states('sensor.reefdoser1_pump1_daily_runtime')) / float(states('input_select.reefdoser1_pump1_daily_freq'))) | round(2) }}"
      friendly_name: 'R1D1 Individual Dosing Time'
      unit_of_measurement: 'sec'

- platform: template
  sensors:
    reefdoser1_pump1_litres_today:
#        value_template: "{{ ((float(states('sensor.reefdoser1_pump1_dosage_today')) * 3600 ) * 0.4166666666667 ) | round(2) }}"
      value_template: "{{ (float(states('sensor.reefdoser1_pump1_dosage_today'))  * 0.4166666666667 ) | round(2) }}"
      unit_of_measurement: 'ml'
      friendly_name: 'Reefdoser1 Pump1 Millilitres Today'
    reefdoser1_pump1_litres_yesterday:
      value_template: "{{ (float(states('sensor.reefdoser1_pump1_dosage_yesterday')) * 0.4166666666667 ) | round(2) }}"
      unit_of_measurement: 'ml'
      friendly_name: 'Reefdoser1 Pump1 Millilitres Yesterday'

There is no component called “history_stats_in_seconds” but you can create one from the standard “history_stats”. Just copy “history_stats” and rename the new file to the name mentioned, then open and edit the following:

        # Count time elapsed between last history state and end of measure
        if last_state:
            measure_end = min(end_timestamp, now_timestamp)
            elapsed += measure_end - last_time

        # Save value in hours
# deleted by cowboy
# need this for history in seconds
#        self.value = elapsed / 3600
# added next line to replace deleted one above
        self.value = elapsed

        # Save counter
        self.count = count

    def update_period(self):

And go down to the next section in same file and modify as described here:

class HistoryStatsHelper:
    """Static methods to make the HistoryStatsSensor code lighter."""

    @staticmethod
    def pretty_duration(hours):
## Commented out by cowboy
## need to do this for history_in_seconds
#
#        """Format a duration in days, hours, minutes, seconds."""
#        seconds = int(3600 * hours)
#        days, seconds = divmod(seconds, 86400)
#        hours, seconds = divmod(seconds, 3600)
#        minutes, seconds = divmod(seconds, 60)
#        if days > 0:
#            return '%dd %dh %dm' % (days, hours, minutes)
#        if hours > 0:
#            return '%dh %dm' % (hours, minutes)
#        return '%dm' % minutes
## Next two lines added by cowboy
        seconds = int(hours)
        return seconds

Save that in custom_componenets or in the original location, just be aware you’re modifying the original location files during upgrades and restores from backups.

Now, instead of calling “platform: history_stats” just use “platform: history_stats_in_seconds” when you need second based granularity.

I also use this same custom_componenet for tracking and calculating how much AutoTopUp Water has been used, and when I reach the predefined set-point to switch over from Kalkwasser to RO Water.

37

Hope that helps you. :slight_smile:

1 Like

It’s help me a lot ! Thx you very much for your reactivity and precision of your answer.
I love this community ! Thx all.

1 Like

@cowboy When you put the modified component in custom_component it’s automatically take in account by HA ? I find the original component on HA web site but could i have a direct access on HA ?
It still me to find how to reset the value ?

Hi,
Something is wrong when I use the custom history_stats_in_seconds


Any ideas ?

Strange. However, I don’t actually have it in custom_componenets - it’s in the same standard components directory that history_stats is in. Cheeky and not recommended but works for me. :slight_smile:

I deleted the unit’s measurements and it’s work.
I always need to find how to reste history of one sensor

Did you figure it out? I just had to do this myself …

I doesn’t find how to reset history of one sensor.

Like you a did a custom history stat for the seconds. I don’t use the last ligne of unit and it works great. I place it in custom component.

Actually calibration work fine and history’s stat work to have a quick look of flask content. Have you test it ?
I still need to reset sensor when flask sensor is empty to reinitialize it.
Good week end.

Aaahhh… I think I better understand now. Sorry, I had something different in mind which I thought you wanted to achieve.

Have you tried using an input_number as your pseudo sensor?

In other words, whenever your doser pump finishes pumping, it takes that value of how much it pumped on that run (or that overall day) and subtracts it from a set value, say 5 litres. Then set that value to input_number.calcium_tracker. Then use that input_number.calcium_tracker as your displayed value of how much it remains. Then have another script function that resets input_number.calcium_tracker back to 5litres when you refill the container? Define input_number.calcium_tracker in history_graphs & use that object to display your graph.

Or do I still not understand correctly?

I will try to explain how the tracker work:
I used an input number to put the volume of these flask:

reefdoser1_pump1_flask:
  name: volume du contenaire plein
  min: 1000
  max: 10000
  step: 100
  unit_of_measurement: ml
  mode: box
  icon: mdi:bottle-tonic

I set the value of 5 litres
I used history stat sensors to track the delivered qty in seconds as :

sensor:
# Historique utilisation        
  - platform: history_stats_in_seconds
    name: R1P1 distri total en sec
    entity_id: switch.pompe_doseuse_kh
    state: 'on'
    start: '{{ as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0)) }}'
    end: '{{ as_timestamp(now()) }}'

and other sensors like :

- platform: template
sensors:
# Stat of delivery volume
      r1p1_distri_total_en_ml:
        value_template: "{{ (float(states('sensor.r1p1_distri_total_en_sec')) * ((float(states('input_number.reefdoser1_pump1_calibration_dosage')))/(float(states('input_number.reefdoser1_pump1_calibration_time'))))) | round(2) }}"
        friendly_name: 'R1P1  distri. total en ml'
        unit_of_measurement: 'ml'
        icon_template: mdi:chart-histogram
      r1p1_vol_restant:
        value_template: "{{ (float(states('input_number.reefdoser1_pump1_flask')) - float(states('sensor.r1p1_distri_total_en_ml'))) | int }}"
        friendly_name: 'R1P1 volume restant'
        unit_of_measurement: 'ml'

The first to count the amount of volume depending on calibration.
The second to calcul still volume of the flask.

I’m thinking to reset the history sensor when I refill the flask but did not find out how to do it.
Another problem when in restart HA it sometimes seems to reset the value.