Stormglass Tides Integration

This is my implementation with Stormglass using the REST sensor platform for tide and sea conditions. You get 10 free calls per day. This implementation makes 2 calls every 4 hours for a total of 8. I haven’t run this long enough to make an distinctions, so I’ve just opted for using Stormglass as the source (denoted by sg below).

rest:
  - resource: "https://api.stormglass.io/v2/weather/point"
    headers:
      Authorization: !secret stormglass_api_key
    params:
      lat: <some lat or your home location's secret lat>
      lng: <some long or your home location's secret long>
      start: "{{ utcnow() | as_timestamp | int(0) }}"
      end: "{{ utcnow() | as_timestamp | int(0) }}"
      params: "currentDirection,currentSpeed,swellDirection,swellHeight,swellPeriod,waterTemperature,waveDirection,waveHeight,wavePeriod"
    method: GET
    # check 4 times per day
    scan_interval: 21600  # 60*60*6
    timeout: 15
    sensor:
      - name: Stormglass Current Direction
        unique_id: "stormglass_current_direction"
        icon: mdi:compass-outline
        value_template: "{{ value_json.hours.0.currentDirection.sg | int(0) }}"
        unit_of_measurement: "°"
      - name: Stormglass Current Speed
        unique_id: "stormglass_current_speed"
        icon: mdi:speedometer
        value_template: "{{ value_json.hours.0.currentSpeed.sg | float(0) }}"
        unit_of_measurement: "m/s"
        device_class: speed
      - name: Stormglass Swell Direction
        unique_id: "stormglass_swell_direction"
        icon: mdi:compass-outline
        value_template: "{{ value_json.hours.0.swellDirection.sg | float(0) }}"
        unit_of_measurement: "°"
      - name: Stormglass Swell Height
        unique_id: "stormglass_swell_height"
        icon: mdi:arrow-up-down
        value_template: "{{ value_json.hours.0.swellHeight.sg | float(0) }}"
        unit_of_measurement: "m"
      - name: Stormglass Swell Period
        unique_id: "stormglass_swell_period"
        icon: mdi:arrow-left-right
        value_template: "{{ value_json.hours.0.swellPeriod.sg | float(0) }}"
        unit_of_measurement: "s"
        device_class: duration
      - name: Stormglass Water Temperature
        unique_id: "stormglass_water_temperature"
        icon: mdi:water-thermometer-outline
        value_template: "{{ value_json.hours.0.waterTemperature.sg | float(0) }}"
        unit_of_measurement: "°C"
        device_class: temperature
      - name: Stormglass Wave Direction
        unique_id: "stormglass_wave_direction"
        icon: mdi:compass-outline
        value_template: "{{ value_json.hours.0.waveDirection.sg | float(0) }}"
        unit_of_measurement: "°"
      - name: Stormglass Wave Height
        unique_id: "stormglass_wave_height"
        icon: mdi:arrow-up-down
        value_template: "{{ value_json.hours.0.waveHeight.sg | float(0) }}"
        unit_of_measurement: "m"
      - name: Stormglass Wave Period
        unique_id: "stormglass_wave_period"
        icon: mdi:arrow-left-right
        value_template: "{{ value_json.hours.0.wavePeriod.sg | float(0) }}"
        unit_of_measurement: "s"
        device_class: duration
  - resource: "https://api.stormglass.io/v2/tide/extremes/point"
    headers:
      Authorization: !secret stormglass_api_key
    params:
      lat: <some lat or your home location's secret lat>
      lng: <some long or your home location's secret long>
      start: "{{ utcnow().date() }}"
      end: "{{ (utcnow() + timedelta(hours=48)).date() }}"
    method: GET
    # check 4 times per day
    scan_interval: 21600  # 60*60*6
    timeout: 15
    sensor:
      - name: Stormglass Next Low Tide
        unique_id: "stormglass_next_low_tide"
        icon: mdi:waves-arrow-left
        value_template: >-
          {% set current = now() %}
          {% set phase = 'low' %}
          {{ value_json.data
            | selectattr('type', 'eq', phase)
            | map(attribute='time')
            | map('as_datetime')
            | map('as_local')
            | select('>=', current)
            | sort
            | first
          }}
        device_class: timestamp
      - name: Stormglass Next High Tide
        unique_id: "stormglass_next_high_tide"
        icon: mdi:waves-arrow-right
        value_template: >-
          {% set current = now() %}
          {% set phase = 'high' %}
          {{ value_json.data
            | selectattr('type', 'eq', phase)
            | map(attribute='time')
            | map('as_datetime')
            | map('as_local')
            | select('>=', current)
            | sort
            | first
          }}
        device_class: timestamp

I also have this template sensor to tell if the tide is incoming or outgoing. The reason for the attributes will become clear in a moment.

template:
  - sensor:
      - name: Stormglass Tide Horizontal Movement
        unique_id: "stormglass_ide_horizontal_movement"
        variables:
          next_low: "{{ states('sensor.stormglass_next_low_tide') | as_timestamp(0) }}"
          next_high: "{{ states('sensor.stormglass_next_high_tide') | as_timestamp(0) }}"
        icon: >-
          {% if next_low < next_high %}
            mdi:waves-arrow-left
          {% elif next_low > next_high %}
            mdi:waves-arrow-right
          {% else %}
            {# default sensor icon #}
          {% endif %}
        state: >-
          {% if next_low < next_high %}
            outgoing
          {% elif next_low > next_high %}
            incoming
          {% else %}
            unknown
          {% endif %}
        attributes:
          next_tide: >-
            {% if next_low < next_high %}
              low
            {% elif next_low > next_high %}
              high
            {% else %}
              unknown
            {% endif %}
          following_tide: >-
            {% if next_low < next_high %}
              high
            {% elif next_low > next_high %}
              low
            {% else %}
              unknown
            {% endif %}

Because I’m pedantic, I want to show the tide order for the next low and high in chronological order. Unfortunately, that was quite tricky to achieve. I considered other options, but in the end settled for this using the template entity row mod. I also wanted to display the times in a particular format.

      - type: entities
        show_header_toggle: false
        entities:
          - type: custom:template-entity-row
            entity: sensor.stormglass_tide_horizontal_movement
            name: Tidal Flow
            state: >-
              {{ states(config.entity) | title }}
          - type: custom:template-entity-row
            entity: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'next_tide') %}
              sensor.stormglass_simon_s_town_next_{{ phase }}_tide
            name: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'next_tide') %}
              {% if phase == None %}
                Next Tide
              {% else %}
                Next {{ phase | title }} Tide
              {% endif %}
            icon: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'next_tide') %}
              {% if phase == 'low' %}
                mdi:waves-arrow-left
              {% elif phase == 'high' %}
                mdi:waves-arrow-right
              {% else %}
                mdi:eye
              {% endif %}
            state: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'next_tide') %}
              {% set entity = 'sensor.stormglass_next_' ~ phase ~ '_tide' %}
              {% set ts = states(entity) | as_timestamp(0) %}
              {% set today = now().date() %}
              {% set date = (ts | as_datetime | as_local).date() %}
              {% if date == today %}
                Today at {{ ts | timestamp_custom('%H:%M') }}
              {% elif date == today + timedelta(days=1) %}
                Tomorrow at {{ ts | timestamp_custom('%H:%M') }}
              {% elif date < now().date() %}
                Unknown
              {% else %}
                {{ ts | timestamp_custom('%a %d %b at %H:%M') }}
              {% endif %}
          - type: custom:template-entity-row
            entity: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'following_tide') %}
              sensor.stormglass_next_{{ phase }}_tide
            name: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'following_tide') %}
              {% if phase == None %}
                Next Tide
              {% else %}
                Next {{ phase | title }} Tide
              {% endif %}
            icon: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'following_tide') %}
              {% if phase == 'low' %}
                mdi:waves-arrow-left
              {% elif phase == 'high' %}
                mdi:waves-arrow-right
              {% else %}
                mdi:eye
              {% endif %}
            state: >-
              {% set phase = state_attr('sensor.stormglass_tide_horizontal_movement', 'following_tide') %}
              {% set entity = 'sensor.stormglass_next_' ~ phase ~ '_tide' %}
              {% set ts = states(entity) | as_timestamp(0) %}
              {% set today = now().date() %}
              {% set date = (ts | as_datetime | as_local).date() %}
              {% if date == today %}
                Today at {{ ts | timestamp_custom('%H:%M') }}
              {% elif date == today + timedelta(days=1) %}
                Tomorrow at {{ ts | timestamp_custom('%H:%M') }}
              {% elif date < now().date() %}
                Unknown
              {% else %}
                {{ ts | timestamp_custom('%a %d %b at %H:%M') }}
              {% endif %}

Why did I do this? We stay 20km from the nearest beach and I wanted a way to know when it would be a good time to go for a swim with the family.

1 Like