Bedside HA Alarm Clock

I have a dedicated device bedside (an old iphone) that I have been using as an alarm clock, and I was looking to upgrade / integrate that functionality with home assistant. Specifically, I wanted:

  • A dimmable display with a clock
  • a few shortcut buttons (ie, turn of house)
  • Alarm clock functionality: display next alarm, off
  • Recurring alarms (ie: Mondays, Tuesdays)
  • Ability to modify a day of recurring alarms without turning off the alarm (eg, no alarm on Wednesday, June 19th – it’s a holiday, without having to remember to switch alarm back on)
  • Ability to quickly add alarms
  • fade in music

Using a combination of front end, automations, templates and scripts, I put together my new alarm clock:

This uses the following add ons: Custom Button Card, Browser Mod.

It also makes use of the local calendar, so you have the ability to add recurring alarms, filter for holidays, delete individual alarms without turning off all future alarms. I don’t believe there is a way to delete calendar entries or add recurring entries via services (if I am wrong, please let me know!!) so you need to do that directly from the calendar, but you can add a calendar entry right from the page.

If anyone is interested, the front end, template, and script codes are below. As a caveat, I am not a programmer, and I borrowed heavily from various threads here and elsewhere. This is more a compilation of others’ work…

Front-end:

  - title: clock
    theme: amoled
    path: clock
    icon: mdi:clock
    badges: []
    type: custom:ha-dashboard    
    cards:
      - type: vertical-stack
        cards:
          - type: custom:button-card
            color_type: label-card
            color: rgb(0, 0, 0)
            name: MBR Alarm Clock
            styles:
              card:
                - height: 25px
                - font-weight: bold
                - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]                
              name:
                - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                
          - type: horizontal-stack
            cards:                        
              - show_name: true
                show_icon: true
                type: custom:button-card
                color_type: label-card
                color: rgb(0,0,0)
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                entity: ""
                name: Other rooms
                icon: mdi:bullseye
                tap_action:
                  action: navigate
                  navigation_path: navigate
                icon_height: 20px
              - type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - font-size: 14px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                entity: scene.attic_enclosure_device_001_bedtime
                name: House off
                icon: mdi:theme-light-dark
                tap_action:
                  action: call-service
                  service: script.house_off  
              - type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - font-size: 14px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                name: Kids Home
                icon: mdi:bed-clock
                tap_action:
                  action: call-service
                  service: script.house_off_kids_home
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                entity: ""
                name: Quick Actions
                icon: mdi:gesture-tap
                tap_action:
                  action: fire-dom-event
                  browser_mod:
                    service: browser_mod.popup
                    data:
                      title: " "
                      content:
                        type: vertical-stack
                        cards: *quick
          - type: conditional
            conditions:
              - state: "on"
                entity: binary_sensor.alarm_status
            card:
              type: custom:button-card
              color_type: label-card
              color: rgb(191, 8, 8)
              name: Open doors / Alarm Zones
              styles:
                card:
                  - height: 25px
                  - font-weight: bold
                name:
                  - font-size: 12px
                  - color: rgb(125,125,125)         
          - type: entity-filter
            entities: *alarm
            state_filter:
              - "on"
              - open
            show_empty: false
            card:
              type: glance
              state_color: true
          - type: custom:button-card
            color_type: label-card
            color: rgb(0,0,0)
            variables:
            show_name: false
            show_icon: false
            show_state: true
            entity: sensor.time
            styles:
              state: 
                - font-size: 5.0em
                - color:  >
                        [[[
                          return "rgb(0, 18,"+states['sensor.rgb2'].state + ")"
                        ]]]
              card:
                - height: 150px
          - type: custom:button-card
            color_type: label-card
            color: rgb(0,0,0)
            
            name: >
              [[[return "Next Alarm: " + states['calendar.alarm_mbr'].attributes.message + ", " + helpers.formatTimeWeekday(states['calendar.alarm_mbr'].attributes.start_time)  ]]]
            styles:
              card:
                - height: 25px
                - font-weight: bold
              name:
                - color: >
                        [[[
                          return "rgb(7,"+states['sensor.rgb'].state + ", 20)"
                        ]]]
              icon:
                - height: 25px
                - color:  >
                        [[[
                          return "rgb(7,"+states['sensor.rgb'].state + ", 20)"
                        ]]]
          - type: conditional
            conditions:
              - state: "playing"
                entity: media_player.roam
            card:                
              type: custom:button-card
              color_type: label-card
              color: rgb(138, 7, 50)
              name: Alarm off
              styles:
                card:
                  - height: 75px
                  - font-weight: bold
              tap_action:
                action: call-service
                service:  script.mbr_pause_alarm
          - square: false
            columns: 3
            type: grid
            cards:              
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                entity: ""
                name: Add Alarm
                icon: mdi:alarm
                tap_action:
                  action: navigate
                  navigation_path: clock2
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                entity: ""
                name: " "
                icon: " "
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  icon:
                    - height: 25px
                    - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                  card:
                    - height: 65px
                    - border: >
                        [[[
                          return "solid 1px rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
                color_type: label-card
                color: rgb(0,0,0)
                entity: ""
                name: Add recurring alarm
                icon: mdi:calendar
                tap_action:
                  action: navigate
                  navigation_path: clock3
          - type: 'custom:button-card'
            styles:
              card:
                - height: 100px
                - border-radius: 15px
                - font-size: 16px
                - padding-bottom: 0px
                - padding-top: 0px
              name:
                - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
              label:
                - color: >
                        [[[
                          return "rgb("+states['sensor.rgb'].state + "," + states['sensor.rgb'].state + "," +states['sensor.rgb'].state +")"
                        ]]]
              grid:
                - grid-template-areas: '"n" "slider" "l"'
                - grid-template-columns: 1fr
                - grid-template-rows: min-content fr min-content min-content
              slider:
            custom_fields:
              slider:
                card:
                  full_row: true
                  type: 'custom:slider-entity-row'
                  entity: input_number.alarm_dim
                  colorize: true
                  hide_state: true
            name: Dimmer
            entity: input_number.alarm_dim
            show_icon: false
  - title: set alarm
    theme: slate
    path: clock2
    icon: mdi:clock
    badges: []
    type: custom:ha-dashboard    
    cards:
      - type: vertical-stack
        cards:
          - type: custom:button-card
            color_type: label-card
            color: rgb(45, 56, 61)
            name: Add Alarm (one time)
            styles:
              card:
                - height: 25px
                - font-weight: bold
          - type: horizontal-stack
            cards:              
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 15px
                    - color: red
                  icon:
                    - height: 30px
                    - color: red
                entity: ""
                name: Cancel / Go Back
                icon: mdi:alpha-x-circle-outline
                tap_action:
                  action: navigate
                  navigation_path: /lovelace-yaml/clock
          - type: conditional
            conditions:
              - state: "on"
                entity: binary_sensor.alarm_status
            card:
              type: custom:button-card
              color_type: label-card
              color: rgb(191, 8, 8)
              name: Open doors / Alarm Zones
              styles:
                card:
                  - height: 25px
                  - font-weight: bold
          - type: entity-filter
            entities: *alarm
            state_filter:
              - "on"
              - open
            show_empty: false
            card:
              type: glance
              state_color: true
          - type: entities
            title: 
            entities:
              - entity: input_datetime.new_alarm
                name: "Enter time for alarm:"
                icon: false
          - square: false
            columns: 3
            type: grid
            cards:              
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                    - color: green
                  icon:
                    - height: 25px
                    - color: green
                  card:
                    - height: 65px
                entity: ""
                name: Add Alarm
                icon: mdi:alarm
                tap_action:
                  action: call-service
                  service:  script.set_new_alarm
                  service_data:
                    summary: New Alarm
                    start: input_datetime.new_alarm
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                entity: ""
                name: " "
                icon: " "
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                entity: ""
                name: Recurring options
                icon: mdi:calendar
                tap_action:
                  action: navigate
                  navigation_path: clock3
  - title: set recurring alarm
    theme: slate
    path: clock3
    icon: mdi:clock
    badges: []
    type: custom:ha-dashboard    
    cards:
      - type: vertical-stack
        cards:
          - type: custom:button-card
            color_type: label-card
            color: rgb(45, 56, 61)
            name: MBR Alarm Clock
            styles:
              card:
                - height: 25px
                - font-weight: bold
          - type: horizontal-stack
            cards: *navigate1
          - type: conditional
            conditions:
              - state: "on"
                entity: binary_sensor.alarm_status
            card:
              type: custom:button-card
              color_type: label-card
              color: rgb(191, 8, 8)
              name: Open doors / Alarm Zones
              styles:
                card:
                  - height: 25px
                  - font-weight: bold
          - type: entity-filter
            entities: *alarm
            state_filter:
              - "on"
              - open
            show_empty: false
            card:
              type: glance
              state_color: true
          - type: entities
            title: Add Alarm
            entities:
              - input_datetime.new_alarm
          - type: horizontal-stack
            cards:
              - entity: input_boolean.alarm_mon
                name: Mon
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_tue
                name: Tue
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_wed
                name: Wed
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_thu
                name: Thu
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_fri
                name: Fri
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_sat
                name: Sat
                template: day_button
                type: custom:button-card
              - entity: input_boolean.alarm_sun
                name: Sun
                template: day_button
                type: custom:button-card
          - square: false
            columns: 3
            type: grid
            cards:              
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                entity: ""
                name: Add Alarm
                icon: mdi:alarm
                tap_action:
                  action: call-service
                  service:  script.set_new_alarm
                  service_data:
                    summary: New Alarm
                    start: input_datetime.new_alarm
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                entity: ""
                name: " "
                icon: " "
              - show_name: true
                show_icon: true
                type: custom:button-card
                styles:
                  name:
                    - font-size: 12px
                  icon:
                    - height: 25px
                  card:
                    - height: 65px
                entity: ""
                name: Recurring options
                icon: mdi:calendar
                tap_action:
                  action: navigate
                  navigation_path: clock3

A create a sensor for time. I wanted to use an existing card, but I also wanted the ability to dim the clock, and I could only do that using a custom button card. The sensor code for sensor.time:

{% set x = states('sensor.time') %}
{{now().strftime("%-I:%M %p")}}

The alarm automation:

alias: mbr alarm
description: ""
trigger:
  - platform: state
    entity_id:
      - calendar.alarm_mbr
    to: "on"
condition: []
action:
  - service: script.test_alarm
    metadata: {}
    data: {}
mode: single

Then script.test_alarm:

alias: bedroom alarm
sequence:
  - service: media_player.volume_set
    metadata: {}
    data:
      volume_level: 0.01
    target:
      entity_id: media_player.roam
  - service: media_player.play_media
    target:
      entity_id:
        - media_player.roam
    data:
      media_content_id: https://open.spotify.com/playlist/1LS91m0a848S93H5gBsgPM
      media_content_type: playlist
  - service: script.fade_the_volume_of_a_media_player
    metadata: {}
    data: {}
  - delay:
      hours: 0
      minutes: 15
      seconds: 0
      milliseconds: 0
  - service: media_player.media_pause
    metadata: {}
    data: {}
    target:
      entity_id: media_player.roam
mode: single
icon: mdi:alarm
description: ""

script.fade_the_volume_of_a_media_player:

alias: Fade the volume of a media player
mode: restart
variables:
  target_player: media_player.roam
  target_volume: 0.33
  curve: linear
  start_volume: 0.01
  fade_duration: 30
  fade_step_timeout: 500
  start_timestamp: "{{ as_timestamp(now()) }}"
  fade_volume_diff: "{{ (target_volume - start_volume) | float(0) }}"
  fade_duration_cutoff: "{{ fade_duration - 0.2 }}"
sequence:
  - alias: Set the media player volume in incremental steps for the fade duration.
    repeat:
      sequence:
        - alias: >-
            Set next volume step. Value based on time progress of fade,
            start/end volume and algorithm.
          service: media_player.volume_set
          data_template:
            entity_id: "{{ target_player }}"
            volume_level: >-
              {%- set relative_fade_pos = (as_timestamp(now()) -
              start_timestamp) / fade_duration %} {%- if curve == 'logarithmic'
              %}
                {{ (start_volume + (relative_fade_pos / (1 + (1 - relative_fade_pos))) * fade_volume_diff) | float(0) }}
              {%- elif curve == 'bezier' %}
                {{ (start_volume + (relative_fade_pos * relative_fade_pos * (3 - 2 * relative_fade_pos)) * fade_volume_diff) | float(0) }}
              {%- else %}
                {{ (start_volume + relative_fade_pos * fade_volume_diff) | float(0) }}
              {%- endif %}
        - alias: >-
            Fade can be aborted by sending an event - wait for event before we
            continue with next fade step.
          wait_for_trigger:
            - alias: >-
                'media_player_fade_volume_abort' event with matching
                target_player entity
              platform: event
              event_type: media_player_fade_volume_abort
              event_data:
                target_player: "{{ target_player }}"
          timeout:
            milliseconds: 500
        - alias: >-
            If abort event was received we stop script execution right away.
            Volume remains at current value.
          if:
            - alias: >-
                Received the 'media_player_fade_volume_abort' event with
                target_player of current script instance.
              condition: template
              value_template: "{{ wait.trigger != none }}"
          then:
            - stop: Script aborted by 'media_player_fade_volume_abort' event.
      until:
        - alias: Time passed in fade is close to desired duration.
          condition: template
          value_template: "{{ (as_timestamp(now()) - start_timestamp) >= 28.2 }}"
  - alias: Ensure media player is set to target volume after fade finished
    service: media_player.volume_set
    data_template:
      entity_id: "{{ target_player }}"
      volume_level: "{{ target_volume }}"
icon: mdi:tune-vertical
description: ""

script.mbr_pause_alarm:

alias: mbr pause alarm
sequence:
  - service: script.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: script.fade_the_volume_of_a_media_player
  - service: script.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: script.test_alarm
  - service: media_player.media_stop
    target:
      entity_id:
        - media_player.roam
    data: {}
description: ""
icon: mdi:alarm-off

and script:set_new_alarm:

alias: Set new alarm
sequence:
  - service: calendar.create_event
    metadata: {}
    data:
      summary: Alarm
      description: ""
      start_date_time: >
        {% set x = today_at(states('input_datetime.new_alarm')) %} {{ x +
        timedelta(days=1) if now() > x else x }}
      end_date_time: >
        {% set x = today_at(states('input_datetime.new_alarm')) +
        timedelta(minutes=15) %} {{ x + timedelta(days=1) if now() +
        timedelta(minutes=15) > x else x }}
    target:
      entity_id: calendar.alarm_mbr
  - service: browser_mod.navigate
    metadata: {}
    data:
      path: /lovelace-yaml/clock
mode: single

I use browser mod to hide the header and sidebar on this device, as well as to return to the alarm page after setting an alarm.

I’d (really) welcome suggestions for improvements. This has been a major step up from my existing alarm clock already – I hope it is helpful for others.

2 Likes