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.
