DIY dosing unit - Home Assistant

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
So, I decided a while ago to build my own 4 pump dosing unit to use with my GHL profilux rather than spend the ~$1000AUD to buy one of the GHL units. I figured that I could use the 10V switch channels in the S-Ports. As you'll see (and can see from the thread title) this is not where I ended up.

I started by printing a 2 part body, allowing for 4 pumps and 4 buttons at the front and DC jack and RJ12 at the back.

20240505_104459.jpg

20240507_083513.jpg

20240507_102355.jpg


Next was a PCB to allow for the interfacing of the Profilux and the pumps. The Profilux S-Port has a 6 pin RJ12 cable with 12v, GND and 4 10V signal pins. So, I decided to go with a ULN2003A transistor array to pull the -ve pins on the pumps to ground only when a signal was received from the Profilux. Additionally, the buttons were wired directly to ground to allow me to manually dose or prime the pumps.

20240507_151634.jpg


However, while this initially worked, the transistor array was getting too hot and the first chip even burnt out. So, I then decided to go with a solid state relay board and wire the 10V pins directly to the relay board.

20240701_154459.jpg


In the end, this probably should have worked but I was testing with a dodgy power supply (I found this out when I moved to my final design).
By this stage, I had been using Home Assistant for non-aquarium related functions and so I decided to change my plans and just order a smart relay. Here I made another mistake. I bought one of these - https://www.aliexpress.com/item/1005003637113966.html - relays and while I intended to use the 12V directly from the existing PCB, I actually bought the 85-250V AC version instead of the 7-32V DC version. This is not an insurmountable issue as both take a 5V DC input through a micro USB port. I now intend to use this.

I have now successfully added the device to Home Assistant and I am able to enter volumes which will be converted to time delays. The delays are set in automations between switching on and off. HA makes it easy to add various conditions so that for example, I can dose alkalinity only between certain hours at night while calcium can be dosed hourly throughout the day.

Screenshot_20241102_171850_Home Assistant.jpg


There's still a few things to do but I plan to have this running tomorrow. The automations are already running as expected, I just need to get the 5V connection done and it will be ready to go.
 
Last edited:

SlowAndStupid

Active Member
View Badges
Joined
Jan 24, 2024
Messages
292
Reaction score
686
Location
USA
Rating - 0%
0   0   0
I'm porting over my z wave devices from homeseer to home assistant and am a huge fan of how much easier and intuitive it is to work with. Love seeing more aquarium control used through it. Im currently running some hydros equipment but wish they would open the api to allow integration into home assist personally.
 
OP
OP
firechild

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
It's not pretty but I have calibration working.

1730718519770.png


I have an automation that when I press the button on the left, that pump dispenses what should be 10mL. I then enter the actual volume on the right and press update and an automation runs to update the multiplier that converts mL to seconds.

I'm sure there are more suitable cards and a cleaner way of displaying this but for now, it functions.
 
OP
OP
firechild

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
Calibration worked flawlessly, I now have this dosing my Calcium/Magnesium and alkalinity.

1731223249722.png



I did also manage to tidy up the Home Assistant calibration card.

1731223908990.png
 

astefane

New Member
View Badges
Joined
Dec 29, 2021
Messages
24
Reaction score
4
Location
Brantford, Ontario
Rating - 0%
0   0   0
This looks great, I've been working on setting up something similar. (I'm currently using a nodemcu, a couple of mosfets, and esphome)
How did you code the calibration function, this looks a lot better then my current method of manually calculating it and forcing the times the mosfets are on.
 
OP
OP
firechild

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
This looks great, I've been working on setting up something similar. (I'm currently using a nodemcu, a couple of mosfets, and esphome)
How did you code the calibration function, this looks a lot better then my current method of manually calculating it and forcing the times the mosfets are on.
I created an input_number for the calibration multiplier which is the value in seconds that doses 1mL. The buttons on the left turn each pump on for 10 * their respective multiplier. I then enter the actual dosed volume and press the update button which uses the below automation to update the value based on the old value and the dispensed amount:

Code:
alias: Update Calibration Multiplier 1
description: ""
triggers:
  - entity_id:
      - input_boolean.update_calibration_1
    trigger: state
    from: "off"
    to: "on"
actions:
  - target:
      entity_id: input_number.calibration_multiplier_1
    data:
      value: >
        {% set dispensing_volume = states('input_number.dispensing_volume_1') |
        float %} {% if dispensing_volume != 0 %}
          {{ states('input_number.calibration_multiplier_1') | float / (dispensing_volume / 10) }}
        {% else %}
          {{ states('input_number.calibration_multiplier_1') | float }}  # Prevent division by zero
        {% endif %}
    action: input_number.set_value
  - action: input_boolean.turn_off
    target:
      entity_id: input_boolean.update_calibration_1
    data: {}
 
OP
OP
firechild

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
Wow awesome! Would you be willing to share all of your esp code??
I'm not using esphome for this one, though that might change down the line. However, I am happy to share the code. When I get some time, I'll pull all the different automations and values from the configuration.yaml file and post them.
 

astefane

New Member
View Badges
Joined
Dec 29, 2021
Messages
24
Reaction score
4
Location
Brantford, Ontario
Rating - 0%
0   0   0
I created an input_number for the calibration multiplier which is the value in seconds that doses 1mL. The buttons on the left turn each pump on for 10 * their respective multiplier. I then enter the actual dosed volume and press the update button which uses the below automation to update the value based on the old value and the dispensed amount:

Code:
alias: Update Calibration Multiplier 1
description: ""
triggers:
  - entity_id:
      - input_boolean.update_calibration_1
    trigger: state
    from: "off"
    to: "on"
actions:
  - target:
      entity_id: input_number.calibration_multiplier_1
    data:
      value: >
        {% set dispensing_volume = states('input_number.dispensing_volume_1') |
        float %} {% if dispensing_volume != 0 %}
          {{ states('input_number.calibration_multiplier_1') | float / (dispensing_volume / 10) }}
        {% else %}
          {{ states('input_number.calibration_multiplier_1') | float }}  # Prevent division by zero
        {% endif %}
    action: input_number.set_value
  - action: input_boolean.turn_off
    target:
      entity_id: input_boolean.update_calibration_1
    data: {}
Great. Thank you for the code.
 
OP
OP
firechild

firechild

Active Member
View Badges
Joined
Nov 23, 2020
Messages
267
Reaction score
349
Location
Sydney
Rating - 0%
0   0   0
As promised, below are the various pieces of code I am using for this:

Input_number and input_boolean values in configuration.yaml:

Code:
input_number:
  dosing_volume_1:
    name: Dosing Volume (mL)
    min: 0
    max: 20
    step: 0.1
    mode: box
  calibration_multiplier_1:
    name: Calibration Multiplier (seconds/mL)
    min: 0
    max: 10  # Adjust as needed
    step: 0.1
  dispensing_volume_1:
    name: Dispensed Volume (mL)
    min: 0
    max: 100
    step: 0.1
    mode: box

Code:
input_boolean:
  pump_1_calibration_trigger:
    name: Trigger Pump 1 Calibration
  pump_2_calibration_trigger:
    name: Trigger Pump 2 Calibration
  pump_3_calibration_trigger:
    name: Trigger Pump 3 Calibration
  pump_4_calibration_trigger:
    name: Trigger Pump 4 Calibration
  update_calibration_1:
    name: Update Pump 1 Calibration
  update_calibration_2:
    name: Update Pump 2 Calibration
  update_calibration_3:
    name: Update Pump 3 Calibration
  update_calibration_4:
    name: Update Pump 4 Calibration

Run calibration dose:

Code:
alias: Trigger Pump 1 Calibration
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_boolean.pump_1_calibration_trigger
    from: "off"
    to: "on"
actions:
  - type: turn_on
    device_id: cf78ce0e9e40e11a544cc7e9db5f7092
    entity_id: 2e6543464f4d1f72aadae15496ec5794
    domain: switch
  - delay:
      seconds: "{{ states('input_number.calibration_multiplier_1') | float * 10 }}"
  - type: turn_off
    device_id: cf78ce0e9e40e11a544cc7e9db5f7092
    entity_id: 2e6543464f4d1f72aadae15496ec5794
    domain: switch
  - action: input_boolean.turn_off
    target:
      entity_id: input_boolean.pump_1_calibration_trigger
    data: {}
mode: single

Update calibration value:

Code:
alias: Update Calibration Multiplier 1
description: ""
triggers:
  - entity_id:
      - input_boolean.update_calibration_1
    trigger: state
    from: "off"
    to: "on"
actions:
  - target:
      entity_id: input_number.calibration_multiplier_1
    data:
      value: >
        {% set dispensing_volume = states('input_number.dispensing_volume_1') |
        float %} {% if dispensing_volume != 0 %}
          {{ states('input_number.calibration_multiplier_1') | float / (dispensing_volume / 10) }}
        {% else %}
          {{ states('input_number.calibration_multiplier_1') | float }}  # Prevent division by zero
        {% endif %}
    action: input_number.set_value
  - action: input_boolean.turn_off
    target:
      entity_id: input_boolean.update_calibration_1
    data: {}

Dosing automation (example of my alk dosing which only happens at night time)

Code:
alias: Dosing 1
description: ""
triggers:
  - trigger: time_pattern
    minutes: "30"
    seconds: "00"
conditions:
  - condition: time
    after: "23:00:00"
    before: "05:00:00"
    weekday:
      - mon
      - tue
      - wed
      - thu
      - fri
      - sat
      - sun
actions:
  - action: switch.turn_on
    metadata: {}
    data: {}
    target:
      entity_id:
        - switch.dosing_relay_switch_1
  - delay:
      seconds: >-
        {{ (states('input_number.dosing_volume_1') | float) *
        (states('input_number.calibration_multiplier_1') | float) }}
  - action: switch.turn_off
    metadata: {}
    data: {}
    target:
      entity_id:
        - switch.dosing_relay_switch_1
mode: single

My card that displays the current dosing volume (I don't currently have anything that shows how much is dosed daily or how many times the dosing automation runs on each pump):

Code:
type: entities
entities:
  - entity: input_number.dosing_volume_1
    name: "Alkalinity (mL) "
    secondary_info: last-changed
    icon: mdi:flask
  - entity: input_number.dosing_volume_2
    name: "Magnesium (mL) "
    secondary_info: last-changed
    icon: mdi:flask
  - entity: input_number.dosing_volume_3
    name: "Calcium (mL) "
    secondary_info: last-changed
    icon: mdi:flask
  - entity: input_number.dosing_volume_4
    name: "Trace (mL) "
    secondary_info: last-changed
    icon: mdi:flask

And finally, my dosing card with calibration buttons:

Code:
type: vertical-stack
cards:
  - type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 33% 66%
    cards:
      - type: custom:bubble-card
        card_type: button
        entity: update.bubble_card_update
        scrolling_effect: false
        show_icon: false
        name: Dispense 10mL
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
            display: flex;
            align-items: right;
            justify-content: right;
          }
      - type: custom:bubble-card
        card_type: button
        entity: update.bubble_card_update
        scrolling_effect: false
        show_icon: false
        name: Actual Dispensed Volume (mL)
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
            display: flex;
            align-items: right;
            justify-content: right;
          }
  - square: false
    type: grid
    cards:
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.pump_1_calibration_trigger
        tap_action:
          action: toggle
        hold_action:
          action: none
        double_tap_action:
          action: none
        button_action:
          double_tap_action:
            action: none
          hold_action:
            action: none
        card_layout: large
        icon: mdi:water-pump
        name: Pump 1
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:numberbox-card
        entity: input_number.dispensing_volume_1
        name: null
        step: 0.1
        min: 0
        max: 100
        card_mod:
          style: >
            .body{display:block!important}
            .body::after{text-align:center;font-size:10px;content:"mL";display:block!important}
        border: false
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.update_calibration_1
        show_icon: true
        name: Update
        icon: circlebutton
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.pump_2_calibration_trigger
        tap_action:
          action: toggle
        hold_action:
          action: none
        double_tap_action:
          action: none
        button_action:
          double_tap_action:
            action: none
          hold_action:
            action: none
        card_layout: large
        icon: mdi:water-pump
        name: Pump 2
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:numberbox-card
        entity: input_number.dispensing_volume_2
        name: null
        step: 0.1
        min: 0
        max: 100
        card_mod:
          style: >
            .body{display:block!important}
            .body::after{text-align:center;font-size:10px;content:"mL";display:block!important}
        border: false
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.update_calibration_2
        show_icon: true
        name: Update
        icon: circlebutton
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.pump_3_calibration_trigger
        tap_action:
          action: toggle
        hold_action:
          action: none
        double_tap_action:
          action: none
        button_action:
          double_tap_action:
            action: none
          hold_action:
            action: none
        card_layout: large
        icon: mdi:water-pump
        name: Pump 3
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:numberbox-card
        entity: input_number.dispensing_volume_3
        name: null
        step: 0.1
        min: 0
        max: 100
        card_mod:
          style: >
            .body{display:block!important}
            .body::after{text-align:center;font-size:10px;content:"mL";display:block!important}
        border: false
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.update_calibration_3
        show_icon: true
        name: Update
        icon: circlebutton
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.pump_4_calibration_trigger
        tap_action:
          action: toggle
        hold_action:
          action: none
        double_tap_action:
          action: none
        button_action:
          double_tap_action:
            action: none
          hold_action:
            action: none
        card_layout: large
        icon: mdi:water-pump
        name: Pump 4
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
      - type: custom:numberbox-card
        entity: input_number.dispensing_volume_4
        name: null
        step: 0.1
        min: 0
        max: 100
        card_mod:
          style: >
            .body{display:block!important}
            .body::after{text-align:center;font-size:10px;content:"mL";display:block!important}
        border: false
      - type: custom:bubble-card
        card_type: button
        entity: input_boolean.update_calibration_4
        show_icon: true
        name: Update
        icon: circlebutton
        styles: |
          .bubble-button-card-container {
            background: #4db6ac !important;
          }
    columns: 3
title: Calibration
 

Lukes Saltwater Tank

Community Member
View Badges
Joined
Dec 7, 2022
Messages
62
Reaction score
42
Location
Germany
Rating - 0%
0   0   0
This looks really good, by far the most detailed and well documented home assistant dosing system I've come across.

I would love to integrate some of this into my project Marine assistant, would that be OK with you?

Would it be much work to get this working with esphome?
 

TOP 10 Trending Threads

DO YOU THINK TECHNOLOGICAL ADVANCEMENTS ARE MORE HELPFUL OR HURTFUL TO REEFING?

  • More helpful.

    Votes: 12 38.7%
  • More hurtful.

    Votes: 2 6.5%
  • I think it depends mostly on the technology.

    Votes: 13 41.9%
  • I think it dependsmostly on the reefer behind the technology.

    Votes: 9 29.0%
Back
Top
Home
Post thread…
Market
What's new