Skip to content

Instantly share code, notes, and snippets.

@Murdo-C
Created July 16, 2025 19:07
Show Gist options
  • Select an option

  • Save Murdo-C/185462ea9fe4475f079f6fd9075e6361 to your computer and use it in GitHub Desktop.

Select an option

Save Murdo-C/185462ea9fe4475f079f6fd9075e6361 to your computer and use it in GitHub Desktop.
TRMNL Plugin - Home Status with Time
<!-- Highcharts + Chartkick -->
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
<script src="https://code.highcharts.com/modules/pattern-fill.js"></script>
<script src="https://code.highcharts.com/modules/timezone.js"></script>
<!-- Layout -->
<div class="layout layout--col gap--space-between">
<div class="grid grid--cols-3">
<div class="item">
<div class="meta"></div>
<div class="content">
<span id="latest-aq" class="value value--tnums"></span>
<span class="label">Living AQ (ppm)</span>
</div>
</div>
<div class="item">
<div class="meta"></div>
<div class="content">
<span id="latest-living-temp" class="value value--tnums"></span>
<span class="label">Living Temp (°C)</span>
</div>
</div>
<div class="item">
<div class="meta"></div>
<div class="content">
<span id="latest-elena-temp" class="value value--tnums"></span>
<span class="label">Elena Temp (°C)</span>
</div>
</div>
</div>
<div id="google-sheet-chart-demo" class="w-full"></div>
</div>
<div class="title_bar">
<img class="image" src="https://www.svgrepo.com/show/22031/home-icon-silhouette.svg" />
<span class="title">Home Status</span>
<span class="instance">Last updated: <span id="last-updated"></span></span>
</div>
<script type="text/javascript">
var timeZone = "{{ trmnl.user.time_zone_iana || 'America/Toronto' }}";
console.log("Device-reported timeZone:", timeZone);
var rawCSV = `{% for row in data offset:1 %}{{ row[0] }},{{ row[1] }},{{ row[2] }},{{ row[3] }},{{ row[4] }},{{ row[5] }}{% unless forloop.last %}\n{% endunless %}{% endfor %}`;
var rows = rawCSV.trim().split("\n");
var now = new Date();
var cutoff = now.getTime() - 24 * 60 * 60 * 1000;
var chart_data = {
"Living Temp": [],
"Elena Temp": [],
"Living Humidity": [],
"Elena Humidity": []
};
var latestRow = null;
var lastTimestamp = null;
rows.forEach(function(line, idx) {
var [timestampStr, ltStr, lhStr, aqStr, etStr, ehStr] = line.split(",");
var date = new Date(timestampStr);
var ts = date.getTime();
if (ts < cutoff) return;
var lt = parseFloat(ltStr);
var lh = parseFloat(lhStr);
var et = parseFloat(etStr);
var eh = parseFloat(ehStr);
var aq = parseFloat(aqStr);
if ([lt, lh, et, eh, aq].some(v => isNaN(v))) {
console.warn("Skipping invalid row:", idx, line);
return;
}
chart_data["Living Temp"].push([ts, lt]);
chart_data["Elena Temp"].push([ts, et]);
chart_data["Living Humidity"].push([ts, lh]);
chart_data["Elena Humidity"].push([ts, eh]);
latestRow = { ts: timestampStr, lt, et, aq };
lastTimestamp = ts;
});
console.log("Chart Data Sample (Living Temp):", chart_data["Living Temp"].slice(-3));
if (latestRow) {
document.getElementById("latest-aq").innerText = latestRow.aq.toFixed(0);
document.getElementById("latest-living-temp").innerText = latestRow.lt.toFixed(1);
document.getElementById("latest-elena-temp").innerText = latestRow.et.toFixed(1);
var date = new Date(latestRow.ts);
var formatter = new Intl.DateTimeFormat("en-CA", {
timeZone: timeZone,
dateStyle: "medium",
timeStyle: "short"
});
document.getElementById("last-updated").innerText = formatter.format(date);
} else {
document.getElementById("last-updated").innerText = "No recent data";
}
Highcharts.setOptions({
time: {
useUTC: true,
timezone: timeZone
}
});
var series = [
{
name: "Living Temp",
data: chart_data["Living Temp"],
yAxis: 0,
lineWidth: 3,
zIndex: 3,
color: "#000000"
},
{
name: "Elena Temp",
data: chart_data["Elena Temp"],
yAxis: 0,
lineWidth: 3,
zIndex: 2,
color: {
pattern: {
image: "https://usetrmnl.com/images/grayscale/gray-4.png",
width: 12,
height: 12
}
}
},
{
name: "Living Humidity",
data: chart_data["Living Humidity"],
yAxis: 1,
lineWidth: 3,
zIndex: 1,
color: {
pattern: {
image: "https://usetrmnl.com/images/grayscale/gray-5.png",
width: 12,
height: 12
}
}
},
{
name: "Elena Humidity",
data: chart_data["Elena Humidity"],
yAxis: 1,
lineWidth: 3,
zIndex: 0,
color: {
pattern: {
image: "https://usetrmnl.com/images/grayscale/gray-6.png",
width: 12,
height: 12
}
}
}
];
var createChart = function () {
new Chartkick.LineChart("google-sheet-chart-demo", series, {
adapter: "highcharts",
curve: true,
points: false,
colors: ["#000000", "#333333", "#666666", "#999999"],
library: {
chart: { height: 320, zoomType: "x" },
yAxis: [
{
title: { text: "Temp (°C)" },
labels: { style: { fontSize: "16px", color: "#000000" } },
gridLineDashStyle: "shortdot",
gridLineWidth: 1,
tickAmount: 5
},
{
title: { text: "Humidity (%)" },
opposite: true,
labels: { style: { fontSize: "16px", color: "#000000" } },
gridLineWidth: 0,
tickAmount: 5,
max: 100,
min: 0
}
],
xAxis: {
labels: {
style: { fontSize: "16px", color: "#000000" },
formatter: function () {
var hoursAgo = Math.round((lastTimestamp - this.value) / 3600000);
var labelTime = new Date(lastTimestamp - hoursAgo * 3600000);
return labelTime.toLocaleTimeString("en-CA", {
timeZone: timeZone,
hour: "numeric",
minute: "2-digit"
});
}
},
type: "datetime",
gridLineDashStyle: "dot",
gridLineWidth: 1
},
plotOptions: {
series: {
lineWidth: 3,
animation: false
}
}
}
});
};
if ("Chartkick" in window) {
createChart();
} else {
window.addEventListener("chartkick:load", createChart, true);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment