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.
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.
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;
}
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.
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