My journey plotting NOAA Tides

Like many people I started with jshufro/home_assistant_noaa_tides (which I understand is no longer maintained) and then the homeassistant-projects/home_assistant_noaa_tides fork. There’s an interoperability issue with the latest release of the noaa_coops python library, and I posted a fix but there’s been no response and I don’t know if the author posts in this community.

Next I found Flight-Lab’s HA_Noaa_Tides integration, which I really like because it has a lot features (including ones I’ve been thinking about). I proposed a pull request that adds the current tide level to the tide_predictions sensor, based on the previous and next tide levels and the current tide factor.

This lets me use the apexcharts-card to create plots like this:

The HA_Noaa_Tides integration includes a sensor that reports the actual water
level:

Apparently my local NOAA station’s data can be glitchy, which I smooth using an outlier filter:

Then I added a data generator to the apexcharts-card to plot the predicted water level until the next
tide:

This is less interesting when the next tide is real soon, but the HA_Noaa_Tides tide_predictions sensor includes the tide following the next tide, so I could extend the plot out even further:

My wife and I find this very useful for choosing when we walk the dog on the beach. If anyone is interested I’m happy to share my apexcharts-card configuration. I don’t know if/when my feature will be added to the HA_Noaa_Tides’s tide_predictions sensor, but until then you can always use my fork, https://github.com/ASBishop/HA_Noaa_Tides/tree/current-tide-level.

1 Like

I was also using the NOAA tides integration for a while but just recently decided to bring it “in house” if you will and use a couple of templates. (I was trying to clean up my installation and thin out my integrations.)

The apexcharts-card allows plotting attributes, so I store the upcoming tides there.

The graph is similar to yours, but I didn’t spend much time styling:

  - platform: rest
    name: Tide Plot
    resource: https://api.tidesandcurrents.noaa.gov/api/prod/datagetter
    params:
      begin_date: "{{ (utcnow() - timedelta(hours=12)).strftime('%Y%m%d %H:%M') }}"
      range: 72
      interval: 60
      station: <your station id>
      product: predictions
      datum: MLLW
      time_zone: gmt
      units: english
      application: DataAPI_Sample
      format: json
    json_attributes: predictions
    value_template: "{{ now() }}"
    scan_interval: 7200


template:

  - triggers:
      - trigger: state
        entity_id: sensor.tide_plot
        not_to:
            - unavailable
            - unknown
    sensor:
        name: Tide Graph Data
        state: "{{ now() }}"
        attributes:
            plot_data: |-
                {% set ns = namespace(list=[]) -%}
                {% for point in state_attr('sensor.tide_plot', 'predictions' ) -%}
                  {% set ns.list = ns.list + [ [as_timestamp(point.t ~ 'Z') * 1000, point.v | float(0)] ] -%}
                {% endfor -%}
                {{ ns.list }}

I have another template for upcoming high & low tides, which I use to show this table and to alert me of upcoming significant tide events.

I spent a little time to try and bring in the measured tides, but where I am they don’t vary that much from predicted, and I care more about the future anyway.

1 Like

Thank you for your post. I had no idea that the HA_Noaa_Tides integration existed and had been using the jshufro integration for years.

The HA_Noaa_Tides integration has many additional exposed data sensors and a visual UI in the Integrations section of Home Assistant instead of yaml as well.

I like your current tide level graph and hope your PR is approved and pulled in soon so I can copy it.

Hi. Would you please share your apexcharts-card yaml for generating the graph from the tide graph data?

I would love to see the upcoming high and low tide template that you mentioned in your post as well along with the table generation yaml.

If you have any other interesting tide templates you are willing to share that would be great.

Thanks.

Here’s how I filter the glitchy NOAA station data (this is in my configuration.yaml). The xxxxxxx is my local station ID.

sensor:
  - platform: filter
    name: "Filtered Water Level"
    entity_id: sensor.noaa_station_xxxxxxx_water_level
    filters:
      - filter: outlier
        window_size: 3
        radius: 1.0

Here’s my apexcharts-card:

type: custom:apexcharts-card
grid_options:
  columns: full
yaxis:
  - decimals: 2
    align_to: 1.5
    max: ~10
span:
  start: day
  offset: "-1d"
graph_span: 60h
now:
  show: true
  label: now
all_series_config:
  stroke_width: 3
  unit: ft
series:
  - entity: sensor.filtered_water_level
    name: Water Level
    color: red
    extend_to: now
  - entity: sensor.noaa_station_xxxxxxx_tide_predictions
    attribute: current_tide_level
    name: Predicted Level
    color: blue
    extend_to: now
  - entity: sensor.noaa_station_xxxxxxx_tide_predictions
    name: Next Tide Levels
    color: yellow
    extend_to: false
    show:
      legend_value: false
    data_generator: |
      const six_min = (6 * 60 * 1000);
      const one_day = (24 * 60 * 60 * 1000);
      let date_str = new Date().toDateString();
      let now = Date.now();
      let l_time = new Date(date_str + " " + entity.attributes.last_tide_time).getTime();
      let n_time = new Date(date_str + " " + entity.attributes.next_tide_time).getTime();
      let f_time = new Date(date_str + " " + entity.attributes.following_tide_time).getTime();
      if (l_time > now) {
        l_time -= one_day;
      }
      if (n_time < now) {
        n_time += one_day;
        f_time += one_day;
      }
      if (f_time < n_time) {
        f_time += one_day;
      }
      let l_level = entity.attributes.last_tide_level;
      let n_level = entity.attributes.next_tide_level;
      let f_level = entity.attributes.following_tide_level;
      let tide_data = [];
      function addTideData(start_time, prev_time, prev_level, next_time, next_level) {
        const period = next_time - prev_time;
        const tide_delta = Math.abs(next_level - prev_level);
        const low_level = Math.min(prev_level, next_level)
        let cosine_multiplier;
        if (next_level > prev_level) {
           cosine_multiplier = -50;
        } else {
           cosine_multiplier = 50;
        }
        for (let time = start_time; time <= next_time; time += six_min) {
          let elapsed = time - prev_time;
          let cosine_factor = cosine_multiplier * Math.cos(elapsed * Math.PI / period);
          let tide_factor = 50 + cosine_factor;
          tide_data.push([time, low_level + (tide_delta * (tide_factor / 100))]);
        }
      }
      addTideData(now, l_time, l_level, n_time, n_level);
      addTideData(n_time + six_min, n_time, n_level, f_time, f_level);
      return tide_data;
1 Like

Again, didn’t spend a lot of time on this (so suggestions welcome):

type: custom:apexcharts-card
graph_span: 3d
span:
  start: day
now:
  show: true
all_series_config:
  stroke_width: 1
  extend_to: now
header:
  show: true
  title: Tides
  show_states: false
  colorize_states: false
series:
  - entity: sensor.tide_graph_data
    unit: ft
    data_generator: |
      return entity.attributes.plot_data
  - platform: rest
    name: Tide Forecast
    resource: https://api.tidesandcurrents.noaa.gov/api/prod/datagetter
    json_attributes: predictions
    value_template: "{{ now() }}"
    scan_interval: 3600
    params:
      product: predictions
      application: HomeAssistant
      begin_date: "{{ utcnow().strftime('%Y%m%d %H:%M') }}"
      range: 48
      datum: MLLW
      station: <station id>
      time_zone: gmt
      units: english
      interval: hilo
      format: json

And the card:

type: markdown
content: >-
  ## Local Tides

  {% if states('sensor.tide_forecast') in ['unknown','unavailable'] %}

  It appears 'sensor.tide_forecast' is not populated

  {% else %}


  {% set t = state_attr('sensor.tide_forecast','predictions')
    | map(attribute='v')
    | map('float')
    | select('>=', 5)
    | list
  %}

  {% if t|length > 0 %}

  <ha-alert alert-type="info">Upcoming large tide of {{ t|max|round(1)
  }}</ha-alert>

  {% endif %}


  <table width="100%" border="1px">

  {% for tide in state_attr('sensor.tide_forecast','predictions') %}

  {% set time = as_local(as_datetime(tide.t ~ 'Z')) %}

  {% if time > now() %}
  <tr>
    <td>
      {{ 'High' if tide.type == 'H' else 'Low' }} Tide
      {{ tide.v|float(0)|round(1) }} feet
    </td>
    <td align="left">
      in {{ time_until(time) }} at {{ time.strftime('%H:%M') }}
   </td>
  </tr>
  {% endif %}
  {% endfor %}
  </table>
  </table>
  {% endif %}

Update: that high tide alert is old, I use this in a separate place in a markdown card. I’ll bet there’s a better way to write this:

{% set ns = namespace(big=false,little=false) %}
{% for tide in state_attr('sensor.tide_forecast','predictions') %}
  {% set height = tide.v|float|round(1) %}

  {% if height > 5 and not ns.big %}
    {% set ns.big = true %}
  <ha-alert alert-type="info">High tide of {{ height }} ft in{{ time_until(as_local(as_datetime(tide.t ~ 'Z')) ) }}</ha-alert>
  {% endif %}     

  {% if height < -1 and not ns.little %}
    {% set ns.little = true %}
  <ha-alert alert-type="info">Low tide of {{ height }} ft in {{ time_until(as_local(as_datetime(tide.t ~ 'Z')) ) }}</ha-alert>
  {% endif %} 
{% endfor %}
1 Like