Skip to content

Instantly share code, notes, and snippets.

@sam2332
Created September 3, 2024 20:18
Show Gist options
  • Save sam2332/96b2b6e66b6250b8401880c353bbc982 to your computer and use it in GitHub Desktop.
Save sam2332/96b2b6e66b6250b8401880c353bbc982 to your computer and use it in GitHub Desktop.
HomeAssistant Lovelace js module for showing durations of states, useful for person trackers
class StateDurationCard extends HTMLElement {
set hass(hass) {
this._hass = hass;
if (!this.content) {
const cardTitle = this.config.title || 'State Duration Totals';
this.innerHTML = `
<ha-card>
<div class="card-header">
<div class="name" style="cursor: pointer;">${cardTitle}</div>
</div>
<div class="card-content"></div>
</ha-card>
`;
this.content = this.querySelector('div.card-content');
// Click event on the title to navigate to the history page with date range
const titleElement = this.querySelector('div.card-header div.name');
titleElement.addEventListener('click', () => {
const hours = this.config.hours || 24;
const endDate = new Date();
const startDate = new Date(endDate.getTime() - hours * 60 * 60 * 1000);
const startDateStr = startDate.toISOString().slice(0, 19); // 'YYYY-MM-DDTHH:MM:SS'
const endDateStr = endDate.toISOString().slice(0, 19); // 'YYYY-MM-DDTHH:MM:SS'
window.location.href = `/history?entity_id=${this.config.entity}&start_date=${startDateStr}&end_date=${endDateStr}`;
});
this.updateContent();
}
}
async updateContent() {
const entityId = this.config.entity;
const hours = this.config.hours || 24; // Default to the last 24 hours
const history = await this.fetchHistory(entityId, hours);
if (history.length > 0 && history[0].length > 0) {
const convertedEntries = history[0].map(state => ({
...state,
last_changed: this.convertToLocalTime(state.last_changed)
}));
const totals = this.calculateTotals(convertedEntries);
// Convert the totals object to an array, sort by duration descending
const sortedTotals = Object.entries(totals).sort((a, b) => b[1] - a[1]);
let contentHTML = '<table width="100%">';
sortedTotals.forEach(([state, duration]) => {
const formattedDuration = this.formatDuration(duration);
contentHTML += `<tr><td>${state}</td><td>${formattedDuration}</td></tr>`;
});
contentHTML += '</table>';
this.content.innerHTML = contentHTML;
} else {
this.content.innerHTML = '<p>No data available for the selected period.</p>';
}
}
calculateTotals(states) {
const totals = {};
const minDuration = this.config.min_duration_seconds * 1000; // Convert seconds to milliseconds
for (let i = 0; i < states.length; i++) {
const current = states[i];
const next = states[i + 1];
let duration = 0;
if (next) {
duration = next.last_changed - current.last_changed;
} else {
duration = new Date() - current.last_changed; // Until now
}
if (duration >= minDuration) { // Only add durations above the minimum
if (!totals[current.state]) {
totals[current.state] = 0;
}
totals[current.state] += duration;
}
}
return totals;
}
formatDuration(ms) {
const hours = Math.floor(ms / (1000 * 60 * 60));
const minutes = Math.floor((ms / (1000 * 60)) % 60);
const seconds = Math.floor((ms / 1000) % 60);
let duration = '';
if (hours > 0) {
duration += `${hours} hours, `;
}
if (minutes > 0) {
duration += `${minutes} minutes, `;
}
if (seconds > 0) {
duration += `${seconds} seconds`;
}
return duration.trim().replace(/,\s*$/, ''); // Remove trailing comma if present
}
convertToLocalTime(utcDatetime) {
return new Date(utcDatetime);
}
async fetchHistory(entityId, hours) {
const endDate = new Date();
const startDate = new Date(endDate.getTime() - (hours * 60 * 60 * 1000));
const url = `history/period/${startDate.toISOString()}?filter_entity_id=${entityId}`;
const response = await this._hass.callApi('GET', url);
return response;
}
setConfig(config) {
if (!config.entity) {
throw new Error('You need to define an entity');
}
this.config = {
min_duration_seconds: 0, // Default value, showing all entries
...config
};
}
getCardSize() {
return 3;
}
}
customElements.define('state-duration-card', StateDurationCard);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment