Show all Active Alexa Timers

Edit 10/26/2023:
Thanks to @jaaem this is now available as a HACS repository at https://github.com/Kethlak/card-alexa-alarms-timers
It’s got a few customization options based on requests people have made on this thread.


Original Post:
Using the Alexa Media Player component via HACS, I created a custom card to display all active timers on my Alexa. I was using Alexa Timer Card - #7 by miki3421 as a reference, and someone asked on there how to display all the active timers, but nobody ever responded. So here’s how I did it.

I created a file at /www/card-alexa-timers.js with this content:

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), 500);
        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++) {
        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];
                this.alarms.push(new AlexaTimer(alarm.timerLabel, alarm.triggerTime));
            }
        }
    }
    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 header="Echo 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 = "Timer " + (i + 1);
          }
          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) {
        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;
    }
}

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

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

And then you have to go into Configuration > Dashboards > Resources tab and create a new resource with the url of “/local/card-alexa-timers.js” and type of “JavaScript module”. I tried to add this in my configuration.yaml file but couldn’t get that working either.

TIP: If you’re going to make changes to this, use a url like “/local/card-alexa-timers.js?v=1” and change the value after “v=” whenever you make changes. HA caches the resources otherwise and your changes won’t show up.

The custom card is only set up to work with a YAML dashboard, and the code for the card in the yaml looks like this:

cards:
  - type: "custom:card-alexa-timers"
    entities:
      - sensor.front_room_dot_next_timer
      - sensor.master_bedroom_dot_next_timer

Where you replace the list of entities with your own “_next_timer” entities created by the Alexa Media Player component.

I played around with binding to the hass timer event, but couldn’t get it working, so I used setInterval instead, but made sure I only run it once so it doesn’t clutter up the memory. Also, this card will disappear if there aren’t any active timers, which is how I wanted it to work.

Just thought I’d throw this out there in case anyone else was trying to do something similar and wanted some advice.

6 Likes

How does it look like?

And where is the benefit to an entities card with conditions?

It makes a table layout, where the first column is the name of the timer and the second column is the remaining time on the timer. The rows are sorted so that the ones with less time left are at the top.

I wanted to have it show with conditions because I’m using it on a wall display, and I don’t want to clutter up the display with an empty timer box. I only want to show relevant things on my wall display, and for me, a lack of timers is not relevant.

I wanted to say I made a few changes to this since posting, mostly changing the display name of unnamed timers. I changed it so it calls the timers by the original length of the timer, which is how the Alexa refers to them, so you can say things like “Alexa, cancel the 10 minute timer”.

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++) {
        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 header="Echo Timers" 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);
1 Like

In case anyone was curious what this card looks like:

3 Likes

-edited - managed to get it all working :slight_smile:

Request - is there a way to add a title? the card disappears if there is no timer set

For anybody else stumped with how to get the 3 dots to show up in Settings->Dashboards…

You need to enable “Advanced mode” in your user profile to see the “Resources” tab.

I must have missed some step along the way or maybe I misunderstood … Any help would be appreciated…

I have the alexa media player installed and can see all the _next_timer entities for my alexa devices.

I use studio code server to handle files such as config.

I created config\www\card-alexa-timers.js and it seems OK. I then added to my resources page as /local/card-alexa-timers.js and have restarted HA but when I try to use the integration I get No card type found or No card type configured.

I don’t see any errors in the logs

Is there a way to check what the issue is? Can I confirm the js loaded ok? (my skill is not in js)

Thanks in advance



alexa timers3

It looks like you may just need to edit you card configuration… as the error states you do not have a type at the top level:

type: custom:card-alexa-timers
entities:
  - sensor.dining_room_echo_dot_next_timer
  - sensor.kitchen_echo_dot_next_timer
  - sensor.living_room_echo_dot_next_timer
  - sensor.nigel_s_echo_flex_next_timer

Many thanks - I stared at it for some while but missed the obvious! that solved it.

1 Like

Hey There! First - thanks for this card. It’s exactly what I was looking for.

One thing I’m struggling to figure out is how to increase the font size of the timers themselves for a tablet display. I was able to do this with the card-mod, but the text pulses (starts small and then increases to the font size I’ve set below). Is there a better way to do this? Thanks!

type: custom:card-alexa-timers
entities:
  - sensor.kitchen_echo_dot_next_timer
card_mod:
  style: |
    ha-card {
      font-size: 30px;
    }

I vaguely remember having a similar problem, but it’s been a while. This is what I’m using, inside my theme’s .yaml code, to set it to 60px for my wall display to be easy to read. Not sure if the transition is what’s making it not resize. I can’t recall.

card-mod-card: |
    ha-card.alexa-timers * {
      font-size: 60px;
      line-height: 80px;
      transition: none !important;
    }

Thank you so much for your reply! Your example was just what I needed; it must have been the transition piece I was missing.

This is what ended up working for my specific use-case:

card_mod:
  style: |
    ha-card {
      font-size: 40px;
      line-height: 40px;
      transition: none !important;
    }

@Kethlak this is GREAT and works perfectly.

I’m now trying to figure out how to create 3 custom sensors for up to 3 active timers. I need them as sensors because the OpenHASP display project only takes sensors as parameters for the display.

Here’s sort of a mockup of where I’m going to put the timer text:
image

It can be on any page, but it needs to get its data from an existing sensor

Perhaps I could create 3 pairs of text “helpers” and then populate them in this part of your code?

      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>";

//something like this (Im NOT a js coder, but this is basically what I want to do, I just dont know how):

          sensor.alexa_timer_(i)_name == name 
          sensor.alexa_timer_(i)_timeleft == timeLeft
    }

@jazzmonger I code a lot in JavaScript, but this is my first Home Assistant module using JavaScript. That part of the code is looping through the collected alarms, so that would be the right place to put code that updates sensors. My code uses sensors, but only to read from them (it’s based on Custom Cards at Custom Cards | Home Assistant Developer Docs).

Time for wild guesses!!
It looks like theoretically you should be able to set the sensor’s values, but it’s probably going to be something more like this (PLEASE NOTE I HAVE NOT TESTED THIS):

if(i == 0) {
    this.hass.states[sensor.alexa_timer_0_name] = name;
    this.hass.states[sensor.alexa_timer_0_timeleft] = timeLeft;
}
if(i == 1) {
    this.hass.states[sensor.alexa_timer_1_name] = name;
    this.hass.states[sensor.alexa_timer_1_timeleft] = timeLeft;
}
if(i == 2) {
    this.hass.states[sensor.alexa_timer_2_name] = name;
    this.hass.states[sensor.alexa_timer_2_timeleft] = timeLeft;
}

There’s probably a more elegant way of doing this, but my brain is a bit tired. Also in order for this to work, you need to create your own sensors in the configuration.yaml file (or an included file if you’re like me), which would maybe look something like this (AGAIN, NOT TESTED):

  - platform: template
    sensors:
      alexa_timer_0_name:
        unique_id: sensor.alexa_timer_0_name

I’d suggest asking in a separate post, maybe in the Development forum? It feels to me like there’s a better way of doing this, and what I posted above is just a wild guess at how I would do this. It feels like there’s probably a way of giving custom sensors properties, so maybe you could have a sensor.alexa_timer_0 sensor with a “name” and “timeleft” property. But I don’t know the right syntax for any of this. I also don’t know if the sensor you create needs to have a default value of some kind, or if “template” is the right platform (I’m kind of flying by the seat of my pants in Home Assistant). I think what you’re trying to do should be straightforward, but I don’t know the right magic words to make it work. Good luck!

Awesome. Interesting ideas. I’ll try them tomorrow when I’m not on an iPad!

I agree, I will also create a new post. This is complicated stuff. I’m honestly not sure why the folks writing the Alexa integration didn’t expose the timers. It’s such a basic feature that is used by everyone, and countless times a day.

New posting here.

Unfortunately your idea doesn’t load… Where are Lovelace errors reported? they aren’t in the HOME ASSISTANT logs.

@jazzmonger The more I look into this, the more I think that you might not be able to change the sensors with a module. Home Assistant is updating the hass object in the module, and I don’t think it’s ever affecting anything external to the card. Basically, you can only read, not write from there.

However, I think maybe what you want can be done with the yaml if you only have one Alexa device.

A kind of pseudocode for this would look like this:

  - platform: template
    sensors:
      alexa_timer_0_name:
        unique_id: sensor.alexa_timer_0_name
        value_template: {get the name of the 0th timer on the device}
  - platform: template
    sensors:
      alexa_timer_0_timeleft:
        unique_id: sensor.alexa_timer_0_timeleft
        value_template: {get and format the time left of the 0th timer on the device}

Otherwise you may need to write an integration, and think those are done in Python, which I am only passingly familiar with. Sorry, this seems to be out of my depth.

yeah, the value template is what I’m struggling with!! I think its going to take a regex which is a huge PITA!

Jeff

@Kethlak Hi Cassandra
This has ben a really useful bit of script so many thanks for sharing it.

I recently installed Home Assistant 2023.4.1 and when I checked the log I see a lot of errors related to the script … I don’t recall seeing these before but maybe I was not looking … I don’t always check the log. it looks like an initiation issue as once everything is online they shop … I am guessing the timer returning unavailable until that point.

http://:8123/local/card-alexa-timers.js:38:58 Uncaught TypeError: Cannot read properties of undefined (reading ‘attributes’)
17:43:10 – (ERROR) components/system_log/init.py - message first occurred at 17:40:01 and shows up 374 times

Is anyone else seeing this or is it just me? I am happy to ignore them but would ideally like to see if I can avoid them … is there a change that could be made to either wait or check for unavailable or delay start up… it may just be a timing issue and some process order or speed of start up has changed. Many thanks