Created
September 25, 2022 22:57
-
-
Save flashwave/6ce306a4b12a3f1e9d87379e53c39f34 to your computer and use it in GitHub Desktop.
temperature shittery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- -------------------------------------------------------- | |
-- Host: 127.0.0.1 | |
-- Server version: 10.6.9-MariaDB-1:10.6.9+maria~ubu2004 - mariadb.org binary distribution | |
-- Server OS: debian-linux-gnu | |
-- HeidiSQL Version: 12.1.0.6537 | |
-- -------------------------------------------------------- | |
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; | |
/*!40101 SET NAMES utf8 */; | |
/*!50503 SET NAMES utf8mb4 */; | |
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; | |
/*!40103 SET TIME_ZONE='+00:00' */; | |
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; | |
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; | |
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; | |
-- Dumping structure for table makai.fm_temperature | |
CREATE TABLE IF NOT EXISTS `fm_temperature` ( | |
`temp_celcius` double NOT NULL, | |
`temp_datetime` timestamp NOT NULL DEFAULT current_timestamp(), | |
KEY `temp_datetime` (`temp_datetime`) | |
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; | |
-- Data exporting was unselected. | |
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; | |
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; | |
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; | |
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; | |
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
define('FM_TEMP_KEY', 'balls'); | |
define('FM_TEMP_INT', 10); | |
ini_set('display_errors', 'on'); | |
error_reporting(-1); | |
date_default_timezone_set('UTC'); | |
mb_internal_encoding('UTF-8'); | |
try { | |
$pdo = new PDO('mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=website;charset=utf8mb4', 'website', 'balls', [ | |
PDO::ATTR_CASE => PDO::CASE_NATURAL, | |
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, | |
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, | |
PDO::ATTR_STRINGIFY_FETCHES => false, | |
PDO::ATTR_EMULATE_PREPARES => false, | |
PDO::MYSQL_ATTR_INIT_COMMAND => " | |
SET SESSION | |
sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', | |
time_zone = '+00:00'; | |
", | |
]); | |
} catch(Exception $ex) { | |
http_response_code(500); | |
echo '<h3>Unable to connect to database</h3>'; | |
die($ex->getMessage()); | |
} | |
if(isset($_POST['temp']) && isset($_POST['hash']) && isset($_POST['time'])) { | |
$temp = (string)filter_input(INPUT_POST, 'temp'); | |
$hash = (string)filter_input(INPUT_POST, 'hash'); | |
$time = (string)filter_input(INPUT_POST, 'time', FILTER_SANITIZE_NUMBER_INT); | |
if(!hash_equals(hash_hmac('sha256', $temp . '|' . $time, FM_TEMP_KEY), $hash)) | |
return; | |
$time = floor((int)json_decode($time) / FM_TEMP_INT); | |
if($time !== floor(time() / FM_TEMP_INT)) | |
return; | |
$insert = $pdo->prepare('INSERT INTO `fm_temperature` (`temp_celcius`) VALUES (:temp)'); | |
$insert->bindValue('temp', (string)json_decode($temp)); | |
$insert->execute(); | |
return; | |
} | |
if(isset($_GET['latest'])) { | |
$temps = $pdo->prepare('SELECT `temp_celcius` AS `tc`, UNIX_TIMESTAMP(`temp_datetime`) AS `dt` FROM `fm_temperature` ORDER BY `temp_datetime` DESC LIMIT 1'); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetch(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(isset($_GET['daily'])) { | |
$temps = $pdo->prepare('SELECT AVG(`temp_celcius`) AS `tc`, DATE_FORMAT(MIN(`temp_datetime`), \'%H:%i\') AS `ts` FROM `fm_temperature` WHERE `temp_datetime` > NOW() - INTERVAL 1 DAY GROUP BY FLOOR(UNIX_TIMESTAMP(`temp_datetime`) / (15 * 60)) ORDER BY `temp_datetime` ASC LIMIT 96'); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetchAll(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(isset($_GET['weekly'])) { | |
$temps = $pdo->prepare('SELECT MAX(`temp_celcius`) AS `tcmax`, AVG(`temp_celcius`) AS `tcavg`, MIN(`temp_celcius`) AS `tcmin`, DATE_FORMAT(`temp_datetime`, \'%X-%V\') AS `tw` FROM `fm_temperature` WHERE `temp_datetime` > NOW() - INTERVAL 1 YEAR GROUP BY YEARWEEK(`temp_datetime`) ORDER BY `temp_datetime` ASC LIMIT 52'); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetchAll(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(!empty($_GET['siri'])) { | |
header('Content-Type: text/plain; charset=utf-8'); | |
$temps = $pdo->prepare('SELECT `temp_celcius`, UNIX_TIMESTAMP(`temp_datetime`) AS `temp_datetime` FROM `fm_temperature` ORDER BY `temp_datetime` DESC LIMIT 1'); | |
$temps->execute(); | |
$temps = $temps->fetch(PDO::FETCH_ASSOC); | |
date_default_timezone_set('Europe/Amsterdam'); | |
printf('It was %2$.1f°C at %1$s.', date('H:i:s', $temps['temp_datetime']), $temps['temp_celcius']); | |
return; | |
} | |
?> | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>Room Temperature</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/> | |
<link href="/css/electrolize/style.css" type="text/css" rel="stylesheet"/> | |
<style type="text/css"> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
position: relative; | |
outline-style: none; | |
} | |
html, body { | |
width: 100%; | |
height: 100%; | |
} | |
body { | |
background-color: #111; | |
color: #fff; | |
font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif; | |
padding: 1px; | |
} | |
h2 { | |
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif; | |
} | |
.current { | |
text-align: center; | |
background-color: #333; | |
margin: 10px; | |
padding: 10px; | |
border-radius: 10px; | |
} | |
.current-temp { | |
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif; | |
font-size: 3em; | |
line-height: 1.2em; | |
} | |
.current-datetime { | |
font-size: .9em; | |
line-height: 1.4em; | |
} | |
.chart { | |
background-color: #ddd; | |
margin: 10px; | |
padding: 10px; | |
border-radius: 10px; | |
color: #666; | |
} | |
.chart-legend { | |
text-align: center; | |
} | |
.chart-body { | |
width: 100%; | |
height: 400px; | |
text-align: center; | |
} | |
.chart-labels { | |
width: 100%; | |
text-align: center; | |
} | |
.legend { | |
display: inline-block; | |
text-align: left; | |
min-width: 100px; | |
} | |
.legend-icon { | |
background-color: var(--lc); | |
width: 10px; | |
height: 10px; | |
display: inline-block; | |
} | |
.legend-name { | |
color: var(--lc); | |
font-weight: 700; | |
display: inline-block; | |
} | |
.bars { | |
height: 100%; | |
display: inline-block; | |
bottom: 0; | |
} | |
.bars-bar { | |
width: 100%; | |
bottom: 0; | |
position: absolute; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="main"> | |
<div class="current"> | |
<div class="current-temp"> | |
<span id="-last-temp">--</span> °C | |
</div> | |
<div class="current-datetime"> | |
<span id="-last-datetime">--:--:--</span> | |
</div> | |
</div> | |
<div class="chart" id="-chart-hr"> | |
<h2>15 minutely Temperature (24hr)</h2> | |
<p style="font-size: .8em">Add 1 or 2 hours to the displayed hour, I cannot be bothered to handle it myself.</p> | |
<div class="chart-body"> | |
</div> | |
<div class="chart-labels" style="height: 35px;"> | |
</div> | |
</div> | |
<div class="chart" id="-chart-wk"> | |
<h2>Weekly Temperature (1yr)</h2> | |
<div class="chart-legend"> | |
<div class="legend" style="--lc: #800;"> | |
<div class="legend-icon"></div> | |
<div class="legend-name">Maximum</div> | |
</div> | |
<div class="legend" style="--lc: #080;"> | |
<div class="legend-icon"></div> | |
<div class="legend-name">Average</div> | |
</div> | |
<div class="legend" style="--lc: #008;"> | |
<div class="legend-icon"></div> | |
<div class="legend-name">Minimum</div> | |
</div> | |
</div> | |
<div class="chart-body"> | |
</div> | |
<div class="chart-labels" style="height: 40px;"> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
var temp = { | |
lastTemp: null, | |
lastDateTime: null, | |
chartHr: null, | |
chartHrBody: null, | |
chartHrLabels: null, | |
chartHrBars: 24 * 60 / 15, | |
chartWk: null, | |
chartWkBody: null, | |
chartWkLabels: null, | |
chartWkBars: 52, | |
}; | |
temp.weightedNumber = function(n1, n2, w) { | |
w = Math.min(1, Math.max(0, w)); | |
return Math.round((n1 * w) + (n2 * (1 - w))); | |
}; | |
temp.weightedColour = function(c1, c2, w) { | |
if(typeof c1 === 'number') | |
c1 = [(c1 >> 16) & 0xFF, (c1 >> 8) & 0xFF, c1 & 0xFF]; | |
if(typeof c2 === 'number') | |
c2 = [(c2 >> 16) & 0xFF, (c2 >> 8) & 0xFF, c2 & 0xFF]; | |
var rgb = [ | |
this.weightedNumber(c1[0], c2[0], w), | |
this.weightedNumber(c1[1], c2[1], w), | |
this.weightedNumber(c1[2], c2[2], w), | |
]; | |
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; | |
}; | |
temp.weightedColourHex = function(c1, c2, w) { | |
return '#' + this.weightedColour(c1, c2, w).toString(16).padStart(6, '0'); | |
}; | |
temp.refreshLatest = function() { | |
var xhr = new XMLHttpRequest; | |
xhr.onload = function() { | |
this.updateLatest(JSON.parse(xhr.responseText)); | |
}.bind(this); | |
xhr.open('GET', '/temp.php?latest'); | |
xhr.send(); | |
}; | |
temp.updateLatest = function(temp) { | |
this.lastTemp.textContent = (parseInt(temp.tc * 10) / 10).toFixed(1).toLocaleString(); | |
this.lastDateTime.textContent = new Date(temp.dt * 1000).toLocaleString(); | |
}; | |
temp.refreshHr = function() { | |
var xhr = new XMLHttpRequest; | |
xhr.onload = function() { | |
this.updateHr(JSON.parse(xhr.responseText)); | |
}.bind(this); | |
xhr.open('GET', '/temp.php?daily'); | |
xhr.send(); | |
}; | |
temp.updateHr = function(temps) { | |
this.chartHrBody.innerHTML = ''; | |
this.chartHrLabels.innerHTML = ''; | |
for(var i = 0; i < temps.length; ++i) { | |
var temp = temps[i], | |
width = (100 / (temps.length + 12)).toString() + '%'; | |
var label = document.createElement('div'), | |
labelTxt = document.createElement('div'); | |
labelTxt.textContent = temp.ts; | |
labelTxt.style.transform = 'rotate(270deg)'; | |
labelTxt.style.fontSize = '.8em'; | |
labelTxt.style.top = '15px'; | |
label.title = temp.ts; | |
label.className = 'chart-body-bars bars'; | |
label.style.width = width; | |
label.style.margin = '0 .5px'; | |
label.appendChild(labelTxt); | |
this.chartHrLabels.appendChild(label); | |
var bars = document.createElement('div'); | |
bars.className = 'chart-body-bars bars'; | |
bars.style.width = width; | |
bars.style.margin = '0 .5px'; | |
this.chartHrBody.appendChild(bars); | |
var bar = document.createElement('div'), | |
barTxt = document.createElement('div'); | |
bar.className = 'bars-bar'; | |
var weight = ((temp.tc - 15) / 20); | |
barTxt.textContent = temp.tc.toFixed(1); | |
barTxt.style.transform = 'rotate(270deg)'; | |
barTxt.style.fontSize = '.8em'; | |
barTxt.style.top = '5px'; | |
barTxt.style.color = '#fff'; | |
bar.appendChild(barTxt); | |
bar.title = temp.tc.toFixed(2) + ' °C'; | |
bar.style.backgroundColor = this.weightedColourHex(0xFF0000, 0xFF, weight); | |
bar.style.overflow = 'hidden'; | |
bar.style.height = (weight * 100).toString() + '%'; | |
bars.appendChild(bar); | |
} | |
}; | |
temp.refreshWk = function() { | |
var xhr = new XMLHttpRequest; | |
xhr.onload = function() { | |
this.updateWk(JSON.parse(xhr.responseText)); | |
}.bind(this); | |
xhr.open('GET', '/temp.php?weekly'); | |
xhr.send(); | |
}; | |
temp.updateWk = function(temps) { | |
this.chartWkBody.innerHTML = ''; | |
this.chartWkLabels.innerHTML = ''; | |
for(var i = 0; i < temps.length; ++i) { | |
var temp = temps[i], | |
width = (100 / (this.chartWkBars + 3)).toString() + '%'; | |
var label = document.createElement('div'), | |
labelTxt = document.createElement('div'); | |
labelTxt.textContent = temp.tw; | |
labelTxt.style.transform = 'rotate(270deg)'; | |
labelTxt.style.fontSize = '.8em'; | |
labelTxt.style.top = '20px'; | |
label.title = temp.tw; | |
label.className = 'chart-body-bars bars'; | |
label.style.width = width; | |
label.style.margin = '0 .5px'; | |
label.appendChild(labelTxt); | |
this.chartWkLabels.appendChild(label); | |
var bars = document.createElement('div'); | |
bars.className = 'chart-body-bars bars'; | |
bars.style.width = width; | |
bars.style.margin = '0 .5px'; | |
this.chartWkBody.appendChild(bars); | |
var max = document.createElement('div'), | |
maxTxt = document.createElement('div'), | |
avg = document.createElement('div'), | |
avgTxt = document.createElement('div'), | |
min = document.createElement('div'), | |
minTxt = document.createElement('div'); | |
max.className = avg.className = min.className = 'bars-bar'; | |
var wMin = (temp.tcmin - 15) / 15, | |
wAvg = (temp.tcavg - 20) / 10, | |
wMax = (temp.tcmax - 20) / 15; | |
maxTxt.textContent = temp.tcmax.toFixed(1); | |
maxTxt.style.transform = 'rotate(270deg)'; | |
maxTxt.style.fontSize = '.8em'; | |
maxTxt.style.top = '5px'; | |
maxTxt.style.color = '#fff'; | |
max.appendChild(maxTxt); | |
max.title = temp.tcmax.toFixed(2) + ' °C'; | |
max.style.backgroundColor = this.weightedColourHex(0xFF0000, 0x800000, wMax); | |
max.style.overflow = 'hidden'; | |
max.style.height = (((temp.tcmax - 15) / 20) * 100).toString() + '%'; | |
bars.appendChild(max); | |
avgTxt.textContent = temp.tcavg.toFixed(1); | |
avgTxt.style.transform = 'rotate(270deg)'; | |
avgTxt.style.fontSize = '.8em'; | |
avgTxt.style.top = '5px'; | |
avgTxt.style.color = '#fff'; | |
avg.appendChild(avgTxt); | |
avg.title = temp.tcavg.toFixed(2) + ' °C'; | |
avg.style.backgroundColor = this.weightedColourHex(0xFF00, 0x8000, wAvg); | |
avg.style.overflow = 'hidden'; | |
avg.style.height = (((temp.tcavg - 15) / 20) * 100).toString() + '%'; | |
bars.appendChild(avg); | |
minTxt.textContent = temp.tcmin.toFixed(1); | |
minTxt.style.transform = 'rotate(270deg)'; | |
minTxt.style.fontSize = '.8em'; | |
minTxt.style.top = '5px'; | |
minTxt.style.color = '#fff'; | |
min.appendChild(minTxt); | |
min.title = temp.tcmin.toFixed(2) + ' °C'; | |
min.style.backgroundColor = this.weightedColourHex(0x80, 0xFF, wMin); | |
min.style.overflow = 'hidden'; | |
min.style.height = (((temp.tcmin - 15) / 20) * 100).toString() + '%'; | |
bars.appendChild(min); | |
} | |
}; | |
temp.loadFake = function() { | |
this.updateHr(this.fakeForHourly()); | |
this.updateWk(this.fakeForWeekly()); | |
}; | |
temp.fakeForHourly = function() { | |
var temps = []; | |
for(var i = 0; i < this.chartHrBars; ++i) | |
temps.push({ tc: (20 * ((i + 1) / this.chartHrBars)) + 15, ts: i.toString() }); | |
return temps; | |
}; | |
temp.fakeForWeekly = function() { | |
var wMin = (temp.tcmin - 15) / 15, | |
wAvg = (temp.tcavg - 20) / 10, | |
wMax = (temp.tcmax - 20) / 15; | |
var temps = []; | |
for(var i = 0; i < this.chartWkBars; ++i) | |
temps.push({ | |
tcmax: (15 * ((i + 1) / this.chartWkBars)) + 20, | |
tcavg: (10 * ((i + 1) / this.chartWkBars)) + 20, | |
tcmin: (15 * ((i + 1) / this.chartWkBars)) + 15, | |
tw: i.toString(), | |
}); | |
return temps; | |
}; | |
window.onload = function() { | |
temp.lastTemp = document.getElementById('-last-temp'); | |
temp.lastDateTime = document.getElementById('-last-datetime'); | |
temp.chartHr = document.getElementById('-chart-hr'); | |
temp.chartHrBody = temp.chartHr.getElementsByClassName('chart-body')[0]; | |
temp.chartHrLabels = temp.chartHr.getElementsByClassName('chart-labels')[0]; | |
temp.chartWk = document.getElementById('-chart-wk'); | |
temp.chartWkBody = temp.chartWk.getElementsByClassName('chart-body')[0]; | |
temp.chartWkLabels = temp.chartWk.getElementsByClassName('chart-labels')[0]; | |
temp.refreshLatest(); | |
setInterval(temp.refreshLatest.bind(temp), 5 * 60 * 1000); | |
temp.refreshHr(); | |
setInterval(temp.refreshHr.bind(temp), 15 * 60 * 1000); | |
temp.refreshWk(); | |
setInterval(temp.refreshWk.bind(temp), 7 * 24 * 60 * 1000); | |
}; | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import hmac | |
import json | |
import requests | |
import time | |
from sense_hat import SenseHat | |
HURL='https://flash.moe/temp.php' | |
HKEY=b'balls' | |
HOFF=10.8 | |
sh = SenseHat() | |
temp = sh.get_temperature_from_humidity() - HOFF | |
time = json.dumps(int(time.time())) | |
temp = json.dumps(temp) | |
hash = hmac.digest(HKEY, (temp + '|' + time).encode(), 'sha256').hex() | |
print(requests.post(HURL, data={ | |
'temp': temp, | |
'hash': hash, | |
'time': time, | |
}).text) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
define('FM_TEMP_KEY', 'balls'); | |
define('FM_TEMP_INT', 10); | |
ini_set('display_errors', 'on'); | |
error_reporting(-1); | |
date_default_timezone_set('UTC'); | |
mb_internal_encoding('UTF-8'); | |
try { | |
$pdo = new PDO('mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=website;charset=utf8mb4', 'website', 'balls', [ | |
PDO::ATTR_CASE => PDO::CASE_NATURAL, | |
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, | |
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, | |
PDO::ATTR_STRINGIFY_FETCHES => false, | |
PDO::ATTR_EMULATE_PREPARES => false, | |
PDO::MYSQL_ATTR_INIT_COMMAND => " | |
SET SESSION | |
sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', | |
time_zone = '+00:00'; | |
", | |
]); | |
} catch(Exception $ex) { | |
http_response_code(500); | |
echo '<h3>Unable to connect to database</h3>'; | |
die($ex->getMessage()); | |
} | |
if(isset($_POST['temp']) && isset($_POST['hash']) && isset($_POST['time'])) { | |
$temp = (string)filter_input(INPUT_POST, 'temp'); | |
$hash = (string)filter_input(INPUT_POST, 'hash'); | |
$time = (string)filter_input(INPUT_POST, 'time', FILTER_SANITIZE_NUMBER_INT); | |
if(!hash_equals(hash_hmac('sha256', $temp . '|' . $time, FM_TEMP_KEY), $hash)) | |
return; | |
$time = floor((int)json_decode($time) / FM_TEMP_INT); | |
if($time !== floor(time() / FM_TEMP_INT)) | |
return; | |
$insert = $pdo->prepare('INSERT INTO `fm_temperature` (`temp_celcius`) VALUES (:temp)'); | |
$insert->bindValue('temp', (string)json_decode($temp)); | |
$insert->execute(); | |
return; | |
} | |
if(isset($_GET['since'])) { | |
$since = (int)filter_input(INPUT_GET, 'since', FILTER_SANITIZE_NUMBER_INT); | |
$temps = $pdo->prepare('SELECT `temp_celcius`, UNIX_TIMESTAMP(`temp_datetime`) AS `temp_datetime` FROM `fm_temperature` WHERE `temp_datetime` > FROM_UNIXTIME(:since) AND `temp_datetime` > NOW() - INTERVAL 1 DAY ORDER BY `temp_datetime` ASC LIMIT 288'); | |
$temps->bindValue('since', $since); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetchAll(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(isset($_GET['since_3mo'])) { | |
$since = (int)filter_input(INPUT_GET, 'since_3mo', FILTER_SANITIZE_NUMBER_INT); | |
$temps = $pdo->prepare('SELECT MAX(`temp_celcius`) AS `temp_celcius_max`, AVG(`temp_celcius`) AS `temp_celcius_avg`, MIN(`temp_celcius`) AS `temp_celcius_min`, UNIX_TIMESTAMP(DATE(`temp_datetime`)) AS `temp_datetime` FROM `fm_temperature` WHERE `temp_datetime` > DATE(FROM_UNIXTIME(:since)) AND `temp_datetime` > NOW() - INTERVAL 3 MONTH GROUP BY DATE(`temp_datetime`) ORDER BY `temp_datetime` ASC LIMIT 92'); | |
$temps->bindValue('since', $since); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetchAll(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(isset($_GET['since_6mo'])) { | |
$since = (int)filter_input(INPUT_GET, 'since_6mo', FILTER_SANITIZE_NUMBER_INT); | |
$temps = $pdo->prepare('SELECT MAX(`temp_celcius`) AS `temp_celcius_max`, AVG(`temp_celcius`) AS `temp_celcius_avg`, MIN(`temp_celcius`) AS `temp_celcius_min`, UNIX_TIMESTAMP(DATE(`temp_datetime`)) AS `temp_datetime` FROM `fm_temperature` WHERE `temp_datetime` > DATE(FROM_UNIXTIME(:since)) AND `temp_datetime` > NOW() - INTERVAL 6 MONTH GROUP BY DATE(`temp_datetime`) ORDER BY `temp_datetime` ASC LIMIT 92'); | |
$temps->bindValue('since', $since); | |
$temps->execute(); | |
header('Content-Type: application/json; charset=utf-8'); | |
echo json_encode($temps->fetchAll(PDO::FETCH_ASSOC)); | |
return; | |
} | |
if(!empty($_GET['siri'])) { | |
header('Content-Type: text/plain; charset=utf-8'); | |
$temps = $pdo->prepare('SELECT `temp_celcius`, UNIX_TIMESTAMP(`temp_datetime`) AS `temp_datetime` FROM `fm_temperature` ORDER BY `temp_datetime` DESC LIMIT 1'); | |
$temps->execute(); | |
$temps = $temps->fetch(PDO::FETCH_ASSOC); | |
date_default_timezone_set('Europe/Amsterdam'); | |
printf('It was %2$.1f°C at %1$s.', date('H:i:s', $temps['temp_datetime']), $temps['temp_celcius']); | |
return; | |
} | |
?> | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>Room Temperature</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/> | |
<link href="/css/electrolize/style.css" type="text/css" rel="stylesheet"/> | |
<style type="text/css"> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
position: relative; | |
outline-style: none; | |
} | |
html, body { | |
width: 100%; | |
height: 100%; | |
} | |
body { | |
background-color: #111; | |
color: #fff; | |
font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif; | |
padding: 1px; | |
} | |
.current { | |
text-align: center; | |
background-color: #333; | |
margin: 10px; | |
padding: 10px; | |
border-radius: 10px; | |
} | |
.current-temp { | |
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif; | |
font-size: 3em; | |
line-height: 1.2em; | |
} | |
.current-datetime { | |
font-size: .9em; | |
line-height: 1.4em; | |
} | |
.chart { | |
background-color: #ddd; | |
margin: 10px; | |
padding: 10px; | |
border-radius: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="main"> | |
<div class="current"> | |
<div class="current-temp"> | |
<span id="-last-temp">--</span> °C | |
</div> | |
<div class="current-datetime"> | |
<span id="-last-datetime">--:--:--</span> | |
</div> | |
</div> | |
<div class="chart"> | |
<canvas id="-chart" width="400" height="170"></canvas> | |
</div> | |
<div class="chart"> | |
<canvas id="-chart-mo" width="400" height="170"></canvas> | |
</div> | |
</div> | |
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script> | |
<script type="text/javascript"> | |
var temp = { | |
last: 0, | |
lastMo: 0, | |
history: [], | |
historyMo: [], | |
lastTemp: null, | |
lastDateTime: null, | |
chart: null, | |
chartMo: null, | |
}; | |
temp.getLastTemperature = function() { | |
if(this.history.length === 0) | |
return {temp_datetime: 0, temp_celcius: 0}; | |
return this.history[this.history.length - 1]; | |
}; | |
temp.refresh = function() { | |
var xhr = new XMLHttpRequest; | |
xhr.onload = function() { | |
var temps = JSON.parse(xhr.responseText); | |
for(var i = 0; i < temps.length; ++i) { | |
var temp = temps[i]; | |
this.last = temp.temp_datetime; | |
this.history.push(temp); | |
} | |
this.refreshUI(); | |
}.bind(this); | |
xhr.open('GET', '/temp2.php?since=' + encodeURIComponent(parseInt(this.last).toString())); | |
xhr.send(); | |
}; | |
temp.refreshUI = function() { | |
var temp = this.getLastTemperature(); | |
this.lastTemp.textContent = (parseInt(temp.temp_celcius * 10) / 10).toFixed(1).toLocaleString(); | |
this.lastDateTime.textContent = new Date(temp.temp_datetime * 1000).toLocaleString(); | |
var take = Math.min(288, this.history.length), | |
dataset = this.chart.data.datasets[0]; | |
this.chart.data.labels = []; | |
dataset.data = []; | |
for(var i = this.history.length - take; i < this.history.length; ++i) { | |
var temp = this.history[i]; | |
this.chart.data.labels.push(new Date(temp.temp_datetime * 1000).toLocaleString()); | |
dataset.data.push(temp.temp_celcius); | |
} | |
this.chart.update(); | |
}; | |
temp.refreshMo = function() { | |
var xhr = new XMLHttpRequest; | |
xhr.onload = function() { | |
var temps = JSON.parse(xhr.responseText); | |
for(var i = 0; i < temps.length; ++i) { | |
var temp = temps[i]; | |
this.lastMo = temp.temp_datetime; | |
this.historyMo.push(temp); | |
} | |
this.refreshUIMo(); | |
}.bind(this); | |
xhr.open('GET', '/temp2.php?since_6mo=' + encodeURIComponent(parseInt(this.lastMo).toString())); | |
xhr.send(); | |
}; | |
temp.refreshUIMo = function() { | |
var take = Math.min(92, this.historyMo.length), | |
datasetMax = this.chartMo.data.datasets[0], | |
datasetAvg = this.chartMo.data.datasets[1], | |
datasetMin = this.chartMo.data.datasets[2]; | |
this.chart.data.labels = []; | |
datasetMax.data = []; | |
datasetAvg.data = []; | |
datasetMin.data = []; | |
for(var i = this.historyMo.length - take; i < this.historyMo.length; ++i) { | |
var temp = this.historyMo[i]; | |
this.chartMo.data.labels.push(new Date(temp.temp_datetime * 1000).toDateString()); | |
datasetMax.data.push(temp.temp_celcius_max); | |
datasetAvg.data.push(temp.temp_celcius_avg); | |
datasetMin.data.push(temp.temp_celcius_min); | |
} | |
this.chartMo.update(); | |
}; | |
window.onload = function() { | |
temp.lastTemp = document.getElementById('-last-temp'); | |
temp.lastDateTime = document.getElementById('-last-datetime'); | |
var ctx = document.getElementById('-chart').getContext('2d'); | |
temp.chart = new Chart(ctx, { | |
type: 'line', | |
data: { | |
datasets: [ | |
{ | |
label: 'Temperature', | |
data: [], | |
borderColor: '#111', | |
backgroundColor: '#222', | |
}, | |
], | |
}, | |
options: { | |
responsive: true, | |
plugins: { | |
legend: { | |
position: 'top', | |
}, | |
title: { | |
display: true, | |
text: 'Temperature History', | |
}, | |
}, | |
scales: { | |
y: { | |
suggestedMax: 34, | |
suggestedMin: 20, | |
ticks: { | |
stepSize: 1, | |
}, | |
}, | |
}, | |
}, | |
}); | |
temp.refresh(); | |
setInterval(temp.refresh.bind(temp), 5 * 60 * 1000); | |
var ctxMo = document.getElementById('-chart-mo').getContext('2d'); | |
temp.chartMo = new Chart(ctxMo, { | |
type: 'line', | |
data: { | |
datasets: [ | |
{ | |
label: 'Maximum Temperature', | |
data: [], | |
borderColor: '#400', | |
backgroundColor: '#800', | |
}, | |
{ | |
label: 'Average Temperature', | |
data: [], | |
borderColor: '#040', | |
backgroundColor: '#080', | |
}, | |
{ | |
label: 'Minimum Temperature', | |
data: [], | |
borderColor: '#004', | |
backgroundColor: '#008', | |
}, | |
], | |
}, | |
options: { | |
responsive: true, | |
plugins: { | |
legend: { | |
position: 'top', | |
}, | |
title: { | |
display: true, | |
text: 'Daily Temperature (6 months)', | |
}, | |
}, | |
scales: { | |
y: { | |
suggestedMax: 34, | |
suggestedMin: 20, | |
ticks: { | |
stepSize: 1, | |
}, | |
}, | |
}, | |
}, | |
}); | |
temp.refreshMo(); | |
setInterval(temp.refreshMo.bind(temp), 31 * 24 * 60 * 1000); | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment