Receive NL-Alert

I also use https://api.public-warning.app/api/v1/providers/nl-alert/alerts as the source for NL alert. This is the official NL-Alert endpoint also used by Actueel | NL Alert.

Using a REST-sensor I poll this endpoint every five minutes with this YAML configuration:

- resource: https://api.public-warning.app/api/v1/providers/nl-alert/alerts
  scan_interval: 300
  sensor:
    - name: NL-Alert
      icon: mdi:message-alert
      value_template: >
        {{ value_json.data[0].id }}
      json_attributes_path: $.data[0]
      json_attributes:
        - id
        - message
        - start_at
        - stop_at
        - area

Then I run an automation every time this sensor is updated to roughly determine if the alert is applicable, and turn off or on ventilation if it is specified in the message.

alias: NL-Alert
description: ""
triggers:
  - trigger: state
    entity_id:
      - sensor.nl_alert
conditions:
  - alias: NL-Alert applicable to Home zone
    condition: template
    value_template: |-
      {% set area_string = state_attr("sensor.nl_alert","area")[0] %}
      {% set area = area_string.split(" ") %}
      {% set coordinates = area[0].split(",") | map("float") | list %}
      {% set min = namespace(lat=coordinates[0],lon=coordinates[1]) %}
      {% set max = namespace(lat=coordinates[0],lon=coordinates[1]) %}


      {% for coordinate_string in area %}
      {% set coordinates = coordinate_string.split(",") | map("float") | list %}
      {% set min.lat = [min.lat, coordinates[0]] | min %}
      {% set max.lat = [max.lat, coordinates[0]] | max %}

      {% set min.lon = [min.lon, coordinates[1]] | min %}
      {% set max.lon = [max.lon, coordinates[1]] | max %}

      {% endfor %}

      {% set target = namespace(
        lat=state_attr("zone.home","latitude"),
        lon=state_attr("zone.home","longitude")
      ) %}
      {% set inside = (
        target.lat >= min.lat and target.lat <= max.lat and
        target.lon >= min.lon and target.lon <= max.lon
      ) %}

      {{ inside }}
actions:
  - alias: Check for fan control
    choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ "ventilatie uit" in (state_attr("sensor.nl_alert","message") |
              lower) }}
        sequence:
          - action: automation.turn_off
            metadata: {}
            data:
              stop_actions: true
            target:
              entity_id: automation.fan_control
          - action: fan.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: fan.itho_cve_fan
        alias: Ventilation off
      - conditions:
          - condition: template
            value_template: >-
              {{ "ventilatie aan" in (state_attr("sensor.nl_alert","message") |
              lower) }}
        sequence:
          - action: automation.turn_on
            metadata: {}
            data: {}
            target:
              entity_id: automation.fan_control
          - action: automation.trigger
            metadata: {}
            data:
              skip_condition: true
            target:
              entity_id: automation.fan_control
        alias: Ventilation on
  - action: notify.notify
    metadata: {}
    data:
      message: |-
        {{ state_attr("sensor.nl_alert","message") }}
      title: NL-Alert
  - action: tts.speak
    metadata: {}
    data:
      cache: true
      message: |-
        "NL Alert: {{ state_attr("sensor.nl_alert","message") }}
      language: nl_NL
      media_player_entity_id: media_player.alle_speakers
    target:
      entity_id: tts.piper
mode: single

This gives you a sensor that will look something like this:

And can be integrated into a dashboard with a markdown card: