Show all Active Alexa Timers

@snigehere Yeah, that does look new. I hadn’t upgraded yet, so I checked before and they weren’t there.
I should be able to take a look at it in the next few days.

@snigehere Okay, I fixed it. For some reason, something is getting passed as an entity that it’s not expecting. Here’s my current code.

class CardAlexaTimers extends HTMLElement {
  // Whenever the state changes, a new `hass` object is set. Use this to
  // update your content.
  set hass(hass) {
    // Initialize the content if it's not there yet.
    this.hassStored = hass;

    if(!this.initialized) {
        // we only want to run this once
        this.initialized = true;
        this.alarms;
        this.interval = window.setInterval(this.draw.bind(this), 1000);
        this.draw();
    }

  }
  
  get hass() {
      return this.hassStored;
  }

  // The user supplied configuration. Throw an exception and Lovelace will
  // render an error card.
  setConfig(config) {
    this.config = config;
  }

  // The height of your card. Home Assistant uses this to automatically
  // distribute all cards over the available columns.
  getCardSize() {
    return 3;
  }
  draw() {
    const entities = this.config.entities;
    
    this.alarms = [];
    for(let i = 0; i < entities.length; i++) {
        if(this.hass.states[entities[i]] !== undefined) {
            const attributes = this.hass.states[entities[i]].attributes;
            if(attributes.hasOwnProperty('sorted_active')) {
                const sorted_active = JSON.parse(attributes.sorted_active);
                for(let j = 0; j < sorted_active.length; j++) {
                    const alarm = sorted_active[j][1];
                    if (alarm.triggerTime >= new Date().getTime()) {
                        this.alarms.push(new AlexaTimer(alarm.timerLabel, alarm.triggerTime, alarm.originalDurationInMillis));
                    }
                }
            }
        }
    }
    this.alarms.sort((a,b) => a.remainingTime - b.remainingTime);

    if (this.alarms.length === 0) {
        this.innerHTML = '';
        this.content = null;
    }
    
    if(this.alarms.length > 0) {
      this.innerHTML = `
        <ha-card class="alexa-timers">
          <div class="card-content"></div>
        </ha-card>
      `;
      this.content = this.querySelector('div');
      
      let table = "<table border='0' width='100%'>";
      for(let i = 0; i < this.alarms.length; i++) {
          let name = this.alarms[i].label;
          if(name === null) {
              name = getNameFromDuration(this.alarms[i].originalDurationInMillis);
          }
          let timeLeft = this.alarms[i].hours + ":" + addLeadingZero(this.alarms[i].minutes) + ":" + addLeadingZero(this.alarms[i].seconds);
          table += "<tr>";
          table += "<td>" + name + "</td>";
          table += "<td>" + timeLeft + "</td>";
          table += "</tr>";
      }
      table += "</table>";
      this.content.innerHTML = table;
    }
  }
}

class AlexaTimer {
    constructor(label, triggerTime, originalDurationInMillis) {
        this.label = label;
        this.triggerTime = triggerTime;
        this.remainingTime = this.triggerTime - (new Date().getTime());
        this.seconds = Math.floor(this.remainingTime / 1000);
        this.minutes = Math.floor(this.seconds / 60);
        this.hours = Math.floor(this.minutes / 60);
        this.seconds = this.seconds % 60;
        this.minutes = this.minutes % 60;
        this.originalDurationInMillis = originalDurationInMillis;
    }
}

function addLeadingZero(num) {
    if(num < 10) {
        return "0" + num;
    }
    else {
        return num.toString();
    }
}

function getNameFromDuration(millis) {
    let seconds = Math.floor(millis / 1000);
    let minutes = Math.floor(seconds / 60);
    let hours = Math.floor(minutes / 60);
    seconds = seconds % 60;
    minutes = minutes % 60;
    let name = "";
    if(hours > 0) {
        name += hours + " Hour";
        if(minutes > 0 || seconds > 0) {
            name += ", ";
        }
        else {
            name += " ";
        }
    }
    if(minutes > 0) {
        name += minutes + " Minute";
        if(seconds > 0) {
            name += ", ";
        }
        else {
            name += " ";
        }
    }
    if(seconds > 0) {
        name += seconds + " Second ";
    }
    name += "Timer";
    return name;
}

































customElements.define('card-alexa-timers', CardAlexaTimers);

Basically, I added an if statement right inside the for loop, wrapping lines 39-48. If it gets an undefined entity, it doesn’t try to get its properties. It still seems to be working for me. I’m not sure what entity it’s getting that is undefined or why, and I’m too lazy to figure it out. It’s probably not my code anyway, seeing as this is a new issue.

@snigehere I forgot to add (in case you’re not aware) that HA likes to cache the resources, so if you make a change in the code, you’ll probably need to change the resource reference at Settings > Dashboards > click the 3 dots in the upper right hand corner of the page and select resources. I mentioned a while ago in this thread that you can just change the name to [whatever your filename was].js?v=[a number] so for example, mine is /local/card-alexa-timers.js?v=101 since I’m on version 101 of this thing. Then you’ll need to clear the cache of any browser you’re using to view dashboards, or it will keep trying to retrieve the old file. Like I have a tablet hanging on my wall to view the timers and such, and I needed to clear the cache of it (I use Fully Kiosk, and it worked to just close the program and open it again) in order for it to stop throwing errors in the log.

@Kethlak Hi Cassandra
Many thanks for your help and guidance with this … the reminder about versions was very helpful as well as I had forgotten.

All installed and working fine - no errors on restart

1 Like

Nice card! Anyway to support alarms too? I tried adding a _next_alarm sensor and your card did not work for me. I wrote some markdown a couple years back that supports both timers and alarms, but your formatting is way nicer. I do not understand how to convert it to a lovelace card: https://community.home-assistant.io/t/alexa-timer-card/258234/28?u=jaaem ,

@Kethlak Thanks so much for sharing this code - I came across this thread when searching to find out if something like this was possible, and set it all up from my phone - it worked as a new card on my test dashboard straight away :grinning: Now I just have to find somewhere suitable to display it!

1 Like

@jaaem Yeah, it looks like alarms are a different data type. However, I wrote this code which you could include as a separate resource, and a separate custom card, that will pull all alarms from all devices.

class CardAlexaAlarms extends HTMLElement {
  // Whenever the state changes, a new `hass` object is set. Use this to
  // update your content.
  set hass(hass) {
    // Initialize the content if it's not there yet.
    this.hassStored = hass;

    if(!this.initialized) {
        // we only want to run this once
        this.initialized = true;
        this.alarms;
        this.interval = window.setInterval(this.draw.bind(this), 1000);
        this.draw();
    }

  }
  
  get hass() {
      return this.hassStored;
  }

  // The user supplied configuration. Throw an exception and Lovelace will
  // render an error card.
  setConfig(config) {
    this.config = config;
  }

  // The height of your card. Home Assistant uses this to automatically
  // distribute all cards over the available columns.
  getCardSize() {
    return 3;
  }
  draw() {
    const entities = this.config.entities;
    
    this.alarms = [];
    for(let i = 0; i < entities.length; i++) {
        if(this.hass.states[entities[i]] !== undefined) {
            const attributes = this.hass.states[entities[i]].attributes;
            if(attributes.hasOwnProperty('sorted_active')) {
                const sorted_active = JSON.parse(attributes.sorted_active);
                for(let j = 0; j < sorted_active.length; j++) {
                    const alarm = sorted_active[j][1];
                    if(Date.parse(alarm.date_time) >= Date.now()) {
                        this.alarms.push(new AlexaAlarm(alarm.alarmLabel, alarm.date_time));
                    }
                }
            }
        }
    }
    this.alarms.sort((a,b) => a.alarmTime - b.alarmTime);

    if (this.alarms.length === 0) {
        this.innerHTML = '';
        this.content = null;
    }
    
    if(this.alarms.length > 0) {
      this.innerHTML = `
        <ha-card class="alexa-alarms">
          <div class="card-content"></div>
        </ha-card>
      `;
      this.content = this.querySelector('div');
      
      let table = "<table border='0' width='100%'>";
      for(let i = 0; i < this.alarms.length; i++) {
          let name = this.alarms[i].name;
          if(name === null) {
              name = getNameFromDuration(this.alarms[i].originalDurationInMillis);
          }
          let timeLeft = "";
          if(this.alarms[i].days > 0) {
            timeLeft = this.alarms[i].days + "days, ";
          }
          timeLeft += this.alarms[i].hours + ":" + addLeadingZero(this.alarms[i].minutes) + ":" + addLeadingZero(this.alarms[i].seconds);
          table += "<tr>";
          table += "<td>" + name + "</td>";
          table += "<td>" + timeLeft + "</td>";
          table += "</tr>";
      }
      table += "</table>";
      this.content.innerHTML = table;
    }
  }
}

class AlexaAlarm {
    constructor(label, date_time) {
        this.name = label;
        if(this.name == null) {
            this.name = getNameFromTime(date_time);
        }
        this.alarmTime = date_time;
        this.remainingTime = Date.parse(date_time) - Date.now();
        this.seconds = Math.floor(this.remainingTime / 1000);
        this.minutes = Math.floor(this.seconds / 60);
        this.hours = Math.floor(this.minutes / 60);
        this.days = Math.floor(this.hours / 24);
        this.seconds = this.seconds % 60;
        this.minutes = this.minutes % 60;
        this.hours = this.hours % 24;
    }
}

function addLeadingZero(num) {
    if(num < 10) {
        return "0" + num;
    }
    else {
        return num.toString();
    }
}

function getNameFromTime(alarmTime) {
    let alarmDateTime = new Date(alarmTime);
    let today = new Date(Date.now());
    let isAlarmToday = false;
    if(alarmDateTime.getFullYear() == today.getFullYear() && alarmDateTime.getDate() == today.getDate() && alarmDateTime.getMonth() == today.getMonth()) {
        isAlarmToday = true;
    }
    let name = "";
    if(isAlarmToday) {
        name = "Today at ";
    }
    else {
        name = getMonthNameFromNumber(alarmDateTime.getMonth()) + " " + alarmDateTime.getDate() + " at ";
    }
    name += addLeadingZero(alarmDateTime.getHours()) + ":" + addLeadingZero(alarmDateTime.getMinutes());
    return name;
}

function getMonthNameFromNumber(monthNum) {
    switch(monthNum) {
        case 0:
            return "January";
        case 1:
            return "February";
        case 2:
            return "March";
        case 3:
            return "April";
        case 4:
            return "May";
        case 5:
            return "June";
        case 6:
            return "July";
        case 7:
            return "August";
        case 8:
            return "September";
        case 9:
            return "October";
        case 10:
            return "November";
        case 11:
            return "December";
        default:
            return null;
    }
}

































customElements.define('card-alexa-alarms', CardAlexaAlarms);

And then in your dashboard yaml, you’ll write something like

        - type: "custom:card-alexa-alarms"
          entities:
            - sensor.front_room_dot_next_alarm
            - sensor.garage_dot_next_alarm
            - sensor.master_bedroom_dot_next_alarm
            - sensor.playroom_echo_dot_next_alarm
            - sensor.server_rack_dot_next_alarm

If you’ve gotten the timers working, you know how to get this one working as well.

It could probably be rewritten into a single module, but eh. If you wanted to do that, you’d need to have two types of entities (which is perfectly doable, by the way) and treat them differently.

We don’t tend to use alarms as much, since we have an echo clock and the alarm noise is really quiet (I know it’s probably changeable, just too lazy to change it, but not too lazy to code for 3 hours for something I’m not planning on using) and we’ve just found that timers work better for us.

1 Like

Thanks! Excellent work. Appreciate you working on something you do not plan to use. I merged the timers and alarm functionality, made a couple tweaks (added alexa name and used time locale) and posted on github. Might make installation a little easier for new people. Thanks so much for writing the code! Starting from your code made it easy to learn!

https://github.com/jdeath/card-alexa-alarms-timers
2 Likes

Very interested in using this. Quick question though - does it work with multiple timers on the same alexa device ? I have a simple card at the moment showing the kitchen timer - but cant seem to pick up a second timer if one is running. In our house - the kitchen is the device most likely to have multiple timers…

@bubble_13 Yes. This should show every timer on every device in your house.

Am I doing something stupid? I’ve copied the code to the file specified:
image

Added the resource data:
image

But when I try to use the car I get this:

I’ve cleared data, cache, etc, changed browsers, can access the file using the local address:

But for some reason I can’t seem to use it, anyone got any ideas please?

A couple thoughts. I’ve had trouble in the past with it caching the .js file, so if you had a version of the file that wasn’t formatted properly or something, the way to get around that is in the resource data, you set URL to something like /local/card-alexa-timers.js?v=randomstringhere , and then update the part after the question mark every time you make a change. HA will think it’s a different file, even though it isn’t, and the file ignores the query string anyway. It’s an old web developer trick.

Otherwise, I’m not familiar with the screen titled “Card Configuration”. It’s probably a different way of accessing the same thing I use. All I can say regarding that is that I use a dashboard with yaml that looks like this:


Don’t know if that helps. I’m using thomasloven’s card mod, but I can’t remember if it’s necessary for the alexa timers thing to work.

@Kethlak - Has there been any new updates to this? I added the latest update to the js file you posted on April 8th here. I also have the ‘?v=1’ that I do change to 2 or 3 when I update that file.
I have to restart home assistant after I create an alexa timer for it to show up on my dashboard. The other issues I see is when the timer gets to 00:00:00 it then doesn’t disappear, instead it counts up from a negative (-1:0-3:0-42) where the 42, in this example, is seconds counting up.
The other issue is that if I tell Alexa to cancel or stop my timer, the timer in Home Assistant persists still.
Xnip2023-08-25_10-43-13

My code depends on the Alexa Media Player integration. From what I understand looking at other posts about similar issues, Amazon changed the way the Alexa works, and it no longer sends out notifications about timers being set, going off, or anything else. Unfortunately, that breaks my code entirely. It’s been broken for a few months, I think.

1 Like

Just an fyi, the most recent Alexa Media Player integration update seems to have fixed the timers issue, among other things. :sweat_smile:

This works wonderfully well. Is there an option to add an “X” on the side on each line to easily remove a timer?


My kitchen setup. I added 3 buttons in a grid to automatically create a 5M/10M and 30M timers, for easy access.

I don’t think Alexa Media Player can do that, unfortunately. The alarms are a sensor, and I don’t think you can tell a sensor what to do. If you find any documentation for that, I am happy to help integrate it into this code.

@Kethlak It should work similar to what I did with the buttons to start a new timer. What I did was:

  - show_name: true
    show_icon: true
    type: button
    name: +5M
    icon: mdi:timer-plus
    tap_action:
      action: call-service
      service: media_player.play_media
      target:
        entity_id: media_player.livingroom_echo
      data:
        media_content_type: custom
        media_content_id: set a 5 minute timer

So a similar thing can be implemented here. You add an “X” next to each line, the X needs to call the service “media_player.play_media” with the relevant entity_id and with a media_content_id which is simply the string “remove the <> timer”. The only issue will be if there are multiple timers with the same name, but in that case Alexa asks you which one you want to remove, so that’ll work fine.
If you have can help do that, that’ll be amazing!

Thanks!

Here’s something I use, have not thought about adding cancel but I think it could be done.
It has a dropdown to select the particular Alexa, an input text for the name, an input_number for the length in minutes and a start button that runs a script.

type: entities
entities:
  - entity: input_select.alexa_devices
    name: Alexa
  - entity: input_text.alexa_timer_name
    name: Timer Name
  - entity: input_number.alexa_timer_length
    name: Time (Minutes)
  - entity: script.create_alexa_timer
    name: Start Timer
title: Create Alexa Timer

And the script:

alias: Create Alexa Timer
sequence:
  - service: media_player.play_media
    data_template:
      media_content_type: custom
      media_content_id: >-
        set {{ states('input_text.alexa_timer_name') }} timer for {{
        states('input_number.alexa_timer_length') }} minutes on {{
        states('input_select.alexa_devices') }}
    target:
      entity_id: media_player.deck_alexa
mode: single
icon: mdi:timer-check

Now you could just use something like:

media_content_id: >-
        cancel {{ states('input_text.alexa_timer_name') }} timer on {{
        states('input_select.alexa_devices') }}

Side note: Per you thought about cancelling and picking the one, I saw an issue testing this that I did not report along those lines. Using that GUI if I create a timer on one device and a timer on another device and ask one of those devices to “cancel all timers”, it will cancel the one on the device you speak to BUT the other device remains active in the HA GUI although it IS cancelled by Alexa.