Aquarium/Fishtank monitor and controller (ongoing project)

Hi,
This is my ongoing project. It has a lot of improvements to make. Any suggestions are welcome. I’m not a graphic designer, so all graphics are from internet. Also, I’m quite new on HA programming.


Gif Showing it working (too big to upload here):
[tela-aquario.gif - Google Drive]

I’m trying to make a dashboard to monitor and control my fishtank. I have some sensors, but I’m also developing a device to monitor temperature, water flux from the canister, pH and Water level, all in one peace.
All controls are executed clicking on the corresponding device on the picture. And the state is shown on the device:

  1. The water level is shown with fluid-level and reflects the sensor that’s installed on the aquarium. There’s the possibility to make automatic the refill (I don’t have).

  2. Canister flux is shown as a fan with the value below. I defined some severities so that it will be red/green/orange if it’s too slow.

  3. Clicking on the circulation pump activates it and a fan spins green.

  4. Clicking on the frog (I hava a “bubble frog”), activates the air comprassor connected to a frog in my aquarium. The water on the fliud changes, full with bubbles.

  5. The CO2 shows the estimated level of gas. For this I created a helper to store the last fill and another that has the estimated duration. Clicking on the valve opens/closes the solenoid to liberate the gas. Clicking on the canister, the date is set to today, indicating the REFILL was done and the level is updated.

  6. The light still has the basic on/of behavior, but I intend to reflect the intensity later.

  7. UV light on/off clicking on it.

  8. pH level will reflect the sensor. But I need a more representable gauge. I’m using the canvas because it was the closest I need, but it does not accept percentage to define the size. I will look for another one.

  9. Temperature bar: I 'd like to make it the same size as the picture. And the number n vertical. I will search how can I do that. Still couldn’t find out how .

    Todo:

  • Better light control, reflecting the brightness level, changing the opacity

  • better pH gauge.

  • Better positioning. Still learning how to do this correctly. I’m having issues when accessing in little screens, such as a phone.

  • Controls to turn on devices not in the picture, like heater, or refill CO2.

  • Adjust temperature bar

  • Show other parameters (amnonia, nitrite, nitrate, etc…)

Here are the codes (yes, it can be immproved):

  • Aquaeye on the configurations refer to the device I’m developping.
  • There are several helpers to calculate, but some are temporary, because aquaeye if not available yet (like ph, level, etc…).

templates.yaml

- sensor:
  - name: "pH do Aquário da Sala"
    unique_id: sala_ph
    unit_of_measurement: "pH"
    device_class: ph
    icon: mdi:ph
    state: "{{ (states('input_select.c_calib_ph9_10')| float) - ( (((states('input_number.v_calib_mv9') | float)-(states('sensor.aquaeye_sala_ph_ads') | float(0)))*((states('input_select.c_calib_ph7')| float)-(states('input_select.c_calib_ph9_10') | float))) / ((states('input_number.v_calib_mv7') | float)-(states('input_number.v_calib_mv9') | float)) ) | round(2) }}"

  - name: "Nível do CO2 da Sala"
    icon: mdi:co2
    unique_id: co2_sala_nivel
    state: >
      {% set abastecimento = states('input_datetime.dia_reabastecimento_co2_sala') | as_datetime | as_local %}
      {% set duracao = states('input_number.duracao_co2_sala') |int  %}
      {% set hoje = now() %}
      {% set dias_passados = (hoje - abastecimento).days |int %}
      {% set nivel = 100 - ((dias_passados * 100) / duracao) |int %}
      {{ nivel | int }}

lovelace panel

type: picture-elements
image: /local/itens/aquario-fundo.png
elements:
  - type: custom:fluid-level-background-card
    entity: sensor.co2_sala_nivel
    background_color: transparent
    show_state: true
    show_icon: false
    full_value: 100
    level_color: rgba(80, 80, 60, 1)
    style:
      z-index: 3
      top: 78%
      left: 10.4%
      width: 6.3%
      height: 60%
    card_mod:
      style: |
        ha-card {
          text-align: center;
          --ha-card-border-color: transparent !important;
          box-shadow: none !important;
          background: none !important;
          border-radius: 10px;
          overflow: hidden;
        }  
        #container, .container {
          width: 100% !important;
          height: 40% !important;
          position: relative !important;
          border-radius: 14px !important;
          margin-left: 0%;
          margin-top: 0%;
          opacity: 0.4;
          overflow: hidden;
        }
    card:
      type: custom:button-card
      entity: sensor.co2_sala_nivel
      title_template: "{{ states('sensor.co2_sala_nivel')|round(0) }} %"
      show_header_toggle: false
      show_name: false
      show_icon: false
      show_title: true
      tap_action:
        action: call-service
        confirmation:
          text: Marcar hoje como dia de recarga ?
        service: input_datetime.set_datetime
        service_data:
          entity_id: input_datetime.dia_reabastecimento_co2_sala
          date: "[[[ return new Date().toLocaleDateString('en-CA') ]]]"
        value: "off"
      card_mod:
        style: |
          ha-card {
           --ha-card-header-font-size: 20px;
             height: 100px !important;
             color: white;
             font-weight: 800;
          }
          .card-header {
           justify-content: center !important;
          }
          .name {
           overflow: unset !important;
            }
  - type: image
    entity: switch.aeracao_aquario
    title: Aeração do Aquário
    tap_action:
      action: toggle
    state_image:
      "on": /local/itens/sapo-ligado.png
      "off": /local/itens/sapo-desligado.png
    style:
      z-index: 2
      left: 50%
      top: 35%
      transform: scale(0.2, 0.2) translate(-50%, -50%)
  - type: image
    entity: switch.energia_aquario_uv
    title: Luz UV
    tap_action:
      action: toggle
    state_image:
      "on": /local/itens/uv-ligada.png
      "off": /local/itens/uv-desligada.png
    style:
      z-index: 2
      left: "-8%"
      top: "-5%"
      transform: scale(0.1, 0.1) translate(-50%, -50%)
  - type: custom:canvas-gauge-card
    entity: input_number.ph_temporario
    title: pH
    show_title: true
    style:
      z-index: 3
      left: 50%
      top: 75%
      transform: translate(-50%, -50%)
    gauge:
      type: linear-gauge
      barBeginCircle: 0
      tickSide: left
      numberSide: left
      valueBox: true
      width: 850
      height: 100
      minValue: 4
      maxValue: 10
      minorTicks: 4
      colorBarProgressEnd: rgb(5, 115, 34)
      colorMajorTicks: white
      colorMinorTicks: white
      colorStrokeTicks: white
      colorTitle: white
      colorUnits: white
      colorNumbers: white
      colorValueText: white
      fontTitleSize: 250
      fontTitleWeight: bolder
      fontTitleStyle: normal
      fontValueSize: 90
      fontUnitsSize: 50
      colorPlate: rgba(0,0,0,0)
      borders: false
      borderOuterWidth: 0
      borderMiddleWidth: 0
      borderInnerWidth: 0
      majorTicks:
        - "4"
        - "4.5"
        - "5"
        - "5.5"
        - "6"
        - "6.5"
        - "7"
        - "7.5"
        - "8"
        - "8.5"
        - "9"
        - "9.5"
        - "10"
      highlights:
        - from: 4
          to: 5
          color: rgb(255, 153, 66)
        - from: 5
          to: 6
          color: rgb(255, 200, 2)
        - from: 6
          to: 7
          color: rgb(225, 235, 52)
        - from: 7
          to: 8
          color: rgb(108, 186, 90)
        - from: 8
          to: 9
          color: rgb(20, 153, 53)
        - from: 9
          to: 10
          color: rgb(5, 115, 34)
  - type: custom:slider-entity-row
    entity: light.luz_do_aquario
    style:
      z-index: 3
      left: 50%
      top: 9%
    card_mod:
      style: |
        ha-card {
         --ha-card-header-font-size: 20px;
           height: 100px !important;
           color: white;
           font-weight: 800;
        }
        .card-header {
         justify-content: center !important;
        }
        .name {
         overflow: unset !important;
        }
  - type: image
    entity: light.luz_do_aquario
    tap_action: none
    hold_action: none
    state_image:
      "on": /local/itens/luz-ligada.png
      "off": /local/itens/luz-desligada.png
    style:
      z-index: 2
      top: "-12%"
      width: 100%
      transform: scale(0.7, 0.7) translate(0%, 0%)
      opacity: "{{ states['light.luz_do_aquario'].attributes.brightness / 255 }}"
  - type: image
    image: /local/itens/circulação.png
    style:
      z-index: 2
      top: 65%
      left: 80%
      height: 28%
      transform: scale(0.6, 0.6) translate(-50%, -50%)
  - type: image
    entity: switch.aquario_co2
    title: CO2 - Clique na válvula para ligar/desligar
    tap_action:
      action: toggle
    state_image:
      "on": /local/itens/co2-cilindro-ON.png
      "off": /local/itens/co2-cilindro-OFF.png
    style:
      z-index: 2
      top: "-10%"
      left: "-13%"
      transform: scale(0.2, 0.2) translate(-50%, -50%)
  - type: custom:button-card
    entity: switch.circulacao_aquario
    icon: mdi:fan
    show_name: false
    title: Liga/Desliga Circulação
    style:
      z-index: 3
      top: 67.5%
      left: 82%
      height: 8%
    styles:
      icon:
        - width: 40%
    state:
      - value: "on"
        styles:
          icon:
            - animation: rotating 1s linear infinite
            - color: green
      - value: "off"
        styles:
          icon:
            - animation: none
            - color: red
    card_mod:
      style: |
        ha-card {
          text-align: center;
          --ha-card-border-color: transparent !important;
          box-shadow: none !important;
          background: none !important;
          border-radius: 10px;
          overflow: hidden;
        }
  - type: custom:button-card
    entity: input_number.fluxo_temporario
    icon: mdi:fan
    tap_action: none
    tltle: Fluxo do Filtro
    "show_label:": false
    show_name: false
    style:
      z-index: 1
      top: 23%
      left: 74%
      height: 8%
    styles:
      card:
        - font-size: 15px
        - font-weight: bold
      grid:
        - grid-template-areas: "\"i\" \"fluxo\""
        - grid-template-columns: 1fr
      custom_fields:
        fluxo:
          - align-self: middle
          - color: white
      icon:
        - color: |
            [[[
              if (entity.state < 400) return 'red';
              if (entity.state >= 400 && entity.state < 649) return 'orange';
              else return 'green';
            ]]]
        - width: 30%
        - animation: rotating 1s linear infinite
    custom_fields:
      fluxo: |
        [[[
          return `<span>${entity.state}L/h</span>`
        ]]]
    card_mod:
      style: |
        ha-card {
          text-align: center;
          --ha-card-border-color: transparent !important;
          box-shadow: none !important;
          background: none !important;
          border-radius: 10px;
          overflow: hidden;
        }  
  - type: custom:bar-card
    animation: "on"
    bar-card-color: transparent
    bar-card-border-radius: 0
    direction: up
    entity_row: true
    icon: false
    max: 32
    width: 8%
    unit_of_measurement: ºC
    positions:
      icon: "off"
      name: "off"
      value: outside
    style:
      z-index: 1
      top: 38.5%
      left: 17.3%
    severity:
      - color: Red
        from: 0
        to: 24
      - color: Orange
        from: 24.1
        to: 25
      - color: Green
        from: 25.1
        to: 27.9
      - color: Red
        from: 28
        to: 31
    card_mod:
      style: |
        ha-card {
              --ha-card-border-width: 0px;
              vertical-align: middle;
              text-align: center;
        }
        bar-card-value {
          margin-top: auto;
          font-size: 13px;
          font-weight: bold;
          text-shadow: 1px 1px #0005;
          text-orientation: mixed;
        }
        bar-card-max {
          margin: 0px;
          margin-left: auto;
          margin-top: -20px;
          top: 10px;
        }
    entities:
      - entity: sensor.temp_bercario
  - type: custom:fluid-level-background-card
    entity: input_number.nivel_temporario
    fill_entity: switch.aeracao_aquario
    background_color: transparent
    show_state: true
    show_icon: false
    camera_view: auto
    level_color: rgba(82, 171, 255, 1)
    style:
      z-index: 2
      top: 80%
      left: 65%
      width: 100%
      height: 110%
    card_mod:
      style: |
        ha-card {
          text-align: center;
          --ha-card-border-color: transparent !important;
          box-shadow: none !important;
          background: none !important;
          border-radius: 10px;
          overflow: hidden;
        }  
        #container, .container {
          width: 71% !important;
          height: 40% !important;
          position: relative !important;
          border-radius: 14px !important;
          margin-left: 0%;
          margin-top: 0%;
          opacity: 0.4;
          overflow: hidden;
        }
    card:
      type: custom:card-templater
      card:
        type: entity
        entity: input_number.nivel_temporario
        title_template: "{{ states(''input_number.nivel_temporario'')|round(0) }} %"
        unit_of_measurement: "%"
        show_header_toggle: false
        show_name: false
        show_icon: false
        show_title: true
        name: " "
        positions:
          value: "off"
        card_mod:
          style: |
            ha-card {
             --ha-card-header-font-size: 20px;
               height: 100px !important;
               color: white;
               font-weight: 800;
            }
            .card-header {
             justify-content: center !important;
            }
            .name {
             overflow: unset !important;
            }

Hope you like…

I did this

Very cool !