Compare commits
4 Commits
master
...
hotfix-mam
| Author | SHA1 | Date | |
|---|---|---|---|
| dd7a1e935a | |||
| e24d7c5a25 | |||
| 7c1167606c | |||
| bc29e0c6b7 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/.pio/
|
.pio
|
||||||
/.idea/
|
|
||||||
/data/http
|
|
||||||
cmake-build-*/
|
|
||||||
CMakeListsPrivate.txt
|
CMakeListsPrivate.txt
|
||||||
|
cmake-build-*/
|
||||||
|
/.idea
|
||||||
3
TODO.txt
3
TODO.txt
@ -13,6 +13,3 @@ Hardware:
|
|||||||
Heatsink
|
Heatsink
|
||||||
Extend Matrix (double?)
|
Extend Matrix (double?)
|
||||||
Microphone
|
Microphone
|
||||||
|
|
||||||
MQTT:
|
|
||||||
register globally, so the values are already there when switching mode
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M11.082 10H11V6h4v2.187a7.868 7.868 0 0 0-3.72 1.623 3.642 3.642 0 0 0-.198.19zM20 1h-4v4h4zm0 10.423l-.055.002a13.903 13.903 0 0 0-2.06.287 3.01 3.01 0 0 0-.744.288l.009.076a2.686 2.686 0 0 0 .267.312L19.623 15H20zM7.866 16.647a4.766 4.766 0 0 1-.45-.647H6v4h4v-2.09a2.986 2.986 0 0 1-2.134-1.263zM1 5h4V1H1zm0 10h4v-4H1z" opacity=".25"/>
|
|
||||||
<path d="M16 8.052V6h4v2.266a18.371 18.371 0 0 0-3.106-.253c-.301 0-.6.015-.894.039zM1 20h4v-4H1zm15-1.335V20h.92q-.491-.724-.92-1.335zM10 1H6v4h4z" opacity=".5"/>
|
|
||||||
<path
|
|
||||||
d="M21 11.385v5.245L19.623 15H20v-3.577zm-8.142 4.106a2.952 2.952 0 0 1-.19.509h1.314c-.074-.083-.153-.176-.218-.243-.115-.116-.444-.448-.547-.455a2.09 2.09 0 0 0-.359.19zM17.592 21H0V0h21v8.921l-.293-.316a1 1 0 0 0-.502-.293l-.15-.035L20 8.266V6h-4v2.052c-.34.028-.673.076-1 .135V6h-4v4h.082a3.274 3.274 0 0 0-1.005 1.928l-.051.038c-.01 0-.016.004-.026.004V11H6v4h1.036a3.013 3.013 0 0 0 .38 1H6v4h4v-2.09c.029 0 .057.011.085.011a1.951 1.951 0 0 0 .915-.225V20h4v-2.724c.298.4.633.866 1 1.389V20h.92c.215.317.44.651.672 1zM16 5h4V1h-4zm-5 0h4V1h-4zM6 5h4V1H6zm0 5h4V6H6zm-1 6H1v4h4zm0-5H1v4h4zm0-5H1v4h4zm0-5H1v4h4zm17.575 20.99l-.822.753a1.308 1.308 0 0 1-.882.343 1.383 1.383 0 0 1-.167-.01 1.307 1.307 0 0 1-.932-.585 74.561 74.561 0 0 0-5.288-7.428c-.454-.458-.79-.761-1.27-.761a2.326 2.326 0 0 0-1.262.603 2.36 2.36 0 0 1-1.306 1.84c-.267.187-.997.493-2.009-.734-1.01-1.23-.57-1.888-.333-2.114a2.358 2.358 0 0 1 2.06-.926c.087-.073.175-.14.262-.204.394-.298.483-.395.453-.671-.075-.671.837-1.513.846-1.521a7.907 7.907 0 0 1 4.969-1.562 17.494 17.494 0 0 1 2.932.237l.148.036 1.02 1.098-1.087.042a14.724 14.724 0 0 0-2.246.312 4.385 4.385 0 0 0-1.635.797l.016.06a4.093 4.093 0 0 1 .13.765 2.322 2.322 0 0 0 .541.739l5.979 7.084a1.303 1.303 0 0 1-.117 1.808zm-7.844-8.063a5.606 5.606 0 0 1 .837-.63 1.8 1.8 0 0 1-.393-.865 3.211 3.211 0 0 0-.103-.591.872.872 0 0 1 .215-.996 5.678 5.678 0 0 1 1.374-.83 6.687 6.687 0 0 0-4.08 1.315 2.255 2.255 0 0 0-.508.706 1.607 1.607 0 0 1-.845 1.529c-.091.068-.185.138-.274.216a.781.781 0 0 1-.585.193c-.6-.05-.733.034-1.374.646a1.479 1.479 0 0 0 .414.756 1.587 1.587 0 0 0 .674.547c.711-.506.82-.62.886-1.219a.784.784 0 0 1 .302-.537 3.354 3.354 0 0 1 1.943-.865 2.27 2.27 0 0 1 1.517.625zm7.197 6.9l-5.705-6.762a5.388 5.388 0 0 0-.781.564 83.715 83.715 0 0 1 5.169 7.316.308.308 0 0 0 .467.06l.821-.752a.306.306 0 0 0 .029-.425z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB |
@ -1,82 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<!--suppress HtmlFormInputWithoutLabel -->
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<link rel="stylesheet" href="main.css">
|
|
||||||
<title>RGBMatrixDisplay</title>
|
|
||||||
<link rel="icon" href="favicon.svg">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<a href="player.htm?index=0">Player 0</a><br>
|
|
||||||
<a href="player.htm?index=1">Player 1</a><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<a class="mode" id="mode0" onclick="sm(0)">NONE</a><br>
|
|
||||||
<a class="mode" id="mode1" onclick="sm(1)">BORDER</a><br>
|
|
||||||
<a class="mode" id="mode2" onclick="sm(2)">CLOCK</a><br>
|
|
||||||
<a class="mode" id="mode3" onclick="sm(3)">GAME_OF_LIFE_BLACK_WHITE</a><br>
|
|
||||||
<a class="mode" id="mode4" onclick="sm(4)">GAME_OF_LIFE_GRAYSCALE</a><br>
|
|
||||||
<a class="mode" id="mode5" onclick="sm(5)">GAME_OF_LIFE_COLOR_FADE</a><br>
|
|
||||||
<a class="mode" id="mode6" onclick="sm(6)">GAME_OF_LIFE_RANDOM_COLOR</a><br>
|
|
||||||
<a class="mode" id="mode7" onclick="sm(7)">PONG</a><br>
|
|
||||||
<a class="mode" id="mode8" onclick="sm(8)">SPACE_INVADERS</a><br>
|
|
||||||
<a class="mode" id="mode9" onclick="sm(9)">COUNT_DOWN</a><br>
|
|
||||||
<a class="mode" id="mode10" onclick="sm(10)">COUNT_DOWN_BARS</a><br>
|
|
||||||
<a class="mode" id="mode11" onclick="sm(11)">COUNT_DOWN_SLEEP</a><br>
|
|
||||||
<a class="mode" id="mode12" onclick="sm(12)">STARFIELD</a><br>
|
|
||||||
<a class="mode" id="mode13" onclick="sm(13)">MATRIX</a><br>
|
|
||||||
<a class="mode" id="mode14" onclick="sm(14)">POWER</a><br>
|
|
||||||
<a class="mode" id="mode15" onclick="sm(15)">ENERGY</a><br>
|
|
||||||
<a class="mode" id="mode16" onclick="sm(16)">TIMER</a><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<div>
|
|
||||||
<a onclick="br(-10)">Dunkler</a>/
|
|
||||||
<a onclick="br(+10)">Heller</a>
|
|
||||||
<span id="brightness">?</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a onclick="sp(0.9)">Langsamer</a>/
|
|
||||||
<a onclick="sp(1.1)">Schneller</a>
|
|
||||||
<span id="speed">?</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<div>
|
|
||||||
<input type="number" min="2025" max="3000" step="1" id="dlYe">
|
|
||||||
<input type="number" min="1" max="12" step="1" id="dlMo">
|
|
||||||
<input type="number" min="1" max="31" step="1" id="dlDa">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input type="number" min="0" max="23" step="1" id="dlHo">
|
|
||||||
<input type="number" min="0" max="59" step="1" id="dlMi">
|
|
||||||
<input type="number" min="0" max="59" step="1" id="dlSe">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button onclick="dl()">Datum setzen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<input type="number" min="1" max="999" step="1" id="tmDa">
|
|
||||||
<input type="number" min="0" max="23" step="1" id="tmHo">
|
|
||||||
<input type="number" min="0" max="59" step="1" id="tmMi">
|
|
||||||
<input type="number" min="0" max="59" step="1" id="tmSe">
|
|
||||||
<button onclick="tm()">Datum setzen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<button onclick="gf('/config/save');">Speichern erzwingen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 7vw;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.player {
|
|
||||||
width: 33vmin;
|
|
||||||
height: 33vmin;
|
|
||||||
font-size: 9vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, select, textarea, button {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paragraph {
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modeActive {
|
|
||||||
background-color: lightgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1000px) {
|
|
||||||
body {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
109
http/main.js
109
http/main.js
@ -1,109 +0,0 @@
|
|||||||
const index = parseInt(new URLSearchParams(window.location.search).get('index')) || 0;
|
|
||||||
|
|
||||||
const dlYe = document.getElementById("dlYe");
|
|
||||||
const dlMo = document.getElementById("dlMo");
|
|
||||||
const dlDa = document.getElementById("dlDa");
|
|
||||||
const dlHo = document.getElementById("dlHo");
|
|
||||||
const dlMi = document.getElementById("dlMi");
|
|
||||||
const dlSe = document.getElementById("dlSe");
|
|
||||||
|
|
||||||
const tmDa = document.getElementById("tmDa");
|
|
||||||
const tmHo = document.getElementById("tmHo");
|
|
||||||
const tmMi = document.getElementById("tmMi");
|
|
||||||
const tmSe = document.getElementById("tmSe");
|
|
||||||
|
|
||||||
const brightness = document.getElementById("brightness")
|
|
||||||
|
|
||||||
const speed = document.getElementById("speed")
|
|
||||||
|
|
||||||
let interval = undefined;
|
|
||||||
|
|
||||||
function dl() {
|
|
||||||
const y = parseInt(dlYe.value);
|
|
||||||
const M = parseInt(dlMo.value) - 1;
|
|
||||||
const d = parseInt(dlDa.value);
|
|
||||||
const h = parseInt(dlHo.value) || 0;
|
|
||||||
const m = parseInt(dlMi.value) || 0;
|
|
||||||
const s = parseInt(dlSe.value) || 0;
|
|
||||||
const deadlineEpoch = (new Date(y, M, d, h, m, s).getTime() / 1000).toFixed(0);
|
|
||||||
set("deadlineEpoch", deadlineEpoch);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tm() {
|
|
||||||
const d = parseInt(tmDa.value) || 0;
|
|
||||||
const h = parseInt(tmHo.value) || 0;
|
|
||||||
const m = parseInt(tmMi.value) || 0;
|
|
||||||
const s = parseInt(tmSe.value) || 0;
|
|
||||||
const timerMillis = (((d * 24 + h) * 60 + m) * 60 + s) * 1000;
|
|
||||||
set("timerMillis", timerMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sm = (v) => set('mode', v);
|
|
||||||
|
|
||||||
const br = (v) => set('brightness', v);
|
|
||||||
|
|
||||||
const sp = (v) => set('speed', v);
|
|
||||||
|
|
||||||
const set = (n, v) => gf(`/set?n=${n}&v=${v}`)
|
|
||||||
|
|
||||||
const fetch = () => {
|
|
||||||
// if (interval !== undefined) {
|
|
||||||
// clearInterval(interval);
|
|
||||||
// interval = undefined;
|
|
||||||
// }
|
|
||||||
// interval = setInterval(fetch, 2000);
|
|
||||||
get("/state", showState());
|
|
||||||
}
|
|
||||||
|
|
||||||
const gf = (path) => {
|
|
||||||
get(path, fetch);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(path, cb = null) {
|
|
||||||
const r = new XMLHttpRequest();
|
|
||||||
r.onreadystatechange = () => !!cb && r.readyState === 4 && r.status === 200 && cb(r);
|
|
||||||
r.open("GET", path, true);
|
|
||||||
r.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showState() {
|
|
||||||
return function (r) {
|
|
||||||
const json = JSON.parse(r.responseText);
|
|
||||||
|
|
||||||
// noinspection JSUnresolvedReference
|
|
||||||
const d = new Date(parseInt(json.config.deadlineEpoch || 0) * 1000);
|
|
||||||
dlYe.value = "" + d.getFullYear();
|
|
||||||
dlMo.value = "" + d.getMonth() + 1;
|
|
||||||
dlDa.value = "" + d.getDate();
|
|
||||||
dlHo.value = "" + d.getHours();
|
|
||||||
dlMi.value = "" + d.getMinutes();
|
|
||||||
dlSe.value = "" + d.getSeconds();
|
|
||||||
|
|
||||||
// noinspection JSUnresolvedReference
|
|
||||||
const s = parseInt(json.config.timerMillis || 0) / 1000;
|
|
||||||
const m = Math.floor(s / 60);
|
|
||||||
const h = Math.floor(m / 60);
|
|
||||||
tmDa.value = "" + Math.floor(h / 24);
|
|
||||||
tmHo.value = "" + h % 24;
|
|
||||||
tmMi.value = "" + m % 60;
|
|
||||||
tmSe.value = "" + s % 60;
|
|
||||||
|
|
||||||
const id = parseInt(json.config.mode);
|
|
||||||
for (const mode of document.getElementsByClassName("mode")) {
|
|
||||||
if (mode.id === "mode" + id) {
|
|
||||||
if (!mode.classList.contains("modeActive")) {
|
|
||||||
mode.classList.add("modeActive");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mode.classList.remove("modeActive");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSUnresolvedReference
|
|
||||||
brightness.innerText = (parseInt(json.config.brightness) / 2.56).toFixed(0) + "%";
|
|
||||||
|
|
||||||
speed.innerText = parseInt(json.config.speed).toFixed(5) + "x";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch();
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<link rel="stylesheet" href="main.css">
|
|
||||||
<script src="main.js"></script>
|
|
||||||
<title>RGBMatrixDisplay</title>
|
|
||||||
<link rel="icon" href="favicon.svg">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><a href='/'>←</a></td>
|
|
||||||
<td>
|
|
||||||
<button class="player" onclick="get(`/player/move?index=${index}&x=0&y=-1`);">↑</button>
|
|
||||||
<br>
|
|
||||||
</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<button class="player" onclick="get(`/player/move?index=${index}&x=-1&y=0`);">←</button>
|
|
||||||
<br>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="player" onclick="get(`/player/fire?index=${index}`);">X</button>
|
|
||||||
<br>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="player" onclick="get(`/player/move?index=${index}&x=+1&y=0`);">→</button>
|
|
||||||
<br>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> </td>
|
|
||||||
<td>
|
|
||||||
<button class="player" onclick="get(`/player/move?index=${index}&x=0&y=+1`);">↓</button>
|
|
||||||
<br>
|
|
||||||
</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,11 +1,20 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[basic]
|
[basic]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.filesystem = littlefs
|
lib_deps = https://github.com/adafruit/Adafruit_NeoPixel
|
||||||
extra_scripts = pre:./scripts/prepare_http.py
|
https://github.com/knolleary/pubsubclient
|
||||||
lib_deps = ../Patrix
|
build_flags =
|
||||||
build_flags = -DWIFI_SSID=\"HappyNet\" -DWIFI_PKEY=\"1Grausame!Sackratte7\" -DWIFI_HOST=\"RGBMatrixDisplay\"
|
|
||||||
monitor_port = /dev/ttyUSB0
|
monitor_port = /dev/ttyUSB0
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
@ -14,8 +23,6 @@ monitor_filters = esp32_exception_decoder
|
|||||||
platform = ${basic.platform}
|
platform = ${basic.platform}
|
||||||
board = ${basic.board}
|
board = ${basic.board}
|
||||||
framework = ${basic.framework}
|
framework = ${basic.framework}
|
||||||
board_build.filesystem = ${basic.board_build.filesystem}
|
|
||||||
extra_scripts = ${basic.extra_scripts}
|
|
||||||
lib_deps = ${basic.lib_deps}
|
lib_deps = ${basic.lib_deps}
|
||||||
build_flags = ${basic.build_flags}
|
build_flags = ${basic.build_flags}
|
||||||
monitor_port = ${basic.monitor_port}
|
monitor_port = ${basic.monitor_port}
|
||||||
@ -28,8 +35,6 @@ upload_speed = 921600
|
|||||||
platform = ${basic.platform}
|
platform = ${basic.platform}
|
||||||
board = ${basic.board}
|
board = ${basic.board}
|
||||||
framework = ${basic.framework}
|
framework = ${basic.framework}
|
||||||
board_build.filesystem = ${basic.board_build.filesystem}
|
|
||||||
extra_scripts = ${basic.extra_scripts}
|
|
||||||
lib_deps = ${basic.lib_deps}
|
lib_deps = ${basic.lib_deps}
|
||||||
build_flags = ${basic.build_flags}
|
build_flags = ${basic.build_flags}
|
||||||
monitor_port = ${basic.monitor_port}
|
monitor_port = ${basic.monitor_port}
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
|
|
||||||
subprocess.run(["./scripts/prepare_http.sh"], check=True)
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SOURCE_DIR="./http"
|
|
||||||
DESTINATION_DIR="./data/http"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "+----------------------+"
|
|
||||||
echo "| minifying http files |"
|
|
||||||
echo "+----------------------+"
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/../" || exit 1
|
|
||||||
|
|
||||||
if [ -e "$DESTINATION_DIR" ]; then
|
|
||||||
rm -r "$DESTINATION_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$DESTINATION_DIR"
|
|
||||||
|
|
||||||
find "$SOURCE_DIR" -type f | while read -r src; do
|
|
||||||
dst="$DESTINATION_DIR/$(basename "$src").gz"
|
|
||||||
echo "source: $(du -sb --apparent-size "$src")"
|
|
||||||
minify "$src" | gzip > "$dst"
|
|
||||||
echo "destination: $(du -sb --apparent-size "$dst")"
|
|
||||||
echo
|
|
||||||
done
|
|
||||||
|
|
||||||
du -sh --apparent-size "$SOURCE_DIR"
|
|
||||||
du -sh --apparent-size "$DESTINATION_DIR"
|
|
||||||
|
|
||||||
echo "+--------------------+"
|
|
||||||
echo "| minifying COMPLETE |"
|
|
||||||
echo "+--------------------+"
|
|
||||||
echo
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
#include "BASICS.h"
|
#include "BASICS.h"
|
||||||
|
|
||||||
double doStep(const double valueCurrent, const double valueMin, const double valueMax, const long long millisecondsTotal, const microseconds_t microsecondsDelta) {
|
double doStep(double valueCurrent, double valueMin, double valueMax, long long millisecondsTotal, microseconds_t microsecondsDelta) {
|
||||||
const auto valueRange = valueMax - valueMin;
|
double valueRange = valueMax - valueMin;
|
||||||
const auto timeRatio = static_cast<double>(microsecondsDelta) / (static_cast<double>(millisecondsTotal) * 1000.0);
|
double timeRatio = (double) microsecondsDelta / ((double) millisecondsTotal * 1000.0);
|
||||||
const auto valueStep = valueRange * timeRatio;
|
double valueStep = valueRange * timeRatio;
|
||||||
const auto valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep));
|
double valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep));
|
||||||
return valueNew;
|
return valueNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool randomBool(const int uncertainty) {
|
bool randomBool(int uncertainty) {
|
||||||
return random(uncertainty) == 0;
|
return random(uncertainty) == 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
#ifndef BASICS_H
|
#ifndef BASICS_H
|
||||||
#define BASICS_H
|
#define BASICS_H
|
||||||
|
|
||||||
#include <patrix/display/Display.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#define X true
|
#define X true
|
||||||
#define _ false
|
#define _ false
|
||||||
|
|
||||||
|
#define ____ 0
|
||||||
|
#define QUAR 64
|
||||||
|
#define HALF 128
|
||||||
|
#define FULL 255
|
||||||
|
|
||||||
|
#define countof(x) (sizeof(x) / sizeof(x[0]))
|
||||||
|
|
||||||
typedef int64_t microseconds_t;
|
typedef int64_t microseconds_t;
|
||||||
|
|
||||||
typedef unsigned long milliseconds_t;
|
typedef unsigned long milliseconds_t;
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
|
|
||||||
#include <mode.h>
|
|
||||||
|
|
||||||
DisplayMatrix<32, 8> display(13);
|
|
||||||
|
|
||||||
void displaySetup() {
|
|
||||||
display.setup(config.get("brightness", 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayLoop() {
|
|
||||||
display.loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t setBrightness(const int brightness) {
|
|
||||||
uint8_t result = display.setBrightness(display.getBrightness() + brightness);
|
|
||||||
config.set("brightness", result);
|
|
||||||
debug("brightness = %d", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#ifndef DISPLAY_H
|
|
||||||
#define DISPLAY_H
|
|
||||||
|
|
||||||
#include <patrix/display/DisplayMatrix.h>
|
|
||||||
|
|
||||||
extern DisplayMatrix<32, 8> display;
|
|
||||||
|
|
||||||
void displaySetup();
|
|
||||||
|
|
||||||
void displayLoop();
|
|
||||||
|
|
||||||
uint8_t setBrightness(int brightness);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
35
src/Node.h
35
src/Node.h
@ -1,35 +0,0 @@
|
|||||||
#ifndef NODE_H
|
|
||||||
#define NODE_H
|
|
||||||
|
|
||||||
#include <patrix/node/PatrixNode.h>
|
|
||||||
|
|
||||||
#include <Display.h>
|
|
||||||
#include <http.h>
|
|
||||||
#include <mode.h>
|
|
||||||
|
|
||||||
class Node final : public PatrixNode {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Node() : PatrixNode(true, true, true) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() override {
|
|
||||||
displaySetup();
|
|
||||||
modeSetup();
|
|
||||||
patrixHttpSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() override {
|
|
||||||
modeLoop(display);
|
|
||||||
displayLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void mqttMessage(char *topic, char *message) override {
|
|
||||||
modeMqttMessage(topic, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
54
src/Vector.h
54
src/Vector.h
@ -1,54 +0,0 @@
|
|||||||
#ifndef POSITION_H
|
|
||||||
#define POSITION_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
class Vector {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
double x;
|
|
||||||
|
|
||||||
double y;
|
|
||||||
|
|
||||||
double length;
|
|
||||||
|
|
||||||
Vector() : x(0.0), y(0.0), length(0.0) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector(const double x, const double y) : x(x), y(y), length(sqrt(x * x + y * y)) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
static Vector polar(const long degrees, const double length) {
|
|
||||||
const auto radians = static_cast<double>(degrees) * DEG_TO_RAD;
|
|
||||||
return {
|
|
||||||
cos(radians) * length,
|
|
||||||
sin(radians) * length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector plus(const double _x, const double _y) const {
|
|
||||||
return {x + _x, y + _y};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector plus(const Vector vector) const {
|
|
||||||
return {x + vector.x, y + vector.y};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector minus(const double _x, const double _y) const {
|
|
||||||
return {x - _x, y - _y};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector minus(const Vector vector) const {
|
|
||||||
return {x - vector.x, y - vector.y};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector multiply(const double i) const {
|
|
||||||
return {x * i, y * i};
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
105
src/config.cpp
Normal file
105
src/config.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#include <EEPROM.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "display.h"
|
||||||
|
|
||||||
|
#define WRITE_DELAY_MS (30 * 1000)
|
||||||
|
|
||||||
|
uint32_t calculateChecksum(Config *ptr);
|
||||||
|
|
||||||
|
void config_defaults();
|
||||||
|
|
||||||
|
bool validateChecksum(uint32_t checksumEEPROM, Config &tmp);
|
||||||
|
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
bool dirty = false;
|
||||||
|
|
||||||
|
bool notify = false;
|
||||||
|
|
||||||
|
milliseconds_t lastDirtyMillis = 0;
|
||||||
|
|
||||||
|
void config_setup() {
|
||||||
|
if (!config_load()) {
|
||||||
|
config_defaults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_set_dirty() {
|
||||||
|
dirty = true;
|
||||||
|
lastDirtyMillis = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_loop() {
|
||||||
|
if (notify && millis() - lastDirtyMillis > WRITE_DELAY_MS + 2000) {
|
||||||
|
notify = false;
|
||||||
|
}
|
||||||
|
if (notify) {
|
||||||
|
bool blink = ((millis() - lastDirtyMillis) / 100) % 2 == 0;
|
||||||
|
display.set(0, 0, blink ? MAGENTA : BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (millis() - lastDirtyMillis <= 30000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty = false;
|
||||||
|
notify = true;
|
||||||
|
|
||||||
|
uint32_t checksum = calculateChecksum(&config);
|
||||||
|
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.writeBytes(0, &config, sizeof(Config));
|
||||||
|
EEPROM.writeUInt(sizeof(Config), checksum);
|
||||||
|
EEPROM.end();
|
||||||
|
|
||||||
|
Serial.printf("Config saved to EEPROM.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void configSaveNowIfDirty() {
|
||||||
|
if (dirty) {
|
||||||
|
lastDirtyMillis = millis() - 30000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool config_load() {
|
||||||
|
Config tmp{};
|
||||||
|
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.readBytes(0, &tmp, sizeof(Config));
|
||||||
|
uint32_t checksum = EEPROM.readUInt(sizeof(Config));
|
||||||
|
EEPROM.end();
|
||||||
|
|
||||||
|
bool success = validateChecksum(checksum, tmp);
|
||||||
|
if (success) {
|
||||||
|
memcpy(&config, &tmp, sizeof(Config));
|
||||||
|
Serial.printf("Config loaded from EEPROM.\n");
|
||||||
|
} else {
|
||||||
|
Serial.printf("Failed to load config from EEPROM.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateChecksum(uint32_t checksumEEPROM, Config &tmp) {
|
||||||
|
uint32_t calculated = calculateChecksum(&tmp);
|
||||||
|
return checksumEEPROM == calculated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_defaults() {
|
||||||
|
config.mode = COUNT_DOWN_SLEEP;
|
||||||
|
config.speed = 1.0;
|
||||||
|
config.brightness = 16;
|
||||||
|
config.date = {0};
|
||||||
|
Serial.printf("Config DEFAULTS loaded.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t calculateChecksum(Config *ptr) {
|
||||||
|
uint32_t checksum = 0;
|
||||||
|
for (auto *b = (uint8_t *) ptr; b < (uint8_t *) ptr + sizeof(Config); b++) {
|
||||||
|
checksum += *b;
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
25
src/config.h
Normal file
25
src/config.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef RGBMATRIXDISPLAY_CONFIG_H
|
||||||
|
#define RGBMATRIXDISPLAY_CONFIG_H
|
||||||
|
|
||||||
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
ModeId mode;
|
||||||
|
double speed;
|
||||||
|
uint8_t brightness;
|
||||||
|
tm date;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Config config;
|
||||||
|
|
||||||
|
void config_setup();
|
||||||
|
|
||||||
|
void config_loop();
|
||||||
|
|
||||||
|
void configSaveNowIfDirty();
|
||||||
|
|
||||||
|
bool config_load();
|
||||||
|
|
||||||
|
void config_set_dirty();
|
||||||
|
|
||||||
|
#endif
|
||||||
17
src/display.cpp
Normal file
17
src/display.cpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "display.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
Display display(32, 8);
|
||||||
|
|
||||||
|
void setBrightness(int brightness) {
|
||||||
|
brightness = max(1, min(brightness, 255));
|
||||||
|
if (display.getBrightness() == brightness) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.brightness = brightness;
|
||||||
|
display.setBrightness(brightness);
|
||||||
|
config_set_dirty();
|
||||||
|
|
||||||
|
Serial.printf("Setting brightness to %5.1f%%\n", brightness / 2.55);
|
||||||
|
}
|
||||||
10
src/display.h
Normal file
10
src/display.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef RGBMATRIXDISPLAY_DISPLAY_H
|
||||||
|
#define RGBMATRIXDISPLAY_DISPLAY_H
|
||||||
|
|
||||||
|
#include "display/Display.h"
|
||||||
|
|
||||||
|
extern Display display;
|
||||||
|
|
||||||
|
void setBrightness(int brightness);
|
||||||
|
|
||||||
|
#endif
|
||||||
29
src/display/Color.cpp
Normal file
29
src/display/Color.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "Color.h"
|
||||||
|
|
||||||
|
const Color BLACK = {____, ____, ____};
|
||||||
|
|
||||||
|
const Color WHITE = {FULL, FULL, FULL};
|
||||||
|
|
||||||
|
const Color RED = {FULL, ____, ____};
|
||||||
|
|
||||||
|
const Color GREEN = {____, FULL, ____};
|
||||||
|
|
||||||
|
const Color ORANGE = {FULL, QUAR, ____};
|
||||||
|
|
||||||
|
const Color BLUE = {____, ____, FULL};
|
||||||
|
|
||||||
|
const Color YELLOW = {FULL, FULL, ____};
|
||||||
|
|
||||||
|
const Color MAGENTA = {FULL, ____, FULL};
|
||||||
|
|
||||||
|
const Color VIOLET = {HALF, ____, FULL};
|
||||||
|
|
||||||
|
const Color TURQUOISE = {____, FULL, FULL};
|
||||||
|
|
||||||
|
Color gray(uint8_t brightness) {
|
||||||
|
return {brightness, brightness, brightness};
|
||||||
|
}
|
||||||
|
|
||||||
|
Color randomColor() {
|
||||||
|
return {(uint8_t) random(255), (uint8_t) random(255), (uint8_t) random(255)};
|
||||||
|
}
|
||||||
36
src/display/Color.h
Normal file
36
src/display/Color.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef PIXEL_H
|
||||||
|
#define PIXEL_H
|
||||||
|
|
||||||
|
#include "BASICS.h"
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
};
|
||||||
|
|
||||||
|
Color gray(uint8_t brightness);
|
||||||
|
|
||||||
|
Color randomColor();
|
||||||
|
|
||||||
|
extern const Color BLACK;
|
||||||
|
|
||||||
|
extern const Color WHITE;
|
||||||
|
|
||||||
|
extern const Color RED;
|
||||||
|
|
||||||
|
extern const Color GREEN;
|
||||||
|
|
||||||
|
extern const Color ORANGE;
|
||||||
|
|
||||||
|
extern const Color BLUE;
|
||||||
|
|
||||||
|
extern const Color YELLOW;
|
||||||
|
|
||||||
|
extern const Color MAGENTA;
|
||||||
|
|
||||||
|
extern const Color VIOLET;
|
||||||
|
|
||||||
|
extern const Color TURQUOISE;
|
||||||
|
|
||||||
|
#endif
|
||||||
180
src/display/Display.cpp
Normal file
180
src/display/Display.cpp
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
|
||||||
|
bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT] = {
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, _, X,
|
||||||
|
_, X, X,
|
||||||
|
X, _, X,
|
||||||
|
_, _, X,
|
||||||
|
_, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
X, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
_, X, X,
|
||||||
|
_, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
_, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
_, X, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
_, _, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, _, _,
|
||||||
|
_, X, _,
|
||||||
|
_, _, _,
|
||||||
|
_, X, _,
|
||||||
|
_, _, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, _, X,
|
||||||
|
_, _, X,
|
||||||
|
_, _, X,
|
||||||
|
X, _, X,
|
||||||
|
_, X, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
_, X, _,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
X, X, X,
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, _,
|
||||||
|
_, _, X,
|
||||||
|
_, X, _,
|
||||||
|
X, _, _,
|
||||||
|
_, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
_, X, _,
|
||||||
|
_, X, _,
|
||||||
|
_, X, _,
|
||||||
|
_, X, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, X, _,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
_, X, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
X, X, X,
|
||||||
|
X, _, X,
|
||||||
|
X, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, X, _,
|
||||||
|
X, _, X,
|
||||||
|
X, X, _,
|
||||||
|
X, X, _,
|
||||||
|
X, _, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, _, _,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
_, _, _,
|
||||||
|
},
|
||||||
|
// this must always be the last symbol (fallback)
|
||||||
|
{
|
||||||
|
X, X, X,
|
||||||
|
X, X, X,
|
||||||
|
X, X, X,
|
||||||
|
X, X, X,
|
||||||
|
X, X, X,
|
||||||
|
},
|
||||||
|
};
|
||||||
503
src/display/Display.h
Normal file
503
src/display/Display.h
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
#ifndef DISPLAY_H
|
||||||
|
#define DISPLAY_H
|
||||||
|
|
||||||
|
#include "Color.h"
|
||||||
|
#include "Adafruit_NeoPixel.h"
|
||||||
|
#include "Vector.h"
|
||||||
|
|
||||||
|
#define SYMBOL_COUNT 26
|
||||||
|
|
||||||
|
#define SYMBOL_J 11
|
||||||
|
#define SYMBOL_X 12
|
||||||
|
#define SYMBOL_DASH 13
|
||||||
|
#define SYMBOL_PERCENT 14
|
||||||
|
#define SYMBOL_T 15
|
||||||
|
#define SYMBOL_A 16
|
||||||
|
#define SYMBOL_G 17
|
||||||
|
#define SYMBOL_E 18
|
||||||
|
#define SYMBOL_H 19
|
||||||
|
#define SYMBOL_R 20
|
||||||
|
#define SYMBOL_I 21
|
||||||
|
#define SYMBOL_L 22
|
||||||
|
#define SYMBOL_SPACE 23
|
||||||
|
|
||||||
|
#define DISPLAY_CHAR_WIDTH 3
|
||||||
|
#define DISPLAY_CHAR_HEIGHT 5
|
||||||
|
|
||||||
|
extern bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT];
|
||||||
|
|
||||||
|
class Display {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
const uint8_t width;
|
||||||
|
|
||||||
|
const uint8_t height;
|
||||||
|
|
||||||
|
const size_t pixelCount;
|
||||||
|
|
||||||
|
const size_t pixelByteCount;
|
||||||
|
|
||||||
|
bool fpsShow = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Adafruit_NeoPixel leds;
|
||||||
|
|
||||||
|
milliseconds_t fpsLastMillis = 0;
|
||||||
|
|
||||||
|
int fps = 0;
|
||||||
|
|
||||||
|
Color *buffer = nullptr;
|
||||||
|
|
||||||
|
uint8_t brightness = 10;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Display(uint8_t width, uint8_t height) : width(width), height(height),
|
||||||
|
pixelCount(width * height),
|
||||||
|
pixelByteCount(pixelCount * sizeof(Color)),
|
||||||
|
leds(pixelCount, GPIO_NUM_13) {
|
||||||
|
buffer = (Color *) malloc(pixelByteCount);
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
Serial.print("+-----------------------------------------------+\n");
|
||||||
|
Serial.print("| OUT OF MEMORY: Cannot allocate double-buffer! |\n");
|
||||||
|
Serial.print("+-----------------------------------------------+\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Display() {
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(buffer);
|
||||||
|
buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
leds.begin();
|
||||||
|
clear();
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
calculateFPS();
|
||||||
|
drawFpsBorder();
|
||||||
|
if (isDirty()) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBrightness(uint8_t value) {
|
||||||
|
brightness = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getBrightness() const {
|
||||||
|
return brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(buffer, 0, pixelByteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ALIGN {
|
||||||
|
LEFT, RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t print2(int x, int y, double valueDbl, Color colorPositive, Color colorZero, Color colorNegative, ALIGN align = RIGHT) {
|
||||||
|
const Color color = valueDbl == 0 ? colorZero : (valueDbl < 0 ? colorNegative : colorPositive);
|
||||||
|
return print2(x, y, valueDbl, color, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t print2(int x, int y, double valueDbl, Color color, ALIGN align = RIGHT) {
|
||||||
|
if (isnan(valueDbl)) {
|
||||||
|
x -= 3 * (DISPLAY_CHAR_WIDTH + 1) - 1;
|
||||||
|
x += print(x, y, SYMBOL_DASH, color, true) + 1;
|
||||||
|
x += print(x, y, SYMBOL_DASH, color, true) + 1;
|
||||||
|
x += print(x, y, SYMBOL_DASH, color, true) + 1;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int value = (int) round(abs(valueDbl));
|
||||||
|
const bool negative = valueDbl < 0;
|
||||||
|
|
||||||
|
const int digitCount = (int) max(1.0, floor(log10(value)) + 1); // log10 is -inf for value==0, hence the max(1.0, ...)
|
||||||
|
if (align == RIGHT) {
|
||||||
|
x -= ((negative ? 1 : 0) + digitCount) * (DISPLAY_CHAR_WIDTH + 1) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int divider = (int) pow(10, digitCount - 1);
|
||||||
|
if (negative) {
|
||||||
|
x += print(x, y, SYMBOL_DASH, color, true) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool showIfZero = false;
|
||||||
|
// Serial.printf("x=%d, y=%d, value=%d, align=%s, digitCount=%d, divider=%d\n", x, y, value, align == LEFT ? "LEFT" : "RIGHT", digitCount, divider);
|
||||||
|
for (int digitPos = 0; digitPos < digitCount; ++digitPos) {
|
||||||
|
const int digitVal = value / divider % 10;
|
||||||
|
showIfZero |= digitVal != 0 || (digitPos == digitCount - 1);
|
||||||
|
x += print(x, y, digitVal, color, showIfZero) + 1;
|
||||||
|
// Serial.printf(" digitPos=%d, x=%d, y=%d, digitVal=%d, showIfZero=%s, divider=%d\n", digitPos, x, y, digitVal, showIfZero ? "true" : "false", divider);
|
||||||
|
divider /= 10;
|
||||||
|
}
|
||||||
|
// Serial.println();
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t print(uint8_t xPos, uint8_t yPos, uint8_t index, Color color, bool showIfZero) {
|
||||||
|
if (index == 0 && !showIfZero) {
|
||||||
|
return DISPLAY_CHAR_WIDTH;
|
||||||
|
}
|
||||||
|
if (index >= SYMBOL_COUNT) {
|
||||||
|
Serial.printf("Cannot print2 symbol #%u.\n", index);
|
||||||
|
index = SYMBOL_COUNT - 1;
|
||||||
|
}
|
||||||
|
bool *symbolBit = SYMBOLS[index];
|
||||||
|
for (uint8_t y = 0; y < DISPLAY_CHAR_HEIGHT; ++y) {
|
||||||
|
for (uint8_t x = 0; x < DISPLAY_CHAR_WIDTH; ++x) {
|
||||||
|
if (*(symbolBit++)) {
|
||||||
|
set(xPos + x, yPos + y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DISPLAY_CHAR_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REMOVE QUICK & DIRTY
|
||||||
|
uint8_t printM(uint8_t xPos, uint8_t yPos, Color color) {
|
||||||
|
bool sym[5][5] = {
|
||||||
|
{X,_,_,_,X},
|
||||||
|
{X,X,_,X,X},
|
||||||
|
{X,_,X,_,X},
|
||||||
|
{X,_,_,_,X},
|
||||||
|
{X,_,_,_,X},
|
||||||
|
};
|
||||||
|
for (int y = 0; y < countof(sym); ++y) {
|
||||||
|
for (int x = 0; x < countof(sym[0]); ++x) {
|
||||||
|
if (sym[y][x]) {
|
||||||
|
set(xPos + x, yPos + y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return countof(sym[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool doCreeperBlink() {
|
||||||
|
const auto now = millis();
|
||||||
|
static auto blink = false;
|
||||||
|
static auto last = now;
|
||||||
|
if (!blink) {
|
||||||
|
if (now - last >= 3000) {
|
||||||
|
last = now;
|
||||||
|
if (random(3) == 0) {
|
||||||
|
blink = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (now - last >= 500) {
|
||||||
|
last = now;
|
||||||
|
blink = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blink;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REMOVE QUICK & DIRTY
|
||||||
|
uint8_t printCreeper(uint8_t xPos, uint8_t yPos) {
|
||||||
|
const auto creeperBlink = doCreeperBlink();
|
||||||
|
bool sym[8][8] = {
|
||||||
|
{X, X, X, X, X, X, X, X},
|
||||||
|
{X, X, X, X, X, X, X, X},
|
||||||
|
{X, _, _, X, X, _, _, X},
|
||||||
|
{X, _, _, X, X, _, _, X},
|
||||||
|
{X, X, X, _, _, X, X, X},
|
||||||
|
{X, X, _, _, _, _, X, X},
|
||||||
|
{X, X, _, _, _, _, X, X},
|
||||||
|
{X, X, _, X, X, _, X, X},
|
||||||
|
};
|
||||||
|
for (int y = 0; y < countof(sym); ++y) {
|
||||||
|
for (int x = 0; x < countof(sym[0]); ++x) {
|
||||||
|
if (creeperBlink && ((x == 1 || x == 2) && y == 2)) {
|
||||||
|
set(xPos + x, yPos + y, GREEN);
|
||||||
|
} else {
|
||||||
|
set(xPos + x, yPos + y, sym[y][x] ? GREEN : BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return countof(sym[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REMOVE QUICK & DIRTY
|
||||||
|
uint8_t printHeart(uint8_t xPos, uint8_t yPos) {
|
||||||
|
bool a[1][1] = {
|
||||||
|
{X},
|
||||||
|
};
|
||||||
|
bool b[3][3] = {
|
||||||
|
{X, _, X},
|
||||||
|
{X, X, X},
|
||||||
|
{_, X, _},
|
||||||
|
};
|
||||||
|
bool c[5][5] = {
|
||||||
|
{_, X, _, X, _},
|
||||||
|
{X, _, X, _, X},
|
||||||
|
{X, _, _, _, X},
|
||||||
|
{_, X, _, X, _},
|
||||||
|
{_, _, X, _, _},
|
||||||
|
};
|
||||||
|
bool d[7][7] = {
|
||||||
|
{_, X, X, _, X, X, _},
|
||||||
|
{X, _, _, X, _, _, X},
|
||||||
|
{X, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, X, _},
|
||||||
|
{_, _, X, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _},
|
||||||
|
};
|
||||||
|
bool e[9][9] = {
|
||||||
|
{_, _, X, _, _, _, X, _, _},
|
||||||
|
{_, X, _, X, _, X, _, X, _},
|
||||||
|
{X, _, _, _, X, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, _, _, _},
|
||||||
|
};
|
||||||
|
bool f[11][11] = {
|
||||||
|
{_, _, X, X, _, _, _, X, X, _, _},
|
||||||
|
{_, X, _, _, X, _, X, _, _, X, _},
|
||||||
|
{X, _, _, _, _, X, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, X, _, _, _, _},
|
||||||
|
{_, _, _, _, _, X, _, _, _, _, _},
|
||||||
|
};
|
||||||
|
bool g[13][13] = {
|
||||||
|
{_, _, _, X, _, _, _, _, _, X, _, _, _},
|
||||||
|
{_, _, X, _, X, _, _, _, X, _, X, _, _},
|
||||||
|
{_, X, _, _, _, X, _, X, _, _, _, X, _},
|
||||||
|
{X, _, _, _, _, _, X, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, _, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _, _, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, _, _, X, _, _, _, _},
|
||||||
|
{_, _, _, _, _, X, _, X, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, X, _, _, _, _, _, _},
|
||||||
|
};
|
||||||
|
bool h[15][15] = {
|
||||||
|
{_, _, _, X, X, _, _, _, _, _, X, X, _, _, _},
|
||||||
|
{_, _, X, _, _, X, _, _, _, X, _, _, X, _, _},
|
||||||
|
{_, X, _, _, _, _, X, _, X, _, _, _, _, X, _},
|
||||||
|
{X, _, _, _, _, _, _, X, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, _, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, _, _, _, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _, _, _, _, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, _, _, _, _, X, _, _, _, _},
|
||||||
|
{_, _, _, _, _, X, _, _, _, X, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, X, _, X, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, X, _, _, _, _, _, _, _},
|
||||||
|
};
|
||||||
|
bool i[17][17] = {
|
||||||
|
{_, _, _, _, X, _, _, _, _, _, _, _, X, _, _, _, _},
|
||||||
|
{_, _, _, X, _, X, _, _, _, _, _, X, _, X, _, _, _},
|
||||||
|
{_, _, X, _, _, _, X, _, _, _, X, _, _, _, X, _, _},
|
||||||
|
{_, X, _, _, _, _, _, X, _, X, _, _, _, _, _, X, _},
|
||||||
|
{X, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, _, _, _, _, _, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _, _, _, _, _, _, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, _, _, _, _, _, _, X, _, _, _, _},
|
||||||
|
{_, _, _, _, _, X, _, _, _, _, _, X, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, X, _, _, _, X, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, X, _, X, _, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _},
|
||||||
|
};
|
||||||
|
bool j[19][19] = {
|
||||||
|
{_, _, _, _, X, X, _, _, _, _, _, _, _, X, X, _, _, _, _},
|
||||||
|
{_, _, _, X, _, _, X, _, _, _, _, _, X, _, _, X, _, _, _},
|
||||||
|
{_, _, X, _, _, _, _, X, _, _, _, X, _, _, _, _, X, _, _},
|
||||||
|
{_, X, _, _, _, _, _, _, X, _, X, _, _, _, _, _, _, X, _},
|
||||||
|
{X, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X},
|
||||||
|
{_, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _},
|
||||||
|
{_, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _, _},
|
||||||
|
{_, _, _, X, _, _, _, _, _, _, _, _, _, _, _, X, _, _, _},
|
||||||
|
{_, _, _, _, X, _, _, _, _, _, _, _, _, _, X, _, _, _, _},
|
||||||
|
{_, _, _, _, _, X, _, _, _, _, _, _, _, X, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, X, _, _, _, _, _, X, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, X, _, _, _, X, _, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, _, X, _, X, _, _, _, _, _, _, _, _},
|
||||||
|
{_, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto now = millis();
|
||||||
|
static auto step = 0;
|
||||||
|
static auto last = now;
|
||||||
|
if (now - last >= 100) {
|
||||||
|
last = now;
|
||||||
|
step = (step + 1) % 30;
|
||||||
|
}
|
||||||
|
bool *s;
|
||||||
|
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
s = (bool *) a;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
s = (bool *) b;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
s = (bool *) c;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
s = (bool *) d;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
s = (bool *) e;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
s = (bool *) f;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
s = (bool *) g;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
s = (bool *) h;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
s = (bool *) i;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
s = (bool *) j;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = step * 2 + 1;
|
||||||
|
for (int y = 0; y < size; ++y) {
|
||||||
|
for (int x = 0; x < size; ++x) {
|
||||||
|
if (s[y * size + x]) {
|
||||||
|
set(xPos + x - step, yPos + y - step, RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REMOVE QUICK & DIRTY
|
||||||
|
uint8_t printI(uint8_t xPos, uint8_t yPos, Color color) {
|
||||||
|
for (int y = 0; y < 5; ++y) {
|
||||||
|
set(xPos, yPos + y, color);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(Vector pos, Color color) {
|
||||||
|
set((uint8_t) round(pos.x), (uint8_t) round(pos.y), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(uint8_t x, uint8_t y, Color color) {
|
||||||
|
if (x < 0 || y < 0 || x >= width || y >= height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((y % 2) != 0) {
|
||||||
|
x = width - x - 1;
|
||||||
|
}
|
||||||
|
set(y * width + x, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(uint16_t index, Color color) {
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer[index] = {
|
||||||
|
// yes, correct order is GRB !!!
|
||||||
|
(uint8_t) (color.g * brightness >> 8),
|
||||||
|
(uint8_t) (color.r * brightness >> 8),
|
||||||
|
(uint8_t) (color.b * brightness >> 8)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(leds.getPixels(), buffer, pixelByteCount);
|
||||||
|
leds.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty() const {
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return memcmp(leds.getPixels(), buffer, pixelByteCount) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateFPS() {
|
||||||
|
fps = (int) round(1000.0 / (millis() - fpsLastMillis));
|
||||||
|
fpsLastMillis = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawFpsBorder() {
|
||||||
|
if (!fpsShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frames = fps;
|
||||||
|
|
||||||
|
Color color = RED;
|
||||||
|
if (frames > 3 * 76) {
|
||||||
|
frames -= 3 * 76;
|
||||||
|
color = WHITE;
|
||||||
|
} else if (frames > 2 * 76) {
|
||||||
|
frames -= 2 * 76;
|
||||||
|
color = BLUE;
|
||||||
|
} else if (frames > 76) {
|
||||||
|
frames -= 76;
|
||||||
|
color = GREEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x <= width - 1 && frames-- > 0; x++) {
|
||||||
|
set(x, 0, color);
|
||||||
|
}
|
||||||
|
for (int y = 0; y <= height - 1 && frames-- > 0; y++) {
|
||||||
|
set(width - 1, y, color);
|
||||||
|
}
|
||||||
|
for (int x = width - 1; x >= 0 && frames-- > 0; x--) {
|
||||||
|
set(x, height - 1, color);
|
||||||
|
}
|
||||||
|
for (int y = height - 1; y >= 0 && frames-- > 0; y--) {
|
||||||
|
set(0, y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
56
src/display/Vector.h
Normal file
56
src/display/Vector.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#ifndef POSITION_H
|
||||||
|
#define POSITION_H
|
||||||
|
|
||||||
|
#include "BASICS.h"
|
||||||
|
|
||||||
|
class Vector {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
double x;
|
||||||
|
|
||||||
|
double y;
|
||||||
|
|
||||||
|
double length;
|
||||||
|
|
||||||
|
Vector() :
|
||||||
|
x(0.0), y(0.0), length(0.0) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector(double x, double y) :
|
||||||
|
x(x), y(y), length(sqrt(x * x + y * y)) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector polar(long degrees, double length) {
|
||||||
|
double radians = (double) degrees * DEG_TO_RAD;
|
||||||
|
return {
|
||||||
|
cos(radians) * length,
|
||||||
|
sin(radians) * length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector plus(double _x, double _y) const {
|
||||||
|
return {x + _x, y + _y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector plus(Vector vector) const {
|
||||||
|
return {x + vector.x, y + vector.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector minus(double _x, double _y) const {
|
||||||
|
return {x - _x, y - _y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector minus(Vector vector) const {
|
||||||
|
return {x - vector.x, y - vector.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector multiply(double i) const {
|
||||||
|
return {x * i, y * i};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
86
src/http.cpp
86
src/http.cpp
@ -1,86 +0,0 @@
|
|||||||
#include "http.h"
|
|
||||||
|
|
||||||
#include <patrix/core/http.h>
|
|
||||||
|
|
||||||
#include <Display.h>
|
|
||||||
#include <mode.h>
|
|
||||||
|
|
||||||
// ReSharper disable CppLocalVariableMayBeConst
|
|
||||||
|
|
||||||
void httpState(AsyncWebServerRequest *request) {
|
|
||||||
auto doc = JsonDocument();
|
|
||||||
auto rootJson = doc.to<JsonObject>();
|
|
||||||
rootJson["config"] = config.json;
|
|
||||||
rootJson["configAutoWriteInMillis"] = config.getAutoWriteInMillis();
|
|
||||||
rootJson["configAutoWriteAtEpoch"] = config.getAutoWriteAtEpoch();
|
|
||||||
auto stream = request->beginResponseStream("application/json");
|
|
||||||
serializeJson(doc, *stream);
|
|
||||||
request->send(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper restore CppLocalVariableMayBeConst
|
|
||||||
|
|
||||||
void httpPlayerMove(AsyncWebServerRequest *request) {
|
|
||||||
if (!request->hasParam("index") || !request->hasParam("x") || !request->hasParam("y")) {
|
|
||||||
request->send(400, "text/plain", "required parameters: index, x, y");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto index = request->getParam("index")->value().toInt();
|
|
||||||
const auto x = request->getParam("x")->value().toInt();
|
|
||||||
const auto y = request->getParam("y")->value().toInt();
|
|
||||||
modeMove(index, x, y);
|
|
||||||
request->send(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpPlayerFire(AsyncWebServerRequest *request) {
|
|
||||||
if (!request->hasParam("index")) {
|
|
||||||
request->send(400, "text/plain", "required parameters: index");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto index = request->getParam("index")->value().toInt();
|
|
||||||
modeFire(index);
|
|
||||||
request->send(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpSet(AsyncWebServerRequest *request) {
|
|
||||||
if (!request->hasParam("n") || !request->hasParam("v")) {
|
|
||||||
request->send(400, "text/plain", "required parameters: n, v");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto name = request->getParam("n")->value();
|
|
||||||
const auto value = request->getParam("v")->value();
|
|
||||||
debug(R"(http: set("%s", "%s"))", name.c_str(), value.c_str());
|
|
||||||
if (name.equals("mode")) {
|
|
||||||
const auto mode = static_cast<ModeId>(value.toInt());
|
|
||||||
debug(" => mode = %d", mode);
|
|
||||||
setMode(mode);
|
|
||||||
} else if (name.equals("timerMillis") || name.equals("deadlineEpoch")) {
|
|
||||||
debug(" => config");
|
|
||||||
config.set(name, value.toInt());
|
|
||||||
modeLoadConfig();
|
|
||||||
} else if (name.equals("brightness")) {
|
|
||||||
const auto brightness = value.toInt();
|
|
||||||
setBrightness(brightness);
|
|
||||||
} else if (name.equals("speed")) {
|
|
||||||
const auto speed = value.toDouble();
|
|
||||||
setModeSpeed(getModeSpeed() * speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
request->send(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpConfigSave(AsyncWebServerRequest *request) {
|
|
||||||
config.write();
|
|
||||||
request->send(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
void patrixHttpSetup() {
|
|
||||||
server.on("/state", httpState);
|
|
||||||
|
|
||||||
server.on("/set", httpSet);
|
|
||||||
server.on("/config/save", httpConfigSave);
|
|
||||||
|
|
||||||
server.on("/player/move", httpPlayerMove);
|
|
||||||
server.on("/player/fire", httpPlayerFire);
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
#ifndef HTTP_H
|
|
||||||
#define HTTP_H
|
|
||||||
|
|
||||||
void patrixHttpSetup();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
37
src/main.cpp
37
src/main.cpp
@ -1,19 +1,32 @@
|
|||||||
#include "Node.h"
|
#include "serial.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
// ReSharper disable once CppUnusedIncludeDirective
|
#include "server.h"
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
// ReSharper disable once CppUnusedIncludeDirective
|
#include "mode.h"
|
||||||
#include <ArduinoOTA.h>
|
#include "display.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
// ReSharper disable once CppUnusedIncludeDirective
|
void setup() {
|
||||||
#include <PubSubClient.h>
|
delay(500);
|
||||||
|
|
||||||
// ReSharper disable once CppUnusedIncludeDirective
|
serial_setup();
|
||||||
#include <Adafruit_NeoPixel.h>
|
config_setup();
|
||||||
|
wifi_setup();
|
||||||
|
|
||||||
auto node = Node();
|
server_setup();
|
||||||
|
|
||||||
PatrixNode &patrixGetNode() {
|
display.setup();
|
||||||
return node;
|
display.setBrightness(config.brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
serial_loop();
|
||||||
|
wifi_loop();
|
||||||
|
mqtt_loop();
|
||||||
|
server_loop();
|
||||||
|
mode_loop();
|
||||||
|
config_loop();
|
||||||
|
display.loop();
|
||||||
}
|
}
|
||||||
|
|||||||
131
src/mode.cpp
131
src/mode.cpp
@ -1,104 +1,80 @@
|
|||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
|
#include "config.h"
|
||||||
#include "mode/Border/Border.h"
|
#include "mode/Border/Border.h"
|
||||||
#include "mode/Clock/Clock.h"
|
#include "mode/Clock/Clock.h"
|
||||||
#include "mode/CountDown/CountDown.h"
|
|
||||||
#include "mode/Energy/Energy.h"
|
|
||||||
#include "mode/GameOfLife/GameOfLife.h"
|
#include "mode/GameOfLife/GameOfLife.h"
|
||||||
#include "mode/Matrix/Matrix.h"
|
|
||||||
#include "mode/Pong/Pong.h"
|
#include "mode/Pong/Pong.h"
|
||||||
#include "mode/Power/Power.h"
|
|
||||||
#include "mode/SpaceInvaders/SpaceInvaders.h"
|
#include "mode/SpaceInvaders/SpaceInvaders.h"
|
||||||
|
#include "mode/CountDown/CountDown.h"
|
||||||
#include "mode/Starfield/Starfield.h"
|
#include "mode/Starfield/Starfield.h"
|
||||||
|
#include "mode/Matrix/Matrix.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "mode/Power/Power.h"
|
||||||
|
#include "mode/Energy/Energy.h"
|
||||||
#include "mode/Timer/Timer.h"
|
#include "mode/Timer/Timer.h"
|
||||||
|
|
||||||
Config config("/main.json");
|
ModeId currentModeId = NONE;
|
||||||
|
|
||||||
auto current = NONE;
|
microseconds_t lastMicros = 0;
|
||||||
|
|
||||||
auto wanted = NONE;
|
|
||||||
|
|
||||||
microseconds_t modeStepLastMicros = 0;
|
|
||||||
|
|
||||||
Mode *mode = nullptr;
|
Mode *mode = nullptr;
|
||||||
|
|
||||||
auto modeSpeed = 1.0;
|
void unloadOldMode();
|
||||||
|
|
||||||
void unloadOldMode(Display& display);
|
void loadNewMode();
|
||||||
|
|
||||||
void loadNewMode(Display& display);
|
void mode_step();
|
||||||
|
|
||||||
void modeStep();
|
void mode_loop() {
|
||||||
|
if (currentModeId != config.mode) {
|
||||||
void modeSetup() {
|
unloadOldMode();
|
||||||
config.read();
|
loadNewMode();
|
||||||
wanted = config.get("mode", GAME_OF_LIFE_RANDOM_COLOR);
|
}
|
||||||
modeSpeed = config.get("speed", 1.0);
|
mode_step();
|
||||||
}
|
}
|
||||||
|
|
||||||
void modeLoop(Display& display) {
|
void setMode(ModeId value) {
|
||||||
config.loop();
|
if (config.mode == value) {
|
||||||
if (current != wanted) {
|
return;
|
||||||
unloadOldMode(display);
|
|
||||||
loadNewMode(display);
|
|
||||||
}
|
}
|
||||||
modeStep();
|
config.mode = value;
|
||||||
|
config_set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void modeMqttMessage(const char *topic, const char *message) {
|
void modeMove(int index, int x, int y) {
|
||||||
if (mode != nullptr) {
|
|
||||||
mode->mqttMessage(topic, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void modeLoadConfig() {
|
|
||||||
if (mode != nullptr) {
|
|
||||||
mode->loadConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMode(const ModeId newMode) {
|
|
||||||
config.setIfNot("mode", newMode);
|
|
||||||
wanted = newMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModeId getModeId() {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getModeSpeed() {
|
|
||||||
return modeSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setModeSpeed(const double newSpeed) {
|
|
||||||
modeSpeed = min(max(0.01, newSpeed), 10000.0);
|
|
||||||
config.setIfNot("speed", modeSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void modeMove(const int index, const int x, const int y) {
|
|
||||||
if (mode != nullptr) {
|
if (mode != nullptr) {
|
||||||
mode->move(index, x, y);
|
mode->move(index, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void modeFire(const int index) {
|
void modeFire(int index) {
|
||||||
if (mode != nullptr) {
|
if (mode != nullptr) {
|
||||||
mode->fire(index);
|
mode->fire(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unloadOldMode(Display& display) {
|
void setSpeed(double speed) {
|
||||||
if (mode != nullptr) {
|
speed = min(max(0.01, speed), 10000.0);
|
||||||
info("Unloading mode: %s", mode->getName());
|
if (config.speed == speed) {
|
||||||
mode->stop();
|
return;
|
||||||
delete mode;
|
|
||||||
mode = nullptr;
|
|
||||||
display.clear();
|
|
||||||
}
|
}
|
||||||
|
config.speed = speed;
|
||||||
|
config_set_dirty();
|
||||||
|
Serial.printf("Setting speed to %6.2fx\n", config.speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadNewMode(Display& display) {
|
void unloadOldMode() {
|
||||||
switch (wanted) {
|
if (mode != nullptr) {
|
||||||
|
delete mode;
|
||||||
|
mode = nullptr;
|
||||||
|
}
|
||||||
|
display.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadNewMode() {
|
||||||
|
currentModeId = config.mode;
|
||||||
|
lastMicros = 0;
|
||||||
|
switch (currentModeId) {
|
||||||
case BORDER:
|
case BORDER:
|
||||||
mode = new Border(display);
|
mode = new Border(display);
|
||||||
break;
|
break;
|
||||||
@ -148,24 +124,19 @@ void loadNewMode(Display& display) {
|
|||||||
mode = new Timer2(display);
|
mode = new Timer2(display);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
info("No such mode: %d", wanted);
|
Serial.print("No mode loaded.\n");
|
||||||
|
display.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (mode != nullptr) {
|
Serial.printf("Mode: %s\n", mode == nullptr ? "None" : mode->getName());
|
||||||
info("Loaded mode: %s", mode->getName());
|
|
||||||
mode->loadConfig();
|
|
||||||
mode->start();
|
|
||||||
}
|
|
||||||
modeStepLastMicros = 0;
|
|
||||||
current = wanted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void modeStep() {
|
void mode_step() {
|
||||||
if (mode == nullptr) {
|
if (mode == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto currentMicros = static_cast<int64_t>(micros());
|
auto currentMicros = (int64_t) micros();
|
||||||
const auto deltaMicros = static_cast<microseconds_t>(min(1000000.0, max(1.0, static_cast<double>(currentMicros - modeStepLastMicros) * modeSpeed)));
|
microseconds_t microseconds = (microseconds_t) min(1000000.0, max(1.0, (double) (currentMicros - lastMicros) * config.speed));
|
||||||
modeStepLastMicros = currentMicros;
|
lastMicros = currentMicros;
|
||||||
mode->loop(deltaMicros);
|
mode->loop(microseconds);
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/mode.h
23
src/mode.h
@ -1,29 +1,16 @@
|
|||||||
#ifndef RGB_MATRIX_DISPLAY_MODE_H
|
#ifndef RGBMATRIXDISPLAY_MODE_H
|
||||||
#define RGB_MATRIX_DISPLAY_MODE_H
|
#define RGBMATRIXDISPLAY_MODE_H
|
||||||
|
|
||||||
#include <patrix/core/Config.h>
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
extern Config config;
|
void mode_loop();
|
||||||
|
|
||||||
void modeSetup();
|
void setMode(ModeId value);
|
||||||
|
|
||||||
void modeLoop(Display& display);
|
void setSpeed(double speed);
|
||||||
|
|
||||||
void setMode(ModeId newMode);
|
|
||||||
|
|
||||||
ModeId getModeId();
|
|
||||||
|
|
||||||
double getModeSpeed();
|
|
||||||
|
|
||||||
void setModeSpeed(double newSpeed);
|
|
||||||
|
|
||||||
void modeMove(int index, int x, int y);
|
void modeMove(int index, int x, int y);
|
||||||
|
|
||||||
void modeFire(int index);
|
void modeFire(int index);
|
||||||
|
|
||||||
void modeMqttMessage(const char *topic, const char *message);
|
|
||||||
|
|
||||||
void modeLoadConfig();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
class Border final : public Mode {
|
class Border : public Mode {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Border(Display& display) : Mode(display) {
|
explicit Border(Display &display) :
|
||||||
|
Mode(display) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,11 +19,15 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
display.clear();
|
for (int y = 0; y < height; y++) {
|
||||||
display.drawLine(0, 0, display.width, 0, 1, White);
|
for (int x = 0; x < width; x++) {
|
||||||
display.drawLine(0, 0, 0, display.height, 1, White);
|
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
|
||||||
display.drawLine(display.width - 1, display.height - 1, -display.width, 0, 1, White);
|
display.set(x, y, WHITE);
|
||||||
display.drawLine(display.width - 1, display.height - 1, 0, -display.height, 1, White);
|
} else {
|
||||||
|
display.set(x, y, BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,16 +3,15 @@
|
|||||||
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
class Clock final : public Mode {
|
class Clock : public Mode {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Clock(Display& display) : Mode(display) {
|
explicit Clock(Display &display) :
|
||||||
//
|
Mode(display) {
|
||||||
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
~Clock() override = default;
|
|
||||||
|
|
||||||
const char *getName() override {
|
const char *getName() override {
|
||||||
return "Clock";
|
return "Clock";
|
||||||
}
|
}
|
||||||
@ -27,7 +26,19 @@ protected:
|
|||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
display.clear();
|
display.clear();
|
||||||
display.printf(16, 1, CENTER, White, "%2d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec);
|
|
||||||
|
uint8_t x = 2;
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_hour / 10 : SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_hour % 10 : SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_min / 10 : SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_min % 10 : SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_sec / 10 : SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, realtimeOK ? now.tm_sec % 10 : SYMBOL_DASH, WHITE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,17 +3,15 @@
|
|||||||
|
|
||||||
#define MAX_FIREWORKS 6
|
#define MAX_FIREWORKS 6
|
||||||
|
|
||||||
#include "CountDownFirework.h"
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
#include "CountDownFirework.h"
|
||||||
|
|
||||||
class CountDown final : public Mode {
|
class CountDown : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
Firework fireworks[MAX_FIREWORKS];
|
Firework fireworks[MAX_FIREWORKS];
|
||||||
|
|
||||||
time_t deadlineEpoch = 0;
|
|
||||||
|
|
||||||
tm target{};
|
|
||||||
|
|
||||||
uint16_t days = 0;
|
uint16_t days = 0;
|
||||||
|
|
||||||
uint16_t hours = 0;
|
uint16_t hours = 0;
|
||||||
@ -30,7 +28,7 @@ class CountDown final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
CountDown(Display& display, const bool bars, const bool plus1DayForSleepingCount) : Mode(display), bars(bars), plus1DayForSleepingCount(plus1DayForSleepingCount) {
|
CountDown(Display& display, bool bars, bool plus1DayForSleepingCount) : Mode(display), bars(bars), plus1DayForSleepingCount(plus1DayForSleepingCount) {
|
||||||
for (auto& firework: fireworks) {
|
for (auto& firework: fireworks) {
|
||||||
firework.init(display);
|
firework.init(display);
|
||||||
}
|
}
|
||||||
@ -39,17 +37,14 @@ public:
|
|||||||
const char *getName() override {
|
const char *getName() override {
|
||||||
if (bars) {
|
if (bars) {
|
||||||
return "CountDown (Bars)";
|
return "CountDown (Bars)";
|
||||||
}
|
} else {
|
||||||
return "CountDown (Numbers)";
|
return "CountDown (Numbers)";
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadConfig() override {
|
|
||||||
deadlineEpoch = config.get("deadlineEpoch", 1767222000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(const microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
if (!realtimeOK) {
|
if (!realtimeOK) {
|
||||||
setMode(NO_TIME);
|
setMode(NO_TIME);
|
||||||
return;
|
return;
|
||||||
@ -63,15 +58,24 @@ protected:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localtime_r(&deadlineEpoch, &target);
|
// GRRRRRRR...
|
||||||
target.tm_year += 1900;
|
config.date.tm_year -= 1900;
|
||||||
target.tm_mon += 1;
|
config.date.tm_mon -= 1;
|
||||||
|
const time_t dateEpochSeconds = mktime(&config.date);
|
||||||
|
config.date.tm_year += 1900;
|
||||||
|
config.date.tm_mon += 1;
|
||||||
|
// ---
|
||||||
|
|
||||||
const auto diffSeconds = difftime(deadlineEpoch, nowEpochSeconds);
|
const double diffSeconds = difftime(dateEpochSeconds, nowEpochSeconds);
|
||||||
days = static_cast<int>(floor(diffSeconds / (24 * 60 * 60)));
|
days = (int) floor(diffSeconds / (24 * 60 * 60));
|
||||||
hours = static_cast<int>(floor(diffSeconds / (60 * 60))) % 24;
|
hours = (int) floor(diffSeconds / (60 * 60)) % 24;
|
||||||
minutes = static_cast<int>(floor(diffSeconds / 60)) % 60;
|
minutes = (int) floor(diffSeconds / 60) % 60;
|
||||||
seconds = static_cast<int>(diffSeconds) % 60;
|
seconds = (int) diffSeconds % 60;
|
||||||
|
// Serial.printf("now=%4d.%02d.%02d (%ld), conf=%4d.%02d.%02d (%ld), diff=%f, %dd %2d:%02d:%02d\n",
|
||||||
|
// now.tm_year, now.tm_mon, now.tm_mday, nowEpochSeconds,
|
||||||
|
// config.date.tm_year, config.date.tm_mon, config.date.tm_mday, dateEpochSeconds,
|
||||||
|
// diffSeconds,
|
||||||
|
// days, hours, minutes, seconds);
|
||||||
setMode(COUNTDOWN);
|
setMode(COUNTDOWN);
|
||||||
if (days == 0) {
|
if (days == 0) {
|
||||||
loopLastDay();
|
loopLastDay();
|
||||||
@ -81,7 +85,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loopLastDay() {
|
void loopLastDay() {
|
||||||
const auto levelTmp = static_cast<int>(round(32 * realtimeMilliseconds / 1000.0));
|
int levelTmp = (int) round(32 * realtimeMilliseconds / 1000.0);
|
||||||
if (level != levelTmp) {
|
if (level != levelTmp) {
|
||||||
level = levelTmp;
|
level = levelTmp;
|
||||||
markDirty();
|
markDirty();
|
||||||
@ -94,8 +98,8 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dateReached() const {
|
bool dateReached() {
|
||||||
return now.tm_year == target.tm_year && now.tm_mon == target.tm_mon && now.tm_mday == target.tm_mday;
|
return now.tm_year == config.date.tm_year && now.tm_mon == config.date.tm_mon && now.tm_mday == config.date.tm_mday;
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(Display& display) override {
|
void draw(Display& display) override {
|
||||||
@ -122,14 +126,14 @@ private:
|
|||||||
|
|
||||||
State _state = NO_TIME;
|
State _state = NO_TIME;
|
||||||
|
|
||||||
void setMode(const State state) {
|
void setMode(State state) {
|
||||||
if (_state != state) {
|
if (_state != state) {
|
||||||
_state = state;
|
_state = state;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawCountdown(Display& display) const {
|
void drawCountdown(Display& display) {
|
||||||
if (plus1DayForSleepingCount) {
|
if (plus1DayForSleepingCount) {
|
||||||
drawSleepingCount(display);
|
drawSleepingCount(display);
|
||||||
} else if (days <= 0 && bars) {
|
} else if (days <= 0 && bars) {
|
||||||
@ -140,31 +144,26 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawSleepingCount(Display& display) const {
|
void drawSleepingCount(Display& display) const {
|
||||||
const auto sleepCount = days + 1;
|
int sleepCount = days + 1;
|
||||||
display.printf(30, 1, RIGHT, White, "%d %s", sleepCount, sleepCount == 1 ? "TAG" : "TAGE");
|
int y = 1;
|
||||||
|
uint8_t x = display.print2(3 * (DISPLAY_CHAR_WIDTH + 1), y, sleepCount, WHITE);
|
||||||
|
x += 2;
|
||||||
|
x += display.print(x, y, SYMBOL_T, WHITE, true) + 1;
|
||||||
|
x += display.print(x, y, SYMBOL_A, WHITE, true) + 1;
|
||||||
|
x += display.print(x, y, SYMBOL_G, WHITE, true) + 1;
|
||||||
|
if (sleepCount != 1) {
|
||||||
|
display.print(x, y, SYMBOL_E, WHITE, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawCountdownBars(Display& display) const {
|
void drawCountdownBars(Display& display) const {
|
||||||
drawBar(display, 0, 24, 1, 24, hours, Red, Magenta, 0);
|
drawBar(display, 0, 24, 1, 24, hours, RED, MAGENTA, 0);
|
||||||
drawBar(display, 2, 30, 2, 60, minutes, Blue, Violet, 5);
|
drawBar(display, 2, 30, 2, 60, minutes, BLUE, VIOLET, 5);
|
||||||
drawBar(display, 5, 30, 2, 60, seconds, Green, Yellow, 5);
|
drawBar(display, 5, 30, 2, 60, seconds, GREEN, YELLOW, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawCountdownNumbers(Display& display) const {
|
static void drawBar(Display& display, uint8_t _y, uint8_t _w, uint8_t _h, uint8_t max, uint8_t value, const Color& color, const Color& tickColor, uint8_t ticks) {
|
||||||
if (days >= 10) {
|
auto totalOnCount = (uint8_t) round((double) value / max * _w * _h);
|
||||||
display.printf(30, 1, RIGHT, White, "%d TAGE", days);
|
|
||||||
drawSecondsBar(display, seconds);
|
|
||||||
} else if (days > 0) {
|
|
||||||
display.printf(30, 1, RIGHT, White, "%d %2d:%02d", days, hours, minutes);
|
|
||||||
drawSecondsBar(display, seconds);
|
|
||||||
} else {
|
|
||||||
display.printf(30, 1, RIGHT, White, "%d:%02d:%02d", hours, minutes, seconds);
|
|
||||||
drawSubSecondsBar(display);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void drawBar(Display& display, const uint8_t _y, const uint8_t _w, const uint8_t _h, const uint8_t max, const uint8_t value, const RGBA& color, const RGBA& tickColor, const uint8_t ticks) {
|
|
||||||
const auto totalOnCount = static_cast<uint8_t>(round(static_cast<double>(value) / max * _w * _h));
|
|
||||||
uint8_t doneOnCount = 0;
|
uint8_t doneOnCount = 0;
|
||||||
for (uint8_t y = 0; y < _h; y++) {
|
for (uint8_t y = 0; y < _h; y++) {
|
||||||
for (uint8_t x = 0; x < _w; x++) {
|
for (uint8_t x = 0; x < _w; x++) {
|
||||||
@ -172,47 +171,128 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doneOnCount++;
|
doneOnCount++;
|
||||||
auto c = color;
|
Color c = color;
|
||||||
if (ticks != 0) {
|
if (ticks != 0) {
|
||||||
if (doneOnCount % ticks == 0) {
|
if (doneOnCount % ticks == 0) {
|
||||||
c = tickColor;
|
c = tickColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
display.setPixel(x, _y + y, c);
|
display.set(x, _y + y, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void drawCountdownNumbers(Display& display) const {
|
||||||
|
uint8_t x = 0;
|
||||||
|
if (days > 0) {
|
||||||
|
drawDay(display, days, &x);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
} else {
|
||||||
|
x += 2;
|
||||||
|
}
|
||||||
|
drawHour(display, days, hours, &x);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
draw2Digit(display, minutes, &x);
|
||||||
|
if (days <= 0) {
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
draw2Digit(display, seconds, &x);
|
||||||
|
drawSubSecondsBar(display);
|
||||||
|
} else {
|
||||||
|
drawSecondsBar(display, seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void drawNoTime(Display& display) {
|
static void drawNoTime(Display& display) {
|
||||||
display.print(1, 1, LEFT, Red, "--:--:--");
|
uint8_t x = 2;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawSecondsBar(Display& display, const int seconds) {
|
static void drawDay(Display& display, int days, uint8_t *x) {
|
||||||
for (auto pos = 0; pos < 30; pos++) {
|
if (days >= 100) {
|
||||||
|
*x += display.print(*x, 1, days / 100, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (days >= 10) {
|
||||||
|
*x += display.print(*x, 1, days / 10 % 10, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*x += display.print(*x, 1, days % 10, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawHour(Display& display, int days, int hours, uint8_t *x) {
|
||||||
|
if (days > 0 || hours >= 10) {
|
||||||
|
*x += display.print(*x, 1, hours / 10, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
*x += display.print(*x, 1, hours % 10, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw2Digit(Display& display, int value, uint8_t *x) {
|
||||||
|
*x += display.print(*x, 1, value / 10, WHITE, true) + 1;
|
||||||
|
*x += display.print(*x, 1, value % 10, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSecondsBar(Display& display, int seconds) {
|
||||||
|
for (int pos = 0; pos < 30; pos++) {
|
||||||
if (pos <= seconds - 30) {
|
if (pos <= seconds - 30) {
|
||||||
display.setPixel(pos + 1, 7, Green);
|
display.set(pos + 1, 7, GREEN);
|
||||||
} else if (pos <= seconds) {
|
} else if (pos <= seconds) {
|
||||||
display.setPixel(pos + 1, 7, Red);
|
display.set(pos + 1, 7, RED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawSubSecondsBar(Display& display) const {
|
void drawSubSecondsBar(Display& display) const {
|
||||||
for (auto pos = 0; pos < 32; pos++) {
|
for (int pos = 0; pos < 32; pos++) {
|
||||||
if (pos < 32 - level) {
|
if (pos < 32 - level) {
|
||||||
display.setPixel(pos, 7, Green);
|
display.set(pos, 7, GREEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawYear(Display& display, const int year) const {
|
// ReSharper disable CppDFAUnusedValue
|
||||||
|
|
||||||
|
void drawYear(Display& display, int year) const {
|
||||||
if (plus1DayForSleepingCount) {
|
if (plus1DayForSleepingCount) {
|
||||||
display.printf(1, 1, LEFT, White, "EMIL 5");
|
uint8_t x = 1;
|
||||||
|
x += display.printM(x, 1, WHITE);
|
||||||
|
x += 1;
|
||||||
|
x += display.print(x, 1,SYMBOL_A, WHITE, true);
|
||||||
|
x += 1;
|
||||||
|
x += display.printM(x, 1, WHITE);
|
||||||
|
x += 1;
|
||||||
|
x += display.print(x, 1,SYMBOL_A, WHITE, true);
|
||||||
|
x += 4;
|
||||||
|
x += display.print(x, 1, 3, WHITE, true);
|
||||||
|
x += 1;
|
||||||
|
x += display.print(x, 1, 2, WHITE, true);
|
||||||
|
display.printHeart(15, 4);
|
||||||
} else {
|
} else {
|
||||||
display.printf(1, 1, LEFT, White, "%5d", year);
|
uint8_t x = 8;
|
||||||
|
x += display.print(x, 1, year / 1000 % 10, WHITE, true) + 1;
|
||||||
|
x += display.print(x, 1, year / 100 % 10, WHITE, true) + 1;
|
||||||
|
x += display.print(x, 1, year / 10 % 10, WHITE, true) + 1;
|
||||||
|
x += display.print(x, 1, year / 1 % 10, WHITE, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper restore CppDFAUnusedValue
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
#define COUNT_DOWN_FIREWORK_H
|
#define COUNT_DOWN_FIREWORK_H
|
||||||
|
|
||||||
#include "BASICS.h"
|
#include "BASICS.h"
|
||||||
#include "Vector.h"
|
#include "display/Vector.h"
|
||||||
|
#include "display/Display.h"
|
||||||
|
|
||||||
#define DARKER_FACTOR 0.75
|
#define DARKER_FACTOR 0.75
|
||||||
#define SLOWER_DIVISOR 1
|
#define SLOWER_DIVISOR 1
|
||||||
@ -17,7 +18,7 @@ class Firework {
|
|||||||
|
|
||||||
uint8_t height = 0;
|
uint8_t height = 0;
|
||||||
|
|
||||||
RGBA color = Magenta;
|
Color color = MAGENTA;
|
||||||
|
|
||||||
State state = RISE;
|
State state = RISE;
|
||||||
|
|
||||||
@ -35,19 +36,19 @@ class Firework {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void init(const Display& display) {
|
void init(Display &display) {
|
||||||
width = display.width;
|
width = display.width;
|
||||||
height = display.height;
|
height = display.height;
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
position = Vector(random(width), height);
|
position = Vector((double) random(width), height);
|
||||||
color = RGBA::rnd();
|
color = randomColor();
|
||||||
state = INITIAL;
|
state = INITIAL;
|
||||||
|
|
||||||
destinationHeight = height / 2.0 + static_cast<double>(random(5)) - 2;
|
destinationHeight = height / 2.0 + (double) random(5) - 2;
|
||||||
explosionRadius = static_cast<double>(random(3)) + 1;
|
explosionRadius = (double) random(3) + 1;
|
||||||
sparkleMax = 100;
|
sparkleMax = 100;
|
||||||
|
|
||||||
explosion = 0.0;
|
explosion = 0.0;
|
||||||
@ -84,12 +85,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(Display& display) const {
|
void draw(Display &display) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case INITIAL:
|
case INITIAL:
|
||||||
break;
|
break;
|
||||||
case RISE:
|
case RISE:
|
||||||
display.setPixel(position.x, position.y, Yellow.factor(DARKER_FACTOR));
|
display.set(position, factor(YELLOW, DARKER_FACTOR));
|
||||||
break;
|
break;
|
||||||
case EXPLODE:
|
case EXPLODE:
|
||||||
drawParticle(display, +0.0, +1.0);
|
drawParticle(display, +0.0, +1.0);
|
||||||
@ -130,9 +131,16 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void drawParticle(Display& display, const double x, const double y) const {
|
static Color factor(Color color, double factor) {
|
||||||
const auto p = position.plus(x * explosion, y * explosion);
|
return {
|
||||||
display.setPixel(p.x, p.y, color.factor(DARKER_FACTOR));
|
(uint8_t) round(color.r * factor),
|
||||||
|
(uint8_t) round(color.g * factor),
|
||||||
|
(uint8_t) round(color.b * factor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawParticle(Display &display, double x, double y) {
|
||||||
|
display.set(position.plus(x * explosion, y * explosion), factor(color, DARKER_FACTOR));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,37 +1,24 @@
|
|||||||
#ifndef MODE_ENERGY_H
|
#ifndef MODE_ENERGY_H
|
||||||
#define MODE_ENERGY_H
|
#define MODE_ENERGY_H
|
||||||
|
|
||||||
#include <patrix/core/mqtt.h>
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
#define PHOTOVOLTAIC_ENERGY_KWH "openDTU/pv/ac/yieldtotal"
|
#define POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE 287.995
|
||||||
#define GRID_IMPORT_WH "electricity/grid/energy/import/wh"
|
|
||||||
#define GRID_EXPORT_WH "electricity/grid/energy/export/wh"
|
|
||||||
|
|
||||||
#define POWER_PHOTOVOLTAIC_photovoltaicEnergyKWh_BEFORE_METER_CHANGE 287.995
|
|
||||||
#define PV_COST_TOTAL_EURO 576.52
|
#define PV_COST_TOTAL_EURO 576.52
|
||||||
#define GRID_KWH_EURO 0.33
|
#define GRID_KWH_EURO 0.33
|
||||||
#define PV_COST_AMORTISATION_KWH ( PV_COST_TOTAL_EURO / GRID_KWH_EURO )
|
#define PV_COST_AMORTISATION_KWH ( PV_COST_TOTAL_EURO / GRID_KWH_EURO )
|
||||||
|
|
||||||
class Energy final : public Mode {
|
class Energy : public Mode {
|
||||||
|
|
||||||
double photovoltaicEnergyKWh = NAN;
|
private:
|
||||||
|
|
||||||
unsigned long photovoltaicEnergyKWhLast = 0;
|
|
||||||
|
|
||||||
double gridImportKWh = NAN;
|
|
||||||
|
|
||||||
unsigned long gridImportKWhLast = 0;
|
|
||||||
|
|
||||||
double gridExportKWh = NAN;
|
|
||||||
|
|
||||||
unsigned long gridExportKWhLast = 0;
|
|
||||||
|
|
||||||
int page = 0;
|
int page = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Energy(Display& display) : Mode(display) {
|
explicit Energy(Display &display) :
|
||||||
|
Mode(display) {
|
||||||
timer(0, 7000);
|
timer(0, 7000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,18 +26,6 @@ public:
|
|||||||
return "Energy";
|
return "Energy";
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() override {
|
|
||||||
mqttSubscribe(PHOTOVOLTAIC_ENERGY_KWH);
|
|
||||||
mqttSubscribe(GRID_IMPORT_WH);
|
|
||||||
mqttSubscribe(GRID_EXPORT_WH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() override {
|
|
||||||
mqttUnsubscribe(PHOTOVOLTAIC_ENERGY_KWH);
|
|
||||||
mqttUnsubscribe(GRID_IMPORT_WH);
|
|
||||||
mqttUnsubscribe(GRID_EXPORT_WH);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void tick(uint8_t index, microseconds_t microseconds) override {
|
void tick(uint8_t index, microseconds_t microseconds) override {
|
||||||
@ -64,23 +39,30 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
const auto photovoltaicEnergyKWhAfterMeterChange = photovoltaicEnergyKWh - POWER_PHOTOVOLTAIC_photovoltaicEnergyKWh_BEFORE_METER_CHANGE;
|
const double produced = getPhotovoltaicEnergyKWh();
|
||||||
const auto selfAfterMeterChange = photovoltaicEnergyKWhAfterMeterChange - gridExportKWh;
|
const double imported = getGridImportKWh();
|
||||||
const auto selfRatio = selfAfterMeterChange / photovoltaicEnergyKWhAfterMeterChange;
|
const double exported = getGridExportKWh();
|
||||||
const auto selfConsumedKWh = selfRatio * photovoltaicEnergyKWh;
|
const double producedAfterMeterChange = produced - POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE;
|
||||||
const auto costSaved = selfConsumedKWh * GRID_KWH_EURO;
|
const double selfAfterMeterChange = producedAfterMeterChange - exported;
|
||||||
const auto amortisationPercent = selfConsumedKWh / PV_COST_AMORTISATION_KWH * 100;
|
const double selfRatio = selfAfterMeterChange / producedAfterMeterChange;
|
||||||
|
const double selfConsumedKWh = selfRatio * produced;
|
||||||
|
const double costSaved = selfConsumedKWh * GRID_KWH_EURO;
|
||||||
|
const double amortisationPercent = selfConsumedKWh / PV_COST_AMORTISATION_KWH * 100;
|
||||||
|
|
||||||
|
const uint8_t l = (DISPLAY_CHAR_WIDTH + 1) * 4 - 1;
|
||||||
|
const uint8_t r = width;
|
||||||
|
|
||||||
display.clear();
|
display.clear();
|
||||||
if (page == 0) {
|
if (page == 0) {
|
||||||
display.printf(1, 1, LEFT, Green, "%3.0f€", costSaved);
|
display.print2(l, 0, costSaved, GREEN);
|
||||||
display.printf(30, 1, RIGHT, White, "%.0f%%", amortisationPercent);
|
uint8_t x = display.print2(r - DISPLAY_CHAR_WIDTH - 1, 0, amortisationPercent, WHITE);
|
||||||
|
display.print(x, 0, SYMBOL_PERCENT, WHITE, true);
|
||||||
} else if (page == 1) {
|
} else if (page == 1) {
|
||||||
display.printf(1, 1, LEFT, Blue, "%3.0f", photovoltaicEnergyKWh);
|
display.print2(l, 0, getPhotovoltaicEnergyKWh(), BLUE);
|
||||||
display.printf(30, 1, RIGHT, Green, "%.0f", selfConsumedKWh);
|
display.print2(r, 0, selfConsumedKWh, GREEN);
|
||||||
} else {
|
} else {
|
||||||
display.printf(1, 1, LEFT, Orange, "%4.0f", gridImportKWh);
|
display.print2(l, 0, imported, ORANGE);
|
||||||
display.printf(30, 1, RIGHT, Magenta, "%.0f", gridExportKWh);
|
display.print2(r, 0, exported, MAGENTA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,9 +11,9 @@ public:
|
|||||||
|
|
||||||
double fade = 0.0;
|
double fade = 0.0;
|
||||||
|
|
||||||
RGBA color = Black;
|
Color color = BLACK;
|
||||||
|
|
||||||
void animate(const microseconds_t microseconds) {
|
void animate(microseconds_t microseconds) {
|
||||||
// TODO fading does not work as expected
|
// TODO fading does not work as expected
|
||||||
if (alive) {
|
if (alive) {
|
||||||
fade = doStep(fade, 0.0, 255.0, 200, +microseconds);
|
fade = doStep(fade, 0.0, 255.0, 200, +microseconds);
|
||||||
@ -26,33 +26,40 @@ public:
|
|||||||
if (alive) {
|
if (alive) {
|
||||||
if (fade < 128) {
|
if (fade < 128) {
|
||||||
return 0;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (uint8_t) ((fade - 128) * 2.0 + 1);
|
||||||
}
|
}
|
||||||
return static_cast<uint8_t>((fade - 128) * 2.0 + 1);
|
} else {
|
||||||
}
|
|
||||||
if (fade < 128) {
|
if (fade < 128) {
|
||||||
return static_cast<uint8_t>(fade * 2.0 + 1);
|
return (uint8_t) (fade * 2.0 + 1);
|
||||||
}
|
} else {
|
||||||
return 255;
|
return 255;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t getG() const {
|
uint8_t getG() const {
|
||||||
if (alive) {
|
if (alive) {
|
||||||
if (fade < 128) {
|
if (fade < 128) {
|
||||||
return static_cast<uint8_t>(fade * 2.0 + 1);
|
return (uint8_t) (fade * 2.0 + 1);
|
||||||
}
|
} else {
|
||||||
return 255;
|
return 255;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (fade < 128) {
|
if (fade < 128) {
|
||||||
return 0;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (uint8_t) ((fade - 128) * 2.0 + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return static_cast<uint8_t>((fade - 128) * 2.0 + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getB() const {
|
uint8_t getB() const {
|
||||||
if (fade < 128) {
|
if (fade < 128) {
|
||||||
return 0;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (uint8_t) ((fade - 128) * 2.0 + 1);
|
||||||
}
|
}
|
||||||
return static_cast<uint8_t>((fade - 128) * 2.0 + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
#ifndef MODE_GAME_OF_LIFE_H
|
#ifndef MODE_GAME_OF_LIFE_H
|
||||||
#define MODE_GAME_OF_LIFE_H
|
#define MODE_GAME_OF_LIFE_H
|
||||||
|
|
||||||
#include "Cell.h"
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
#include "Cell.h"
|
||||||
|
|
||||||
enum ColorMode {
|
enum ColorMode {
|
||||||
BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR
|
BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameOfLife final : public Mode {
|
class GameOfLife : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
ColorMode colorMode;
|
ColorMode colorMode;
|
||||||
|
|
||||||
@ -32,16 +34,17 @@ class GameOfLife final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit GameOfLife(Display& display, const ColorMode colorMode) : Mode(display),
|
explicit GameOfLife(Display &display, ColorMode colorMode) :
|
||||||
|
Mode(display),
|
||||||
colorMode(colorMode),
|
colorMode(colorMode),
|
||||||
cellsSize(display.pixelCount * sizeof(Cell)) {
|
cellsSize(display.pixelCount * sizeof(Cell)) {
|
||||||
cells = static_cast<Cell *>(malloc(cellsSize));
|
cells = (Cell *) malloc(cellsSize);
|
||||||
cellsEnd = cells + display.pixelCount;
|
cellsEnd = cells + display.pixelCount;
|
||||||
for (auto cell = cells; cell < cells + display.pixelCount; cell++) {
|
for (Cell *cell = cells; cell < cells + display.pixelCount; cell++) {
|
||||||
kill(cell);
|
kill(cell);
|
||||||
}
|
}
|
||||||
next = static_cast<Cell *>(malloc(cellsSize));
|
next = (Cell *) malloc(cellsSize);
|
||||||
for (auto cell = next; cell < next + display.pixelCount; cell++) {
|
for (Cell *cell = next; cell < next + display.pixelCount; cell++) {
|
||||||
kill(cell);
|
kill(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +76,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(const microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
runtime += microseconds;
|
runtime += microseconds;
|
||||||
if (runtime >= 500000) {
|
if (runtime >= 500000) {
|
||||||
runtime = 0;
|
runtime = 0;
|
||||||
@ -89,7 +92,7 @@ protected:
|
|||||||
nextGeneration();
|
nextGeneration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto cell = cells; cell < cellsEnd; cell++) {
|
for (Cell *cell = cells; cell < cellsEnd; cell++) {
|
||||||
cell->animate(microseconds);
|
cell->animate(microseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,25 +101,24 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
auto cell = cells;
|
Cell *cell = cells;
|
||||||
display.clear();
|
for (int y = 0; y < height; y++) {
|
||||||
for (auto y = 0; y < height; y++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (auto x = 0; x < width; x++) {
|
|
||||||
uint8_t brightness;
|
uint8_t brightness;
|
||||||
switch (colorMode) {
|
switch (colorMode) {
|
||||||
case BLACK_WHITE:
|
case BLACK_WHITE:
|
||||||
brightness = cell->alive ? 255 : 0;
|
brightness = cell->alive ? 255 : 0;
|
||||||
display.setPixel(x, y, RGBA::gray(brightness));
|
display.set(x, y, gray(brightness));
|
||||||
break;
|
break;
|
||||||
case GRAYSCALE:
|
case GRAYSCALE:
|
||||||
brightness = static_cast<uint8_t>(cell->fade);
|
brightness = (uint8_t) cell->fade;
|
||||||
display.setPixel(x, y, RGBA::gray(brightness));
|
display.set(x, y, gray(brightness));
|
||||||
break;
|
break;
|
||||||
case COLOR_FADE:
|
case COLOR_FADE:
|
||||||
display.setPixel(x, y, {cell->getR(), cell->getG(), cell->getB()});
|
display.set(x, y, {cell->getR(), cell->getG(), cell->getB()});
|
||||||
break;
|
break;
|
||||||
case RANDOM_COLOR:
|
case RANDOM_COLOR:
|
||||||
display.setPixel(x, y, cell->alive ? cell->color : Black);
|
display.set(x, y, cell->alive ? cell->color : BLACK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cell++;
|
cell++;
|
||||||
@ -128,7 +130,7 @@ private:
|
|||||||
|
|
||||||
void randomFill() {
|
void randomFill() {
|
||||||
isSteadyCount = 0;
|
isSteadyCount = 0;
|
||||||
for (auto cell = cells; cell < cellsEnd; cell++) {
|
for (Cell *cell = cells; cell < cellsEnd; cell++) {
|
||||||
if (random(4) == 0) {
|
if (random(4) == 0) {
|
||||||
if (!cell->alive) {
|
if (!cell->alive) {
|
||||||
spawn(cell);
|
spawn(cell);
|
||||||
@ -142,7 +144,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void spawn(Cell *cell) {
|
void spawn(Cell *cell) {
|
||||||
cell->color = RGBA::rnd(255);
|
cell->color = randomColor();
|
||||||
cell->alive = true;
|
cell->alive = true;
|
||||||
aliveCount++;
|
aliveCount++;
|
||||||
}
|
}
|
||||||
@ -154,11 +156,11 @@ private:
|
|||||||
|
|
||||||
void nextGeneration() {
|
void nextGeneration() {
|
||||||
memcpy(next, cells, cellsSize);
|
memcpy(next, cells, cellsSize);
|
||||||
auto src = cells;
|
Cell *src = cells;
|
||||||
auto dst = next;
|
Cell *dst = next;
|
||||||
for (auto y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (auto x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
const auto around = countAround(x, y);
|
uint8_t around = countAround(x, y);
|
||||||
if (src->alive) {
|
if (src->alive) {
|
||||||
if (around <= 2 || 6 <= around) {
|
if (around <= 2 || 6 <= around) {
|
||||||
kill(dst);
|
kill(dst);
|
||||||
@ -173,11 +175,11 @@ private:
|
|||||||
memcpy(cells, next, cellsSize);
|
memcpy(cells, next, cellsSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print() const {
|
void print() {
|
||||||
auto cell = cells;
|
Cell *cell = cells;
|
||||||
for (auto y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
Serial.print("|");
|
Serial.print("|");
|
||||||
for (auto x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
Serial.print(cell->alive ? "x|" : " |");
|
Serial.print(cell->alive ? "x|" : " |");
|
||||||
cell++;
|
cell++;
|
||||||
}
|
}
|
||||||
@ -186,20 +188,20 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t countAround(const int x, const int y) const {
|
uint8_t countAround(int x, int y) {
|
||||||
return countIfAlive(x - 1, y - 1) + countIfAlive(x + 0, y - 1) + countIfAlive(x + 1, y - 1) +
|
return countIfAlive(x - 1, y - 1) + countIfAlive(x + 0, y - 1) + countIfAlive(x + 1, y - 1) +
|
||||||
countIfAlive(x - 1, y + 0) + /* */ countIfAlive(x + 1, y + 0) +
|
countIfAlive(x - 1, y + 0) + /* */ countIfAlive(x + 1, y + 0) +
|
||||||
countIfAlive(x - 1, y + 1) + countIfAlive(x + 0, y + 1) + countIfAlive(x + 1, y + 1);
|
countIfAlive(x - 1, y + 1) + countIfAlive(x + 0, y + 1) + countIfAlive(x + 1, y + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t countIfAlive(const int x, const int y) const {
|
uint8_t countIfAlive(int x, int y) {
|
||||||
if (x < 0 || y < 0 || x >= width || y >= height) {
|
if (x < 0 || y < 0 || x >= width || y >= height) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return get(x, y)->alive ? 1 : 0;
|
return get(x, y)->alive ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cell *get(const int x, const int y) const {
|
Cell *get(int x, int y) {
|
||||||
return cells + width * y + x;
|
return cells + width * y + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
class Matrix final : public Mode {
|
class Matrix : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
struct Glyph {
|
struct Glyph {
|
||||||
double x = 0;
|
double x;
|
||||||
double y = 0;
|
double y;
|
||||||
double velocity = 0;
|
double velocity = 0;
|
||||||
uint8_t length = 0;
|
uint8_t length = 0;
|
||||||
};
|
};
|
||||||
@ -16,7 +18,8 @@ class Matrix final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Matrix(Display& display) : Mode(display) {
|
explicit Matrix(Display &display) :
|
||||||
|
Mode(display) {
|
||||||
for (auto &glyph: glyphs) {
|
for (auto &glyph: glyphs) {
|
||||||
resetGlyph(glyph);
|
resetGlyph(glyph);
|
||||||
}
|
}
|
||||||
@ -25,7 +28,7 @@ public:
|
|||||||
void resetGlyph(Glyph &glyph) const {
|
void resetGlyph(Glyph &glyph) const {
|
||||||
glyph.x = random(width);
|
glyph.x = random(width);
|
||||||
glyph.y = 0;
|
glyph.y = 0;
|
||||||
glyph.velocity = random(20) + 5;
|
glyph.velocity = (random(20) + 5);
|
||||||
glyph.length = random(8) + 2;
|
glyph.length = random(8) + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,9 +38,9 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(const microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
for (auto &glyph: glyphs) {
|
for (auto &glyph: glyphs) {
|
||||||
glyph.y += glyph.velocity * static_cast<double>(microseconds) / 1000000.0;
|
glyph.y += glyph.velocity * (double) microseconds / 1000000.0;
|
||||||
if (glyph.y - glyph.length >= height) {
|
if (glyph.y - glyph.length >= height) {
|
||||||
resetGlyph(glyph);
|
resetGlyph(glyph);
|
||||||
}
|
}
|
||||||
@ -47,12 +50,12 @@ protected:
|
|||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
display.clear();
|
display.clear();
|
||||||
for (const auto& glyph: glyphs) {
|
for (auto &glyph: glyphs) {
|
||||||
for (auto i = 0; i < glyph.length; ++i) {
|
for (int i = 0; i < glyph.length; ++i) {
|
||||||
display.setPixel(glyph.x, glyph.y - i, {64, 128, 64});
|
display.set((int) round(glyph.x), (int) round(glyph.y - i), {64, 128, 64});
|
||||||
}
|
}
|
||||||
if ((static_cast<int>(round(glyph.y)) + glyph.length) % 2 == 0) {
|
if (((int) round(glyph.y) + glyph.length) % 2 == 0) {
|
||||||
display.setPixel(glyph.x, glyph.y, {0, 255, 0});
|
display.set((int) round(glyph.x), (int) round(glyph.y), {0, 255, 0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
#ifndef MODE_H
|
#ifndef MODE_H
|
||||||
#define MODE_H
|
#define MODE_H
|
||||||
|
|
||||||
#include <BASICS.h>
|
#include "BASICS.h"
|
||||||
#include <patrix/core/log.h>
|
#include "display/Display.h"
|
||||||
|
|
||||||
#define FAKE_DAYS 0
|
#define FAKE_DAYS 0
|
||||||
#define FAKE_HOURS 0
|
#define FAKE_HOURS 2
|
||||||
#define FAKE_MINUTES 0
|
#define FAKE_MINUTES 0
|
||||||
#define FAKE_SECONDS 0
|
#define FAKE_SECONDS 0
|
||||||
|
|
||||||
@ -65,13 +65,13 @@ protected:
|
|||||||
|
|
||||||
time_t nowEpochSeconds = 0;
|
time_t nowEpochSeconds = 0;
|
||||||
|
|
||||||
virtual void tick(uint8_t index, microseconds_t microseconds) {}
|
virtual void tick(uint8_t index, microseconds_t microseconds) {};
|
||||||
|
|
||||||
virtual void step(const microseconds_t microseconds) {}
|
virtual void step(microseconds_t microseconds) {};
|
||||||
|
|
||||||
virtual void draw(Display& display) {}
|
virtual void draw(Display &display) {};
|
||||||
|
|
||||||
void timer(const uint8_t index, const milliseconds_t milliseconds) {
|
void timer(uint8_t index, milliseconds_t milliseconds) {
|
||||||
if (index >= countof(timers)) {
|
if (index >= countof(timers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -85,29 +85,26 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Mode(Display& display) : _display(display),
|
explicit Mode(Display &display) :
|
||||||
|
_display(display),
|
||||||
width(display.width),
|
width(display.width),
|
||||||
height(display.height) {
|
height(display.height) {
|
||||||
//
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~Mode() = default;
|
virtual ~Mode() = default;
|
||||||
|
|
||||||
virtual const char *getName() = 0;
|
virtual const char *getName() = 0;
|
||||||
|
|
||||||
virtual void start() {}
|
virtual void move(int index, int x, int y) {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
virtual void stop() {}
|
virtual void fire(int index) {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
virtual void move(int index, int x, int y) {}
|
void loop(microseconds_t microseconds) {
|
||||||
|
|
||||||
virtual void fire(int index) {}
|
|
||||||
|
|
||||||
virtual void mqttMessage(const String& topic, const String& message) {}
|
|
||||||
|
|
||||||
virtual void loadConfig() {}
|
|
||||||
|
|
||||||
void loop(const microseconds_t microseconds) {
|
|
||||||
handleRealtime();
|
handleRealtime();
|
||||||
handleTimers(microseconds);
|
handleTimers(microseconds);
|
||||||
step(microseconds);
|
step(microseconds);
|
||||||
@ -144,14 +141,14 @@ private:
|
|||||||
|
|
||||||
void realtimeMillisecondsUpdate() {
|
void realtimeMillisecondsUpdate() {
|
||||||
if (lastSecond < 0 || lastSecond != now.tm_sec) {
|
if (lastSecond < 0 || lastSecond != now.tm_sec) {
|
||||||
lastSecond = static_cast<int8_t>(now.tm_sec);
|
lastSecond = (int8_t) now.tm_sec;
|
||||||
lastSecondChange_Milliseconds = millis();
|
lastSecondChange_Milliseconds = millis();
|
||||||
}
|
}
|
||||||
realtimeMilliseconds = millis() - lastSecondChange_Milliseconds;
|
realtimeMilliseconds = millis() - lastSecondChange_Milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTimers(const microseconds_t microseconds) {
|
void handleTimers(microseconds_t microseconds) {
|
||||||
for (auto timer = timers; timer < timers + countof(timers); timer++) {
|
for (Timer *timer = timers; timer < timers + countof(timers); timer++) {
|
||||||
if (timer->interval > 0) {
|
if (timer->interval > 0) {
|
||||||
if (microseconds >= timer->rest) {
|
if (microseconds >= timer->rest) {
|
||||||
timer->rest = timer->interval;
|
timer->rest = timer->interval;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public:
|
|||||||
|
|
||||||
bool random = true;
|
bool random = true;
|
||||||
|
|
||||||
void randomMove(const uint8_t height) {
|
void randomMove(uint8_t height) {
|
||||||
if (moveUp) {
|
if (moveUp) {
|
||||||
y--;
|
y--;
|
||||||
if (y <= 0 || randomBool(20)) {
|
if (y <= 0 || randomBool(20)) {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
#ifndef MODE_PONG_H
|
#ifndef MODE_PONG_H
|
||||||
#define MODE_PONG_H
|
#define MODE_PONG_H
|
||||||
|
|
||||||
#include "Player.h"
|
|
||||||
#include "Vector.h"
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "display/Vector.h"
|
||||||
|
|
||||||
class Pong final : public Mode {
|
class Pong : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
SCORE, PLAY, OVER
|
SCORE, PLAY, OVER
|
||||||
@ -25,7 +27,8 @@ class Pong final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Pong(Display& display) : Mode(display),
|
explicit Pong(Display &display) :
|
||||||
|
Mode(display),
|
||||||
ball(width / 2.0, height / 2.0),
|
ball(width / 2.0, height / 2.0),
|
||||||
velocity(Vector::polar(random(360), exp10(1))) {
|
velocity(Vector::polar(random(360), exp10(1))) {
|
||||||
timer(0, 100);
|
timer(0, 100);
|
||||||
@ -37,7 +40,7 @@ public:
|
|||||||
return "Pong";
|
return "Pong";
|
||||||
}
|
}
|
||||||
|
|
||||||
void move(const int index, int x, const int y) override {
|
void move(int index, int x, int y) override {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
player0.random = false;
|
player0.random = false;
|
||||||
player0.y = min(height - player0.size, max(0, player0.y + y));
|
player0.y = min(height - player0.size, max(0, player0.y + y));
|
||||||
@ -47,7 +50,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fire(const int index) override {
|
void fire(int index) override {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
player0.random = false;
|
player0.random = false;
|
||||||
} else if (index == 1) {
|
} else if (index == 1) {
|
||||||
@ -57,7 +60,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void tick(uint8_t index, const microseconds_t microseconds) override {
|
void tick(uint8_t index, microseconds_t microseconds) override {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SCORE:
|
case SCORE:
|
||||||
timeoutMicroseconds -= microseconds;
|
timeoutMicroseconds -= microseconds;
|
||||||
@ -94,25 +97,25 @@ protected:
|
|||||||
display.clear();
|
display.clear();
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SCORE:
|
case SCORE:
|
||||||
display.printf(1, 1, LEFT, Green, "%d", player0.score);
|
display.print(1, 1, player0.score, GREEN, true);
|
||||||
display.printf(30, 1, RIGHT, Red, "%d", player1.score);
|
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED, true);
|
||||||
break;
|
break;
|
||||||
case PLAY:
|
case PLAY:
|
||||||
for (auto i = 0; i < player0.size; ++i) {
|
for (int i = 0; i < player0.size; ++i) {
|
||||||
display.setPixel(1, static_cast<uint8_t>(round(player0.y)) + i, Green);
|
display.set(1, (uint8_t) round(player0.y) + i, GREEN);
|
||||||
}
|
}
|
||||||
for (auto i = 0; i < player1.size; ++i) {
|
for (int i = 0; i < player1.size; ++i) {
|
||||||
display.setPixel(width - 2, static_cast<uint8_t>(round(player1.y)) + i, Red);
|
display.set(width - 2, (uint8_t) round(player1.y) + i, RED);
|
||||||
}
|
}
|
||||||
display.setPixel(static_cast<uint8_t>(round(ball.x)), static_cast<uint8_t>(round(ball.y)), White);
|
display.set((uint8_t) round(ball.x), (uint8_t) round(ball.y), WHITE);
|
||||||
break;
|
break;
|
||||||
case OVER:
|
case OVER:
|
||||||
if (player0.score > player1.score) {
|
if (player0.score > player1.score) {
|
||||||
display.printf(1, 1, LEFT, Green, "W", player0.score);
|
display.print(1, 1, 11, GREEN, true);
|
||||||
display.printf(30, 1, RIGHT, Red, "L", player1.score);
|
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED, true);
|
||||||
} else if (player0.score < player1.score) {
|
} else if (player0.score < player1.score) {
|
||||||
display.printf(1, 1, LEFT, Red, "L", player0.score);
|
display.print(1, 1, 12, RED, true);
|
||||||
display.printf(30, 1, RIGHT, Green, "W", player1.score);
|
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -146,11 +149,11 @@ private:
|
|||||||
void checkScoring() {
|
void checkScoring() {
|
||||||
if (ball.x < 0) {
|
if (ball.x < 0) {
|
||||||
player1.score++;
|
player1.score++;
|
||||||
Serial.printf("Player 1 scored: %d\n", player1.score);
|
Serial.println("Player 1 scored");
|
||||||
spawnBall(+1);
|
spawnBall(+1);
|
||||||
} else if (ball.x >= width) {
|
} else if (ball.x >= width) {
|
||||||
player0.score++;
|
player0.score++;
|
||||||
Serial.printf("Player 0 scored: %d\n", player0.score);
|
Serial.println("Player 0 scored");
|
||||||
spawnBall(-1);
|
spawnBall(-1);
|
||||||
}
|
}
|
||||||
if (player0.score >= 10 || player1.score >= 10) {
|
if (player0.score >= 10 || player1.score >= 10) {
|
||||||
@ -160,24 +163,26 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void paddleBounce() {
|
void paddleBounce() {
|
||||||
const auto paddleHitPosition0 = ball.y - player0.y;
|
double paddleHitPosition0 = ball.y - player0.y;
|
||||||
if (ball.x >= 1 && ball.x < 2 && paddleHitPosition0 >= 0 && paddleHitPosition0 < player0.size) {
|
if (ball.x >= 1 && ball.x < 2 && paddleHitPosition0 >= 0 && paddleHitPosition0 < player0.size) {
|
||||||
|
Serial.printf("Player 0 hit: paddleHitPosition0=%.2f\n", paddleHitPosition0);
|
||||||
velocity.x = -velocity.x;
|
velocity.x = -velocity.x;
|
||||||
velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition0 - 1));
|
velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition0 - 1));
|
||||||
ball.x = 3;
|
ball.x = 3;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto paddleHitPosition1 = ball.y - player1.y;
|
double paddleHitPosition1 = ball.y - player1.y;
|
||||||
if (ball.x >= width - 2 && ball.x < width - 1 && paddleHitPosition1 >= 0 && paddleHitPosition1 < player1.size) {
|
if (ball.x >= width - 2 && ball.x < width - 1 && paddleHitPosition1 >= 0 && paddleHitPosition1 < player1.size) {
|
||||||
|
Serial.printf("Player 1 hit: paddleHitPosition1=%.2f\n", paddleHitPosition1);
|
||||||
velocity.x = -velocity.x;
|
velocity.x = -velocity.x;
|
||||||
velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition1 - 1));
|
velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition1 - 1));
|
||||||
ball.x = width - 4;
|
ball.x = width - 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void spawnBall(const int direction) {
|
void spawnBall(int direction) {
|
||||||
ball.x = static_cast<double>(width) / 2.0;
|
ball.x = (double) width / 2.0;
|
||||||
ball.y = static_cast<double>(height) / 2.0;
|
ball.y = (double) height / 2.0;
|
||||||
velocity.x = direction;
|
velocity.x = direction;
|
||||||
velocity.y = 0;
|
velocity.y = 0;
|
||||||
status = SCORE;
|
status = SCORE;
|
||||||
|
|||||||
@ -1,25 +1,18 @@
|
|||||||
#ifndef MODE_POWER_H
|
#ifndef MODE_POWER_H
|
||||||
#define MODE_POWER_H
|
#define MODE_POWER_H
|
||||||
|
|
||||||
#include <patrix/core/mqtt.h>
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
#define PHOTOVOLTAIC_POWER_W "openDTU/pv/ac/power"
|
#pragma clang diagnostic push
|
||||||
#define GRID_POWER_W "electricity/grid/power/signed/w"
|
#pragma ide diagnostic ignored "UnusedValue"
|
||||||
|
|
||||||
class Power final : public Mode {
|
class Power : public Mode {
|
||||||
|
|
||||||
double photovoltaicPowerW = NAN;
|
|
||||||
|
|
||||||
unsigned long photovoltaicPowerWLast = 0;
|
|
||||||
|
|
||||||
double gridPowerW = NAN;
|
|
||||||
|
|
||||||
unsigned long gridPowerWLast = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Power(Display& display) : Mode(display) {
|
explicit Power(Display &display) :
|
||||||
|
Mode(display) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,28 +20,8 @@ public:
|
|||||||
return "Power";
|
return "Power";
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() override {
|
|
||||||
mqttSubscribe(PHOTOVOLTAIC_POWER_W);
|
|
||||||
mqttSubscribe(GRID_POWER_W);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() override {
|
|
||||||
mqttUnsubscribe(PHOTOVOLTAIC_POWER_W);
|
|
||||||
mqttUnsubscribe(GRID_POWER_W);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void mqttMessage(const String& topic, const String& message) override {
|
|
||||||
if (topic.equals(PHOTOVOLTAIC_POWER_W)) {
|
|
||||||
photovoltaicPowerW = message.toDouble();
|
|
||||||
photovoltaicPowerWLast = millis();
|
|
||||||
} else if (topic.equals(GRID_POWER_W)) {
|
|
||||||
gridPowerW = message.toDouble();
|
|
||||||
gridPowerWLast = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void step(microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
if (realtimeChanged) {
|
if (realtimeChanged) {
|
||||||
markDirty();
|
markDirty();
|
||||||
@ -57,14 +30,12 @@ protected:
|
|||||||
|
|
||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
display.clear();
|
display.clear();
|
||||||
|
display.print2((DISPLAY_CHAR_WIDTH + 1) * 3 - 1, 0, getPhotovoltaicPowerW(), GREEN);
|
||||||
const auto pvColor = photovoltaicPowerW >= 100 ? Green : photovoltaicPowerW >= 20 ? Yellow : Red;
|
display.print2(width, 0, getGridPowerW(), ORANGE, WHITE, MAGENTA);
|
||||||
display.printf(1, 1, LEFT, pvColor, "%3.0f", photovoltaicPowerW);
|
|
||||||
|
|
||||||
const auto gridColor = gridPowerW >= 20 ? Orange : gridPowerW >= -20 ? Green : Magenta;
|
|
||||||
display.printf(30, 1, RIGHT, gridColor, "%.0f", gridPowerW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -18,7 +18,9 @@ struct Invader {
|
|||||||
uint8_t y;
|
uint8_t y;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpaceInvaders final : public Mode {
|
class SpaceInvaders : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
microseconds_t heroRuntime = 0;
|
microseconds_t heroRuntime = 0;
|
||||||
uint8_t heroX = 0;
|
uint8_t heroX = 0;
|
||||||
@ -44,14 +46,15 @@ class SpaceInvaders final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit SpaceInvaders(Display& display) : Mode(display),
|
explicit SpaceInvaders(Display &display) :
|
||||||
|
Mode(display),
|
||||||
invadersCountX(width / 3),
|
invadersCountX(width / 3),
|
||||||
invadersCountY(height / 4) {
|
invadersCountY(height / 4) {
|
||||||
|
|
||||||
swarmBegin = static_cast<Invader *>(malloc(sizeof(Invader) * invadersCountX * invadersCountY));
|
swarmBegin = (Invader *) malloc(sizeof(Invader) * invadersCountX * invadersCountY);
|
||||||
swarmEnd = swarmBegin + invadersCountX * invadersCountY;
|
swarmEnd = swarmBegin + invadersCountX * invadersCountY;
|
||||||
|
|
||||||
rocketsBegin = static_cast<Rocket *>(malloc(sizeof(Rocket) * ROCKET_MAX));
|
rocketsBegin = (Rocket *) malloc(sizeof(Rocket) * ROCKET_MAX);
|
||||||
rocketsEnd = rocketsBegin + ROCKET_MAX;
|
rocketsEnd = rocketsBegin + ROCKET_MAX;
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
@ -60,13 +63,13 @@ public:
|
|||||||
~SpaceInvaders() override {
|
~SpaceInvaders() override {
|
||||||
free(swarmBegin);
|
free(swarmBegin);
|
||||||
free(rocketsBegin);
|
free(rocketsBegin);
|
||||||
}
|
};
|
||||||
|
|
||||||
const char *getName() override {
|
const char *getName() override {
|
||||||
return "Space Invaders";
|
return "Space Invaders";
|
||||||
}
|
}
|
||||||
|
|
||||||
void move(int index, const int x, int y) override {
|
void move(int index, int x, int y) override {
|
||||||
randomEnabled = false;
|
randomEnabled = false;
|
||||||
heroX = max(1, min(width - 2, heroX + x));
|
heroX = max(1, min(width - 2, heroX + x));
|
||||||
}
|
}
|
||||||
@ -78,7 +81,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(const microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
stepRockets(microseconds);
|
stepRockets(microseconds);
|
||||||
stepInvaders(microseconds);
|
stepInvaders(microseconds);
|
||||||
if (randomEnabled) {
|
if (randomEnabled) {
|
||||||
@ -109,11 +112,11 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void stepRockets(const microseconds_t microseconds) {
|
void stepRockets(microseconds_t microseconds) {
|
||||||
rocketRuntime += microseconds;
|
rocketRuntime += microseconds;
|
||||||
if (rocketRuntime > 200000) {
|
if (rocketRuntime > 200000) {
|
||||||
rocketRuntime = rocketRuntime % 200000;
|
rocketRuntime = rocketRuntime % 200000;
|
||||||
for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
||||||
if (rocket->alive) {
|
if (rocket->alive) {
|
||||||
if (rocket->y == 0) {
|
if (rocket->y == 0) {
|
||||||
rocket->alive = false;
|
rocket->alive = false;
|
||||||
@ -131,7 +134,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stepInvaders(const microseconds_t microseconds) {
|
void stepInvaders(microseconds_t microseconds) {
|
||||||
swarmRuntime += microseconds;
|
swarmRuntime += microseconds;
|
||||||
|
|
||||||
if (swarmDown && swarmRuntime > 500000) {
|
if (swarmDown && swarmRuntime > 500000) {
|
||||||
@ -158,7 +161,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void randomStepHero(const microseconds_t microseconds) {
|
void randomStepHero(microseconds_t microseconds) {
|
||||||
heroRuntime += microseconds;
|
heroRuntime += microseconds;
|
||||||
if (heroRuntime >= 50000) {
|
if (heroRuntime >= 50000) {
|
||||||
heroRuntime = heroRuntime % 50000;
|
heroRuntime = heroRuntime % 50000;
|
||||||
@ -182,8 +185,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shoot() const {
|
void shoot() {
|
||||||
for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
||||||
if (!rocket->alive && rocket->flash == 0) {
|
if (!rocket->alive && rocket->flash == 0) {
|
||||||
rocket->alive = true;
|
rocket->alive = true;
|
||||||
rocket->x = heroX;
|
rocket->x = heroX;
|
||||||
@ -193,11 +196,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void collide() {
|
void collide() {
|
||||||
for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
||||||
if (!rocket->alive) {
|
if (!rocket->alive) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (auto invader = swarmBegin; invader < swarmEnd; invader++) {
|
for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) {
|
||||||
if (!invader->alive) {
|
if (!invader->alive) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -218,33 +221,33 @@ private:
|
|||||||
&& swarmX + invader->x * 3 + 1 >= rocket->x;
|
&& swarmX + invader->x * 3 + 1 >= rocket->x;
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawInvaders(Display& display) const {
|
void drawInvaders(Display &display) {
|
||||||
for (auto invader = swarmBegin; invader < swarmEnd; invader++) {
|
for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) {
|
||||||
if (invader->alive) {
|
if (invader->alive) {
|
||||||
display.setPixel(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, Red);
|
display.set(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, RED);
|
||||||
display.setPixel(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, Red);
|
display.set(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, RED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawRockets(Display& display) const {
|
void drawRockets(Display &display) {
|
||||||
for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
||||||
if (rocket->alive) {
|
if (rocket->alive) {
|
||||||
display.setPixel(rocket->x, rocket->y, Yellow);
|
display.set(rocket->x, rocket->y, YELLOW);
|
||||||
} else if (rocket->flash > 0) {
|
} else if (rocket->flash > 0) {
|
||||||
display.setPixel(rocket->x - 1, rocket->y - 1, RGBA::gray(rocket->flash));
|
display.set(rocket->x - 1, rocket->y - 1, gray(rocket->flash));
|
||||||
display.setPixel(rocket->x - 1, rocket->y + 1, RGBA::gray(rocket->flash));
|
display.set(rocket->x - 1, rocket->y + 1, gray(rocket->flash));
|
||||||
display.setPixel(rocket->x + 0, rocket->y + 0, RGBA::gray(rocket->flash));
|
display.set(rocket->x + 0, rocket->y + 0, gray(rocket->flash));
|
||||||
display.setPixel(rocket->x + 1, rocket->y + 1, RGBA::gray(rocket->flash));
|
display.set(rocket->x + 1, rocket->y + 1, gray(rocket->flash));
|
||||||
display.setPixel(rocket->x + 1, rocket->y - 1, RGBA::gray(rocket->flash));
|
display.set(rocket->x + 1, rocket->y - 1, gray(rocket->flash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawHero(Display& display) const {
|
void drawHero(Display &display) {
|
||||||
display.setPixel(heroX - 1, height - 1, Blue);
|
display.set(heroX - 1, height - 1, BLUE);
|
||||||
display.setPixel(heroX + 0, height - 1, Blue);
|
display.set(heroX + 0, height - 1, BLUE);
|
||||||
display.setPixel(heroX + 1, height - 1, Blue);
|
display.set(heroX + 1, height - 1, BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
@ -255,7 +258,7 @@ private:
|
|||||||
randomEnabled = true;
|
randomEnabled = true;
|
||||||
|
|
||||||
rocketRuntime = 0;
|
rocketRuntime = 0;
|
||||||
for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
|
||||||
rocket->alive = false;
|
rocket->alive = false;
|
||||||
rocket->flash = 0;
|
rocket->flash = 0;
|
||||||
rocket->x = 0;
|
rocket->x = 0;
|
||||||
@ -269,7 +272,7 @@ private:
|
|||||||
swarmLeft = false;
|
swarmLeft = false;
|
||||||
swarmDown = false;
|
swarmDown = false;
|
||||||
uint8_t n = 0;
|
uint8_t n = 0;
|
||||||
for (auto invader = swarmBegin; invader < swarmEnd; invader++) {
|
for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) {
|
||||||
invader->alive = true;
|
invader->alive = true;
|
||||||
invader->x = n % invadersCountX;
|
invader->x = n % invadersCountX;
|
||||||
invader->y = n / invadersCountX;
|
invader->y = n / invadersCountX;
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
class Starfield final : public Mode {
|
class Starfield : public Mode {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
Vector center;
|
Vector center;
|
||||||
|
|
||||||
@ -13,7 +15,8 @@ class Starfield final : public Mode {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Starfield(Display& display) : Mode(display),
|
explicit Starfield(Display &display) :
|
||||||
|
Mode(display),
|
||||||
center(width / 2.0, height / 2.0),
|
center(width / 2.0, height / 2.0),
|
||||||
centerNext(center.x, center.y) {
|
centerNext(center.x, center.y) {
|
||||||
for (auto &star: stars) {
|
for (auto &star: stars) {
|
||||||
@ -28,10 +31,10 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(const microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
stepCenter();
|
stepCenter();
|
||||||
for (auto &star: stars) {
|
for (auto &star: stars) {
|
||||||
const auto velocity = star.minus(center).multiply(static_cast<double>(microseconds) / 200000.0);
|
const Vector velocity = star.minus(center).multiply((double) microseconds / 200000.0);
|
||||||
star = star.plus(velocity);
|
star = star.plus(velocity);
|
||||||
if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) {
|
if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) {
|
||||||
star = center.plus(Vector::polar(random(360), 1));
|
star = center.plus(Vector::polar(random(360), 1));
|
||||||
@ -42,7 +45,6 @@ protected:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once CppMemberFunctionMayBeStatic
|
|
||||||
void stepCenter() {
|
void stepCenter() {
|
||||||
// TODO moving center overtakes moving stars (less stars in direction of moving center)
|
// TODO moving center overtakes moving stars (less stars in direction of moving center)
|
||||||
// Vector diff = centerNext.minus(center);
|
// Vector diff = centerNext.minus(center);
|
||||||
@ -65,8 +67,8 @@ protected:
|
|||||||
void draw(Display &display) override {
|
void draw(Display &display) override {
|
||||||
display.clear();
|
display.clear();
|
||||||
for (auto &star: stars) {
|
for (auto &star: stars) {
|
||||||
const auto brightness = static_cast<uint8_t>(round(255.0 * star.minus(center).length / (width / 2.0)));
|
uint8_t brightness = (uint8_t) round(255.0 * star.minus(center).length / (width / 2.0));
|
||||||
display.setPixel(star.x, star.y, RGBA::gray(brightness));
|
display.set(star, gray(brightness));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,9 @@
|
|||||||
|
|
||||||
#include "mode/Mode.h"
|
#include "mode/Mode.h"
|
||||||
|
|
||||||
#define DEFAULT_DURATION_MILLIS (6 * 60 * 1000L)
|
|
||||||
|
|
||||||
class Timer2 final : public Mode {
|
class Timer2 final : public Mode {
|
||||||
|
|
||||||
long timerMillis = DEFAULT_DURATION_MILLIS;
|
const unsigned long targetMillis = millis() + 6 * 60 * 1000;
|
||||||
|
|
||||||
long restMillis = timerMillis;
|
|
||||||
|
|
||||||
unsigned long lastMillis = 0;
|
|
||||||
|
|
||||||
uint16_t days = 0;
|
uint16_t days = 0;
|
||||||
|
|
||||||
@ -21,6 +15,8 @@ class Timer2 final : public Mode {
|
|||||||
|
|
||||||
uint16_t seconds = 0;
|
uint16_t seconds = 0;
|
||||||
|
|
||||||
|
unsigned long diffSeconds = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Timer2(Display& display) : Mode(display) {
|
explicit Timer2(Display& display) : Mode(display) {
|
||||||
@ -31,52 +27,87 @@ public:
|
|||||||
return "Timer";
|
return "Timer";
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadConfig() override {
|
|
||||||
const auto newTimerMillis = config.get("timerMillis", DEFAULT_DURATION_MILLIS);
|
|
||||||
if (restMillis > 0) {
|
|
||||||
restMillis += newTimerMillis - timerMillis;
|
|
||||||
}
|
|
||||||
timerMillis = newTimerMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() override {
|
|
||||||
restMillis = timerMillis;
|
|
||||||
lastMillis = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void step(microseconds_t microseconds) override {
|
void step(microseconds_t microseconds) override {
|
||||||
const auto now = millis();
|
const auto now = millis();
|
||||||
const auto deltaMillis = now - lastMillis;
|
diffSeconds = now >= targetMillis ? 0 : (targetMillis - now) / 1000;
|
||||||
lastMillis = now;
|
days = diffSeconds / (24 * 60 * 60);
|
||||||
|
hours = diffSeconds / (60 * 60) % 24;
|
||||||
restMillis -= static_cast<long>(deltaMillis);
|
minutes = diffSeconds / 60 % 60;
|
||||||
if (restMillis < 0) {
|
seconds = diffSeconds % 60;
|
||||||
restMillis = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto restSeconds = restMillis / 1000;
|
|
||||||
days = restSeconds / (24 * 60 * 60);
|
|
||||||
hours = restSeconds / (60 * 60) % 24;
|
|
||||||
minutes = restSeconds / 60 % 60;
|
|
||||||
seconds = restSeconds % 60;
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(Display& display) override {
|
void draw(Display& display) override {
|
||||||
display.clear();
|
display.clear();
|
||||||
if (days > 10) {
|
|
||||||
display.printf(30, 1, RIGHT, White, "%d TAGE", days);
|
if (diffSeconds == 0) {
|
||||||
} else if (days > 0) {
|
drawNoTime(display);
|
||||||
display.printf(30, 1, RIGHT, White, "%d %2d:%02d", days, hours, minutes);
|
return;
|
||||||
} else if (hours > 0) {
|
|
||||||
display.printf(30, 1, RIGHT, White, "%d:%02d:%02d", hours, minutes, seconds);
|
|
||||||
} else if (minutes > 0) {
|
|
||||||
display.printf(30, 1, RIGHT, White, "%d:%02d", minutes, seconds);
|
|
||||||
} else {
|
|
||||||
display.printf(30, 1, RIGHT, Orange, "%d", seconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t x = 0;
|
||||||
|
if (days > 0) {
|
||||||
|
drawDay(display, days, &x);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
} else {
|
||||||
|
x += 2;
|
||||||
|
}
|
||||||
|
drawHour(display, days, hours, &x);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
draw2Digit(display, minutes, &x);
|
||||||
|
if (days <= 0) {
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
draw2Digit(display, seconds, &x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static void drawNoTime(Display& display) {
|
||||||
|
uint8_t x = 2;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x += display.print(x, 1, 10, WHITE, true);
|
||||||
|
x += display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
x++;
|
||||||
|
display.print(x, 1, SYMBOL_DASH, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawDay(Display& display, int days, uint8_t *x) {
|
||||||
|
if (days >= 100) {
|
||||||
|
*x += display.print(*x, 1, days / 100, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (days >= 10) {
|
||||||
|
*x += display.print(*x, 1, days / 10 % 10, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*x += display.print(*x, 1, days % 10, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawHour(Display& display, int days, int hours, uint8_t *x) {
|
||||||
|
if (days > 0 || hours >= 10) {
|
||||||
|
*x += display.print(*x, 1, hours / 10, WHITE, true) + 1;
|
||||||
|
} else {
|
||||||
|
*x += DISPLAY_CHAR_WIDTH + 1;
|
||||||
|
}
|
||||||
|
*x += display.print(*x, 1, hours % 10, WHITE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw2Digit(Display& display, int value, uint8_t *x) {
|
||||||
|
*x += display.print(*x, 1, value / 10, WHITE, true) + 1;
|
||||||
|
*x += display.print(*x, 1, value % 10, WHITE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
141
src/mqtt.cpp
Normal file
141
src/mqtt.cpp
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "mqtt.h"
|
||||||
|
#include "PubSubClient.h"
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
|
#define MQTT_CONNECT_TIMEOUT_MILLISECONDS 5000
|
||||||
|
|
||||||
|
#define MQTT_MAX_MESSAGE_AGE_MILLIS 11000
|
||||||
|
|
||||||
|
#define PHOTOVOLTAIC_POWER_W "openDTU/pv/ac/power"
|
||||||
|
#define PHOTOVOLTAIC_ENERGY_KWH "openDTU/pv/ac/yieldtotal"
|
||||||
|
#define GRID_POWER_W "electricity/grid/power/signed/w"
|
||||||
|
#define GRID_IMPORT_WH "electricity/grid/energy/import/wh"
|
||||||
|
#define GRID_EXPORT_WH "electricity/grid/energy/export/wh"
|
||||||
|
|
||||||
|
WiFiClient espClient;
|
||||||
|
|
||||||
|
PubSubClient mqtt(espClient);
|
||||||
|
|
||||||
|
bool mqttConnected = false;
|
||||||
|
|
||||||
|
unsigned long mqttLastConnectTry = 0;
|
||||||
|
|
||||||
|
double photovoltaicPowerW = NAN;
|
||||||
|
|
||||||
|
unsigned long photovoltaicPowerWLast = 0;
|
||||||
|
|
||||||
|
double photovoltaicEnergyKWh = NAN;
|
||||||
|
|
||||||
|
unsigned long photovoltaicEnergyKWhLast = 0;
|
||||||
|
|
||||||
|
double gridPowerW = NAN;
|
||||||
|
|
||||||
|
unsigned long gridPowerWLast = 0;
|
||||||
|
|
||||||
|
double gridImportKWh = NAN;
|
||||||
|
|
||||||
|
unsigned long gridImportKWhLast = 0;
|
||||||
|
|
||||||
|
double gridExportKWh = NAN;
|
||||||
|
|
||||||
|
unsigned long gridExportKWhLast = 0;
|
||||||
|
|
||||||
|
void mqttDisconnect();
|
||||||
|
|
||||||
|
void mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
|
||||||
|
char message[128];
|
||||||
|
if (length > sizeof message - 1) {
|
||||||
|
Serial.printf("MQTT: received too long message: topic=%s, length=%d", topic, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(message, payload, length);
|
||||||
|
message[length] = 0;
|
||||||
|
if (strcmp(PHOTOVOLTAIC_POWER_W, topic) == 0) {
|
||||||
|
photovoltaicPowerW = strtod(message, nullptr);
|
||||||
|
photovoltaicPowerWLast = millis();
|
||||||
|
} else if (strcmp(PHOTOVOLTAIC_ENERGY_KWH, topic) == 0) {
|
||||||
|
photovoltaicEnergyKWh = strtod(message, nullptr);
|
||||||
|
photovoltaicEnergyKWhLast = millis();
|
||||||
|
} else if (strcmp(GRID_POWER_W, topic) == 0) {
|
||||||
|
gridPowerW = strtod(message, nullptr);
|
||||||
|
gridPowerWLast = millis();
|
||||||
|
} else if (strcmp(GRID_IMPORT_WH, topic) == 0) {
|
||||||
|
gridImportKWh = strtod(message, nullptr) / 1000;
|
||||||
|
gridImportKWhLast = millis();
|
||||||
|
} else if (strcmp(GRID_EXPORT_WH, topic) == 0) {
|
||||||
|
gridExportKWh = strtod(message, nullptr) / 1000;
|
||||||
|
gridExportKWhLast = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqtt_loop() {
|
||||||
|
if (!wifiIsConnected()) {
|
||||||
|
mqttDisconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mqtt.loop() && (mqttLastConnectTry == 0 || millis() - mqttLastConnectTry > MQTT_CONNECT_TIMEOUT_MILLISECONDS)) {
|
||||||
|
mqttLastConnectTry = millis();
|
||||||
|
mqtt.setServer("10.0.0.50", 1883);
|
||||||
|
mqttConnected = mqtt.connect(WiFiClass::getHostname());
|
||||||
|
if (mqttConnected) {
|
||||||
|
Serial.printf("Successfully connected mqtt broker at %s:%d\n", "10.0.0.50", 1883);
|
||||||
|
mqtt.setCallback(mqttCallback);
|
||||||
|
mqtt.subscribe(PHOTOVOLTAIC_POWER_W);
|
||||||
|
mqtt.subscribe(PHOTOVOLTAIC_ENERGY_KWH);
|
||||||
|
mqtt.subscribe(GRID_POWER_W);
|
||||||
|
mqtt.subscribe(GRID_IMPORT_WH);
|
||||||
|
mqtt.subscribe(GRID_EXPORT_WH);
|
||||||
|
} else {
|
||||||
|
Serial.printf("ERROR: Failed to connect MQTT broker at %s:%d\n", "10.0.0.50", 1883);
|
||||||
|
mqttDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttDisconnect() {
|
||||||
|
if (mqttConnected) {
|
||||||
|
mqtt.disconnect();
|
||||||
|
mqttConnected = false;
|
||||||
|
photovoltaicPowerW = NAN;
|
||||||
|
photovoltaicPowerWLast = 0;
|
||||||
|
gridPowerW = NAN;
|
||||||
|
gridPowerWLast = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double getPhotovoltaicPowerW() {
|
||||||
|
if (millis() - photovoltaicPowerWLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
return photovoltaicPowerW;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getPhotovoltaicEnergyKWh() {
|
||||||
|
if (millis() - photovoltaicEnergyKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
return photovoltaicEnergyKWh;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getGridPowerW() {
|
||||||
|
if (millis() - gridPowerWLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
return gridPowerW;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getGridImportKWh() {
|
||||||
|
if (millis() - gridImportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
return gridImportKWh;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getGridExportKWh() {
|
||||||
|
if (millis() - gridExportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
return gridExportKWh;
|
||||||
|
}
|
||||||
16
src/mqtt.h
Normal file
16
src/mqtt.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef SENSOR2_MQTT_H
|
||||||
|
#define SENSOR2_MQTT_H
|
||||||
|
|
||||||
|
void mqtt_loop();
|
||||||
|
|
||||||
|
double getPhotovoltaicPowerW();
|
||||||
|
|
||||||
|
double getPhotovoltaicEnergyKWh();
|
||||||
|
|
||||||
|
double getGridPowerW();
|
||||||
|
|
||||||
|
double getGridImportKWh();
|
||||||
|
|
||||||
|
double getGridExportKWh();
|
||||||
|
|
||||||
|
#endif
|
||||||
52
src/serial.cpp
Normal file
52
src/serial.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "serial.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "mode.h"
|
||||||
|
|
||||||
|
void serial_setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("\n\n\nStartup!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_loop() {
|
||||||
|
if (Serial.available()) {
|
||||||
|
int input = Serial.read();
|
||||||
|
switch (input) {
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
setMode((ModeId) (input - '0'));
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
case 'b':
|
||||||
|
setMode((ModeId) (input - 'a' + 10));
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
setSpeed(1.0);
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
setBrightness(display.getBrightness() + 10);
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
setBrightness(display.getBrightness() - 10);
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
setSpeed(config.speed / 1.1);
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
setSpeed(config.speed * 1.1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Serial.printf("Unknown command: %c\n", input);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/serial.h
Normal file
8
src/serial.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef RGBMATRIXDISPLAY_SERIAL_H
|
||||||
|
#define RGBMATRIXDISPLAY_SERIAL_H
|
||||||
|
|
||||||
|
void serial_loop();
|
||||||
|
|
||||||
|
void serial_setup();
|
||||||
|
|
||||||
|
#endif
|
||||||
315
src/server.cpp
Normal file
315
src/server.cpp
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
#include <WebServer.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include "server.h"
|
||||||
|
#include "mode/Mode.h"
|
||||||
|
#include "mode.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
static const char *const style = R"(
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 8vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
button.player{
|
||||||
|
width: 33vmin;
|
||||||
|
height: 33vmin;
|
||||||
|
font-size: 9vw;
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char *const script = R"(
|
||||||
|
<script>
|
||||||
|
function get(path){
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("GET", path, true);
|
||||||
|
r.send();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
)";
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
void web_index();
|
||||||
|
|
||||||
|
void web_player();
|
||||||
|
|
||||||
|
void web_player_move();
|
||||||
|
|
||||||
|
void web_player_fire();
|
||||||
|
|
||||||
|
void web_setMode();
|
||||||
|
|
||||||
|
void web_brighter();
|
||||||
|
|
||||||
|
void web_darker();
|
||||||
|
|
||||||
|
void web_faster();
|
||||||
|
|
||||||
|
void web_slower();
|
||||||
|
|
||||||
|
void web_fps_on();
|
||||||
|
|
||||||
|
void web_fps_off();
|
||||||
|
|
||||||
|
void web_config_date();
|
||||||
|
|
||||||
|
void web_config_save();
|
||||||
|
|
||||||
|
void server_setup() {
|
||||||
|
server.on("/", web_index);
|
||||||
|
server.on("/player", web_player);
|
||||||
|
server.on("/player/move", web_player_move);
|
||||||
|
server.on("/player/fire", web_player_fire);
|
||||||
|
server.on("/mode", web_setMode);
|
||||||
|
server.on("/brighter", web_brighter);
|
||||||
|
server.on("/darker", web_darker);
|
||||||
|
server.on("/faster", web_faster);
|
||||||
|
server.on("/slower", web_slower);
|
||||||
|
server.on("/fps/on", web_fps_on);
|
||||||
|
server.on("/fps/off", web_fps_off);
|
||||||
|
server.on("/config/date", web_config_date);
|
||||||
|
server.on("/config/save", web_config_save);
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void server_loop() {
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_index() {
|
||||||
|
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||||
|
server.send(200, "text/html", "");
|
||||||
|
server.sendContent(style);
|
||||||
|
server.sendContent(script);
|
||||||
|
|
||||||
|
server.sendContent(R"(<p>)");
|
||||||
|
server.sendContent(R"(<a href="/player?index=0">Player 0</a><br>)");
|
||||||
|
server.sendContent(R"(<a href="/player?index=1">Player 1</a><br>)");
|
||||||
|
server.sendContent(R"(</p>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<p>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=0');">NONE</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=1');">BORDER</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=2');">CLOCK</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=3');">GAME_OF_LIFE_BLACK_WHITE</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=4');">GAME_OF_LIFE_GRAYSCALE</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=5');">GAME_OF_LIFE_COLOR_FADE</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=6');">GAME_OF_LIFE_RANDOM_COLOR</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=7');">PONG</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=8');">SPACE_INVADERS</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=9');">COUNT_DOWN</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=10');">COUNT_DOWN_BARS</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=11');">COUNT_DOWN_SLEEP</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=12');">STARFIELD</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=13');">MATRIX</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=14');">POWER</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=15');">ENERGY</a><br>)");
|
||||||
|
server.sendContent(R"(<a onclick="get('/mode?mode=16');">TIMER</a><br>)");
|
||||||
|
server.sendContent(R"(</p>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<p>)");
|
||||||
|
server.sendContent(R"(Helligkeit: <a onclick="get('/brighter');">+</a> / <a onclick="get('/darker');">-</a><br>)");
|
||||||
|
server.sendContent(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)");
|
||||||
|
server.sendContent(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)");
|
||||||
|
server.sendContent(R"(</p>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<p>)");
|
||||||
|
server.sendContent(R"(<input type="number" min="1900" max="3000" step="1" name="year" id="year">)");
|
||||||
|
server.sendContent(R"(<input type="number" min="1" max="12" step="1" name="month" id="month">)");
|
||||||
|
server.sendContent(R"(<input type="number" min="1" max="31" step="1" name="day" id="day">)");
|
||||||
|
server.sendContent(R"(<button onclick="get('/config/date?year=' + document.getElementById('year').value + '&month=' + document.getElementById('month').value + '&day=' + document.getElementById('day').value);">Datum setzen</button>)");
|
||||||
|
server.sendContent(R"(</p>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<p>)");
|
||||||
|
server.sendContent(R"(<button onclick="get('/config/save');">Speichern erzwingen</button>)");
|
||||||
|
server.sendContent(R"(</p>)");
|
||||||
|
|
||||||
|
server.client().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_player() {
|
||||||
|
char buffer[128];
|
||||||
|
|
||||||
|
if (!server.hasArg("index")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'index'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double value = strtod(server.arg("index").c_str(), nullptr);
|
||||||
|
int index = (int) value;
|
||||||
|
|
||||||
|
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||||
|
server.send(200, "text/html", "");
|
||||||
|
server.sendContent(style);
|
||||||
|
server.sendContent(script);
|
||||||
|
|
||||||
|
server.sendContent(R"(<meta name="viewport" content= "width=device-width, user-scalable=no">)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<table>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td><a href='/'>←</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td>)");
|
||||||
|
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=-1');">↑</button><br>)", index);
|
||||||
|
server.sendContent(buffer);
|
||||||
|
server.sendContent(R"(</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td> </td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(</tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td>)");
|
||||||
|
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=-1&y=0');">←</button><br>)", index);
|
||||||
|
server.sendContent(buffer);
|
||||||
|
server.sendContent(R"(</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td>)");
|
||||||
|
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/fire?index=%d');">X</button><br>)", index);
|
||||||
|
server.sendContent(buffer);
|
||||||
|
server.sendContent(R"(</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td>)");
|
||||||
|
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=+1&y=0');">→</button><br>)", index);
|
||||||
|
server.sendContent(buffer);
|
||||||
|
server.sendContent(R"(</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(</tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td> </td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td>)");
|
||||||
|
snprintf(buffer, sizeof buffer, R"(<button class="player" onclick="get('/player/move?index=%d&x=0&y=+1');">↓</button><br>)", index);
|
||||||
|
server.sendContent(buffer);
|
||||||
|
server.sendContent(R"(</td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(<td> </td>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(</tr>)");
|
||||||
|
|
||||||
|
server.sendContent(R"(</table>)");
|
||||||
|
server.client().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_player_move() {
|
||||||
|
double value;
|
||||||
|
|
||||||
|
if (!server.hasArg("index")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'index'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = strtod(server.arg("index").c_str(), nullptr);
|
||||||
|
int index = (int) value;
|
||||||
|
|
||||||
|
if (!server.hasArg("x")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'x'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = strtod(server.arg("x").c_str(), nullptr);
|
||||||
|
int x = (int) value;
|
||||||
|
|
||||||
|
if (!server.hasArg("y")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'y'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = strtod(server.arg("y").c_str(), nullptr);
|
||||||
|
int y = (int) value;
|
||||||
|
|
||||||
|
modeMove(index, x, y);
|
||||||
|
|
||||||
|
server.send(200, "application/json", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_player_fire() {
|
||||||
|
double value;
|
||||||
|
|
||||||
|
if (!server.hasArg("index")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'index'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = strtod(server.arg("index").c_str(), nullptr);
|
||||||
|
int index = (int) value;
|
||||||
|
|
||||||
|
modeFire(index);
|
||||||
|
|
||||||
|
server.send(200, "application/json", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_setMode() {
|
||||||
|
if (!server.hasArg("mode")) {
|
||||||
|
server.send(400, "text/plain", "Missing 'mode'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double value = strtod(server.arg("mode").c_str(), nullptr);
|
||||||
|
if (isnan(value)) {
|
||||||
|
server.send(400, "text/plain", "'mode' not a number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMode((ModeId) value);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_brighter() {
|
||||||
|
setBrightness(display.getBrightness() + 10);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_darker() {
|
||||||
|
setBrightness(display.getBrightness() - 10);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_faster() {
|
||||||
|
setSpeed(config.speed * 1.1);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_slower() {
|
||||||
|
setSpeed(config.speed / 1.1);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_fps_on() {
|
||||||
|
display.fpsShow = true;
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_fps_off() {
|
||||||
|
display.fpsShow = false;
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_config_save() {
|
||||||
|
configSaveNowIfDirty();
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void web_config_date() {
|
||||||
|
double year = strtod(server.arg("year").c_str(), nullptr);
|
||||||
|
double month = strtod(server.arg("month").c_str(), nullptr);
|
||||||
|
double day = strtod(server.arg("day").c_str(), nullptr);
|
||||||
|
if (!isnan(year)) {
|
||||||
|
config.date.tm_year = (int) year;
|
||||||
|
config.date.tm_mon = (int) month;
|
||||||
|
config.date.tm_mday = (int) day;
|
||||||
|
config.date.tm_hour = 0;
|
||||||
|
config.date.tm_min = 0;
|
||||||
|
config.date.tm_sec = 0;
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
server.send(400);
|
||||||
|
}
|
||||||
8
src/server.h
Normal file
8
src/server.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef RGBMATRIXDISPLAY_SERVER_H
|
||||||
|
#define RGBMATRIXDISPLAY_SERVER_H
|
||||||
|
|
||||||
|
void server_setup();
|
||||||
|
|
||||||
|
void server_loop();
|
||||||
|
|
||||||
|
#endif
|
||||||
104
src/wifi.cpp
Normal file
104
src/wifi.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#include "wifi.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <esp_sntp.h>
|
||||||
|
|
||||||
|
bool wifiConnected = false;
|
||||||
|
|
||||||
|
void onConnect();
|
||||||
|
|
||||||
|
uint32_t ip2int(const IPAddress &ip);
|
||||||
|
|
||||||
|
void timeSyncCallback(struct timeval *tv);
|
||||||
|
|
||||||
|
char *calculateGateway(char *calculatedGateway, size_t size);
|
||||||
|
|
||||||
|
void wifi_setup() {
|
||||||
|
WiFiClass::setHostname("RGBMatrixDisplay");
|
||||||
|
WiFi.begin("HappyNet", "1Grausame!Sackratte7");
|
||||||
|
yield();
|
||||||
|
|
||||||
|
ArduinoOTA.onStart([]() {
|
||||||
|
Serial.print("\n\nOTA Update: ");
|
||||||
|
display.clear();
|
||||||
|
display.loop();
|
||||||
|
});
|
||||||
|
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
||||||
|
double ratio = (double) progress / (double) total;
|
||||||
|
Serial.printf("\rOTA Update: %3.0f%%", ratio * 100);
|
||||||
|
|
||||||
|
auto index = (uint16_t) round(ratio * (double) display.pixelCount);
|
||||||
|
auto color = (uint8_t) round(ratio * 255.0);
|
||||||
|
display.set(index, {(uint8_t) (255 - color), color, 0});
|
||||||
|
display.loop();
|
||||||
|
});
|
||||||
|
ArduinoOTA.onEnd([]() {
|
||||||
|
Serial.println("\nOTA Success!\n");
|
||||||
|
display.clear();
|
||||||
|
display.loop();
|
||||||
|
});
|
||||||
|
ArduinoOTA.onError([](int error) {
|
||||||
|
Serial.println("\nOTA Failure!\n");
|
||||||
|
display.clear();
|
||||||
|
display.loop();
|
||||||
|
});
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifi_loop() {
|
||||||
|
ArduinoOTA.handle();
|
||||||
|
bool hasIp = (uint32_t) WiFi.localIP() != 0;
|
||||||
|
if (!wifiConnected) {
|
||||||
|
if (hasIp) {
|
||||||
|
wifiConnected = true;
|
||||||
|
onConnect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!hasIp) {
|
||||||
|
wifiConnected = false;
|
||||||
|
Serial.println("WiFi disconnected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onConnect() {
|
||||||
|
Serial.printf("WiFi connected: %s\n", WiFi.localIP().toString().c_str());
|
||||||
|
char calculatedGateway[16] = {0};
|
||||||
|
calculateGateway(calculatedGateway, sizeof(calculatedGateway));
|
||||||
|
sntp_set_time_sync_notification_cb(timeSyncCallback);
|
||||||
|
Serial.printf("configTime(%s / %s / %s)\n", WiFi.gatewayIP().toString().c_str(), calculatedGateway, "pool.ntp.org");
|
||||||
|
configTime(3600, 3600, "pool.ntp.org", WiFi.gatewayIP().toString().c_str(), calculatedGateway);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wifiIsConnected() {
|
||||||
|
return wifiConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *calculateGateway(char *calculatedGateway, size_t size) {
|
||||||
|
uint32_t local = ip2int(WiFi.localIP());
|
||||||
|
uint32_t netmask = ip2int(WiFi.subnetMask());
|
||||||
|
uint32_t gateway = local & netmask + 1;
|
||||||
|
snprintf(
|
||||||
|
calculatedGateway,
|
||||||
|
size,
|
||||||
|
"%u.%u.%u.%u",
|
||||||
|
(gateway >> 24) & 0xFF,
|
||||||
|
(gateway >> 16) & 0xFF,
|
||||||
|
(gateway >> 8) & 0xFF,
|
||||||
|
(gateway >> 0) & 0xFF
|
||||||
|
);
|
||||||
|
return calculatedGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ip2int(const IPAddress &ip) {
|
||||||
|
return ((ip[0] * 256 + ip[1]) * 256 + ip[2]) * 256 + ip[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
void timeSyncCallback(struct timeval *tv) {
|
||||||
|
Serial.printf("timeSyncCallback: %ld\n", tv->tv_sec);
|
||||||
|
}
|
||||||
10
src/wifi.h
Normal file
10
src/wifi.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef RGBMATRIXDISPLAY_WIFI_H
|
||||||
|
#define RGBMATRIXDISPLAY_WIFI_H
|
||||||
|
|
||||||
|
void wifi_setup();
|
||||||
|
|
||||||
|
void wifi_loop();
|
||||||
|
|
||||||
|
bool wifiIsConnected();
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user