Tides in Lovelace

Great, glad its working!

Seems like everybody on this thread is using the jshufro fork of NOAA tides because it has more features. But this fork no longer works with the latest version of Home Assistant.

And the author does not appear to be fixing it.

I put in a feature request to add this forks features to the built in NOAA tide component.

Please vote for my feature request and add any other comments that are useful to it.

1 Like

Interesting thread. I did some work on this some time ago and ended up with a canvas gauge card that emulates a standard tide gauge. It’s got a few other features like overlaying wave heights (grey band) and the critical depth (in pink) needed for launching one of our lifeboats.

1 Like

Heads up. There is a pull request for the jshufro/home_assistant_noaa_tides repository that fixes the problems that broke the integration with 2025.1 (Thank You Flight-Lab!)

It does appear that the original developer is not supporting the repository any longer, and no new releases have come out for a while. There are a couple Pull Requests now that have not been reviewed and/or merged into the project. Long term, forking this code into a new supported repository would be great. In the meantime, the basic features that I have been using are working again with Flight-Lab’s changes.

This is EXACTLY what I’m tring to do!

But having LOTS of trouble doing it. I’m in Australia so can’t use NOAA for tide data, however I’m using the stormglass.io tide API which is outputting data to a sensor (call it tide_height_by_hour) as a ā€œstate attributeā€ which comes up as follows:
data:

  • sg: 0.46
    time: ā€œ2025-01-06T00:00:00+00:00ā€
  • sg: 0.33
    time: ā€œ2025-01-06T01:00:00+00:00ā€
  • sg: 0.22
    time: ā€œ2025-01-06T02:00:00+00:00ā€
    etc etc (for every hour for the next few days)

I can’t for the life of me work out how to convert this into a sensor that I can put into a graph going forward.

I’ve love any advice or suggestions. Thanks so much!

I also don’t have a sensor, and the data is very similar to your format! ApexCharts supports data manipulation, which worked well for this. Here’s what a similar graph card might look like for yours (just replace your_state_attribute_name with the actual list attribute name):

type: custom:apexcharts-card
graph_span: 135h
header:
  show: true
  title: My Title
  show_states: false
span:
  start: minute
series:
  - entity: sensor.tide_height_by_hour
    name: Height (ft)
    extend_to: end
    type: line
    data_generator: >
      return entity.attributes.your_state_attribute_name.map((prediction) => { return[new
      Date(prediction.time), prediction.sg]; });

This card looks truly awesome and would be exactly what I am looking for. Would there be any chance of you sharing the YAML file?

Sorry it took so long to respond, I was in the middle of some major changes. Here is my YAML, with apologies for the complexity. I wrote it ages ago and I’m sure it could be simplified. The huge thing is to get the scaling of the canvas gauge to work. It MUST be the same pixel size as the image it is put on, so the image blankv is 479x603. I then scale it down so I can display other entities around it. I’ve left in a number of these to save me editing effort.

The other principle is to overlay several gauges using colour, white & transparency to build up the sections of the complex gauge. In the background is a set of 4 that display the wave heights overlaid onto the current tide height, with colours indicating when they fall below the safety threshold for getting our lifeboat out to sea (over the bar).
Overlaid onto this is a set of 3 gauges that display the pair of high & low tides and the current height. This is probably the only bit you need. I use red to show when these heights mean we can’t launch our all-weather lifeboat (a different limit to the bar).
Note that because of the above scaling you have to be a bit clever how you show labels on the tide gauge. sensor.tidedef is the current height (adjusted for atmospheric forcing) and it has a large number of attributes with most of the other bits of data. This allows a single popup card to be used.

Let me know if you need more explanation. If you can improve it, or better still, simplify the code, PLEASE tell me!

type: picture-elements
image: /local/images/blankv.jpg
title: Tides
elements:
  - type: state-label
    entity: sensor.nult
    suffix: UKHO tidal data*
    style:
      transform: translate(0%,-200%)
      top: 0%
      right: 0%
      font-size: 90%
      font-weight: bold
  - type: state-label
    entity: sensor.tidedef
    attribute: display
    style:
      transform: translate(0%,-80%)
      top: 0%
      left: 0%
      font-weight: bold
    card_mod:
      style: |
        :host {
          color: {{ iif( states('sensor.tidedef')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: time
    suffix: .
    style:
      transform: translate(0%,-150%)
      top: 0%
      right: 0%
  - type: state-label
    entity: sensor.tide_phase_display
    style:
      transform: translate(0%,-50%)
      top: 0%
      right: 0%
    card_mod:
      style: |
        :host {
          color: {{ iif( 'n/a' in states('sensor.tide_phase_display'), 'white', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: later-display
    style:
      transform: translate(0%,0%)
      top: 0%
      left: 0%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','laterh')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: short-display
    style:
      transform: none
      font-weight: bold
      width: 13%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((states("sensor.tidedef")|float(0)*83/9.5) + 10.3)|string +"%"}} !important;
          {{ 'right: 86%' if state_attr('sensor.tidedef','gauge_posn')== 'right' else 'left: 20%' }};
          color: {{ iif( states('sensor.tidedef')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
          background-color: #FFFFFF80;
          border-radius: 50px !important;
        }
        div {
          white-space: unset !important;
          text-align: right !important;
          line-height: 100%;
          }
  - type: state-label
    entity: sensor.tidedef
    attribute: last-display
    style:
      transform: none
      right: 86%
      width: 13%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((state_attr('sensor.tidedef','lasth')|float(0)*83/9.5) + 10.3)|string +"%"}} !important;
          color: {{ iif( state_attr('sensor.tidedef','lasth')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
        div {
          white-space: unset !important;
          text-align: right !important;
          line-height: 100%;
          }
  - type: state-label
    entity: sensor.tidedef
    attribute: next-display
    style:
      transform: none
      left: 20%
      width: 13%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((state_attr('sensor.tidedef','nexth')|float(0)*83/9.5) + 10.3)|string +"%"}} !important;
          color: {{ iif( state_attr('sensor.tidedef','nexth')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
        div {
          white-space: unset !important;
          line-height: 100%;
          }
  - type: state-label
    entity: sensor.tidedef
    attribute: nult
    prefix: "Next tides:"
    style:
      transform: none
      text-decoration: underline double
      font-weight: bold
      bottom: 87.5%
      left: 40%
      color: black
  - type: state-label
    entity: sensor.tidedef
    attribute: nextdisplay
    style:
      transform: none
      bottom: 84%
      left: 40%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','nexth')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: next1display
    style:
      transform: none
      bottom: 81%
      left: 40%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','next1h')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: next2display
    style:
      transform: none
      bottom: 78%
      left: 40%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','next2h')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: next3display
    style:
      transform: none
      bottom: 75%
      left: 40%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','next3h')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: next4display
    style:
      transform: none
      bottom: 72%
      left: 40%
    card_mod:
      style: |
        :host {
          color: {{ iif( state_attr('sensor.tidedef','next4h')|float(0) <= states('input_number.tide_low_restriction')|float(0), 'red', 'black') }};
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: nult
    prefix: >-
      Grounding risk when tide arrow is red.  Pink on tide gauge = ALB
      restriction,  Grey = depths of wave peaks/troughs.
    style:
      transform: none
      bottom: 31.5%
      color: black
      left: 40%
      width: 55%
      font-size: 80%
    card_mod:
      style: |
        div {
          white-space: unset !important;
          line-height: 100%;
          }
  - type: state-label
    entity: sensor.tide_offset
    attribute: pwr_display
    prefix: "Pressure/Wind/Rain height: "
    style:
      transform: none
      font-size: 90%
      bottom: 5%
      right: 0%
      font-style: italic
      color: black
    card_mod:
      style: |
        div {
          {% if states('sensor.tide_offset')|float(0) <= -0.1 %}
            color: red;
          {% endif %}
          white-space: unset !important;
         line-height: 100%;
          }
  - type: custom:canvas-gauge-card
    entity: sensor.tide_trough_high
    style:
      transform: translate(0%,0%) scale(.08,1)
      transform-origin: left bottom
      left: 16%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: none
      majorTicks: none
      animate: false
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: "#00000080"
      colorNeedle: "#ffffff00"
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: none
      needleWidth: 0
      tickSide: none
      numberSide: none
      needleSide: both
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
      highlights: false
  - type: custom:canvas-gauge-card
    entity: sensor.tide_trough_low
    style:
      transform: translate(0%,0%) scale(.08,1)
      transform-origin: left bottom
      left: 16%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: none
      majorTicks: none
      animate: false
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: white
      colorNeedle: "#00000040"
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: arrow
      needleWidth: 2
      tickSide: none
      numberSide: none
      needleSide: right
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
      highlights: false
  - type: custom:canvas-gauge-card
    entity: input_number.bar_warning
    style:
      transform: translate(0%,0%) scale(.08,1)
      transform-origin: left bottom
      left: 16%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: none
      majorTicks: none
      animate: false
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: "#DC143C"
      colorNeedle: red
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: arrow
      needleWidth: 3
      tickSide: none
      numberSide: none
      needleSide: right
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
      highlights: false
  - type: custom:canvas-gauge-card
    entity: sensor.tide_trough_red
    style:
      transform: translate(0%,0%) scale(.08,1)
      transform-origin: left bottom
      left: 16%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: none
      majorTicks: none
      animate: false
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: "#FFC0CB"
      colorNeedle: red
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: arrow
      needleWidth: 3
      tickSide: none
      numberSide: none
      needleSide: right
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
      highlights: false
  - type: custom:canvas-gauge-card
    entity: sensor.tide_high
    style:
      transform: translate(0%,0%) scale(.2,1)
      transform-origin: left bottom
      left: 7.5%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      majorTicks: none
      strokeTicks: false
      animate: false
      colorPlate: "#ffffff00"
      colorBar: "#fdf6ccff"
      colorBarProgress: "#c2e1b7ff"
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      highlightsWidth: 0
      colorNeedle: blue
      borders: false
      needleType: arrow
      needleWidth: 5
      tickSide: both
      numberSide: left
      needleSide: both
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
  - type: custom:canvas-gauge-card
    entity: sensor.tidedef
    style:
      transform: translate(0%,0%) scale(.2,1)
      transform-origin: left bottom
      left: 7.5%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: true
      majorTicks:
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
        - ""
      animate: true
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: " #9fddf9ff"
      colorNeedle: red
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: arrow
      needleWidth: 5
      tickSide: both
      numberSide: left
      needleSide: both
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
  - type: custom:canvas-gauge-card
    entity: sensor.tide_low
    style:
      transform: translate(0%,0%) scale(.2,1)
      transform-origin: left bottom
      left: 7.5%
      top: 0%
      width: 100%
      height: 100%
    gauge:
      type: linear-gauge
      width: 479
      height: 609
      minValue: -0.5
      maxValue: 9
      strokeTicks: true
      majorTicks: none
      animate: false
      highlightsWidth: 0
      colorPlate: "#ffffff00"
      colorBar: "#ffffff00"
      colorBarProgress: "#dcf2fd"
      colorNeedle: blue
      borderShadowWidth: 0
      colorBorderOuter: "#ffffff00"
      colorBorderMiddle: "#ffffff00"
      colorBorderInner: "#ffffff00"
      colorBorderOuterEnd: "#ffffff00"
      colorBorderMiddleEnd: "#ffffff00"
      colorBorderInnerEnd: "#ffffff00"
      borders: false
      needleType: arrow
      needleWidth: 5
      tickSide: both
      numberSide: left
      needleSide: both
      barStrokeWidth: 0
      barBeginCircle: false
      valueBox: false
  - type: state-label
    entity: sensor.tidedef
    attribute: nult
    prefix: "9.0"
    style:
      transform: none
      color: black
      left: 13.25%
      font-size: 70%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((9.0|float(0)*83/9.5) + 10)|string +"%"}} !important;
        }
        div {
          background-clip: content-box;
          background-color: #FFFFFF40;
          border-radius: 5px !important;
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: nult
    prefix: 4.5
    style:
      transform: none
      color: black
      left: 13.25%
      font-size: 70%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((4.5|float(0)*83/9.5) + 10)|string +"%"}} !important;
        }
        div {
          background-clip: content-box;
          background-color: #FFFFFF40;
          border-radius: 5px !important;
        }
  - type: state-label
    entity: sensor.tidedef
    attribute: nult
    prefix: " 0 "
    style:
      transform: none
      color: black
      left: 14.75%
      font-size: 70%
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((0*83/9.5) + 10)|string +"%"}} !important;
        }
        div {
          background-clip: content-box;
          background-color: #FFFFFF40;
          border-radius: 5px !important;
        }
  - type: state-icon
    entity: sensor.tidedef
    style:
      left: 12.5%
      transform: scale(1,2)
    card_mod:
      style: |
        :host {
          position: relative;
          bottom: {{((states("sensor.tidedef")|float(0)*83/9.5) + 10.3)|string +"%"}} !important;
          --icon-primary-color: {{ iif( states('sensor.tide_trough_low')|float(0) <= states('input_number.bar_warning')|float(0), 'red', 'blue') }};
card_mod:
  style: |
    ha-card { 
    width: 479px !important;
    background: white !important;
    color: black;
    }
2 Likes

I want to thank all in this thread for the amazing work. I’m using NOAA Tides. It took me a while to figure out that the next tide is the current state for the sensor. The attributes for Low and High are actually those that follow. I’m using a combination horizontal and vertical stack card to show sun, moon, and tide data:


I’m only 3 weeks into using Home Assistant, so I’m sure I’ll tweak things more.

2 Likes

Can you share your code for that card? that’s awesome… I’m still presenting a very simplistic dashboard after 6-7 years… lol

The coffee code for the gauge is in the thread, look for @Lordlinhey’s posts.

Edited from my coffee deprived initial post.

thanks Nick - appreciate it