553 lines
20 KiB
YAML
553 lines
20 KiB
YAML
substitutions:
|
|
name: "energymonitor"
|
|
friendly_name: "Energy Monitor"
|
|
grid_power_gauge_max_w: "5000.0"
|
|
balcony_solar_gauge_max_w: "800.0"
|
|
|
|
esphome:
|
|
name: ${name}
|
|
friendly_name: ${friendly_name}
|
|
name_add_mac_suffix: false
|
|
project:
|
|
name: monitor.energymonitor
|
|
version: "1.0"
|
|
on_boot:
|
|
then:
|
|
- light.turn_on: display_backlight
|
|
|
|
esp32:
|
|
board: m5stack-core-esp32
|
|
framework:
|
|
type: esp-idf
|
|
advanced:
|
|
minimum_chip_revision: "3.1"
|
|
sram1_as_iram: true
|
|
|
|
network:
|
|
enable_ipv6: true
|
|
|
|
wifi:
|
|
networks:
|
|
- ssid: "Voltage-legacy"
|
|
password: !secret voltage_legacy_psk
|
|
priority: 10
|
|
- ssid: "Moppelkotze"
|
|
password: !secret moppelkotze_psk
|
|
priority: 5
|
|
- ssid: "TKRZ"
|
|
password: !secret tkrz_psk
|
|
priority: 4
|
|
use_address: 192.168.126.250
|
|
on_connect:
|
|
- lambda: id(wifi_connected) = true;
|
|
on_disconnect:
|
|
- lambda: id(wifi_connected) = false;
|
|
power_save_mode: high
|
|
min_auth_mode: WPA2
|
|
ap:
|
|
ssid: "Energymonitor Fallback Hotspot"
|
|
password: !secret fallback_psk
|
|
|
|
captive_portal:
|
|
|
|
time:
|
|
- platform: sntp
|
|
id: sntp_time
|
|
timezone: Europe/Berlin
|
|
servers:
|
|
- de.pool.ntp.org
|
|
|
|
# Values are maintained in secrets.yaml.
|
|
wireguard:
|
|
id: energymonitor_wireguard
|
|
address: !secret energymonitor_wireguard_address
|
|
netmask: !secret energymonitor_wireguard_netmask
|
|
private_key: !secret energymonitor_wireguard_private_key
|
|
peer_endpoint: !secret energymonitor_wireguard_peer_endpoint
|
|
peer_port: !secret energymonitor_wireguard_peer_port
|
|
peer_public_key: !secret energymonitor_wireguard_peer_public_key
|
|
peer_allowed_ips:
|
|
- !secret energymonitor_wireguard_peer_allowed_ip
|
|
peer_persistent_keepalive: 25s
|
|
# Avoid rebooting while the placeholder endpoint is intentionally unreachable.
|
|
reboot_timeout: 0s
|
|
|
|
logger:
|
|
|
|
api:
|
|
id: api_server
|
|
encryption:
|
|
key: !secret apikey
|
|
|
|
ota:
|
|
platform: esphome
|
|
password: !secret ota
|
|
|
|
spi:
|
|
clk_pin: GPIO18
|
|
mosi_pin: GPIO23
|
|
|
|
output:
|
|
- platform: gpio
|
|
pin: GPIO32
|
|
id: display_backlight_output
|
|
|
|
light:
|
|
- platform: binary
|
|
id: display_backlight
|
|
name: "Display Backlight"
|
|
output: display_backlight_output
|
|
restore_mode: ALWAYS_ON
|
|
|
|
font:
|
|
- file: "fonts/Roboto-Regular.ttf"
|
|
id: font_small
|
|
size: 18
|
|
glyphsets:
|
|
- GF_Latin_Kernel
|
|
glyphs: ["äöüÄÖÜß"]
|
|
- file: "fonts/Roboto-Bold.ttf"
|
|
id: font_title
|
|
size: 28
|
|
glyphsets:
|
|
- GF_Latin_Kernel
|
|
glyphs: ["äöüÄÖÜß"]
|
|
- file: "fonts/Roboto-Bold.ttf"
|
|
id: font_value
|
|
size: 52
|
|
glyphsets:
|
|
- GF_Latin_Kernel
|
|
glyphs: ["äöüÄÖÜß"]
|
|
- file: "fonts/Roboto-Regular.ttf"
|
|
id: font_device
|
|
size: 13
|
|
glyphsets:
|
|
- GF_Latin_Kernel
|
|
glyphs: ["äöüÄÖÜß"]
|
|
- file: "fonts/materialdesignicons-webfont.ttf"
|
|
id: font_icon
|
|
size: 24
|
|
glyphs:
|
|
- "\U000F0306" # mdi:key
|
|
- "\U000F07D0" # mdi:home-assistant
|
|
- "\U000F091F" # mdi:wifi-strength-1
|
|
- "\U000F0922" # mdi:wifi-strength-2
|
|
- "\U000F0925" # mdi:wifi-strength-3
|
|
- "\U000F0928" # mdi:wifi-strength-4
|
|
- "\U000F092D" # mdi:wifi-strength-off
|
|
|
|
globals:
|
|
- id: page_index
|
|
type: int
|
|
restore_value: true
|
|
initial_value: '0'
|
|
- id: wifi_connected
|
|
type: bool
|
|
restore_value: false
|
|
initial_value: 'false'
|
|
- id: page_one_gauge_view
|
|
type: bool
|
|
restore_value: true
|
|
initial_value: 'false'
|
|
- id: device_usage_view
|
|
type: int
|
|
restore_value: true
|
|
initial_value: '0'
|
|
|
|
sensor:
|
|
# WiFi signal strength
|
|
- platform: wifi_signal
|
|
name: "WiFi Signalstärke"
|
|
id: wifisignal
|
|
update_interval: 10s
|
|
- platform: homeassistant
|
|
id: grid_power
|
|
entity_id: sensor.netzleistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: balcony_solar_power
|
|
entity_id: sensor.balkonkraftwerk_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: solar_energy_daily
|
|
entity_id: sensor.energy_solar_daily
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: grid_import_energy_daily
|
|
entity_id: sensor.energy_import_daily
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: grid_export_energy_daily
|
|
entity_id: sensor.energy_export_daily
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: hallway_fragrance_power
|
|
entity_id: sensor.flur_raumduft_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: cellar_power
|
|
entity_id: sensor.keller_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: sideboard_power
|
|
entity_id: sensor.kuche_anrichte_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: kitchen_music_power
|
|
entity_id: sensor.kuche_musik_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: bedroom_bed_power
|
|
entity_id: sensor.schlafzimmer_bett_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: bedroom_cinema_power
|
|
entity_id: sensor.schlafzimmer_heimkino_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: desk_decoration_power
|
|
entity_id: sensor.schreibtisch_deko_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: desk_power
|
|
entity_id: sensor.schreibtisch_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: server_room_power
|
|
entity_id: sensor.serverraum_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: living_room_cinema_power
|
|
entity_id: sensor.wohnzimmer_heimkino_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: games_cabinet_power
|
|
entity_id: sensor.wohnzimmer_spieleschrank_power
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: refrigerator_power
|
|
entity_id: sensor.tz3000_ww6drja5_ts011f_leistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: coffee_machine_power
|
|
entity_id: sensor.kaffeemaschine_leistung_2
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: dryer_power
|
|
entity_id: sensor.ikea_of_sweden_inspelning_smart_plug_leistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: washing_machine_power
|
|
entity_id: sensor.waschmaschine_leistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: arcade_power
|
|
entity_id: sensor.arcade_automat_leistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: dishwasher_power
|
|
entity_id: sensor.spulmaschine_leistung
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: microwave_power
|
|
entity_id: sensor.waschetrockner_leistung
|
|
internal: true
|
|
- platform: wireguard
|
|
latest_handshake:
|
|
name: 'WireGuard Latest Handshake'
|
|
|
|
script:
|
|
- id: show_previous_page
|
|
then:
|
|
- lambda: id(page_index) = (id(page_index) + 2 - 1) % 2;
|
|
- component.update: tft
|
|
- id: toggle_page_mode
|
|
then:
|
|
- lambda: |-
|
|
if (id(page_index) == 0) {
|
|
id(page_one_gauge_view) = !id(page_one_gauge_view);
|
|
} else {
|
|
id(device_usage_view) = (id(device_usage_view) + 1) % 2;
|
|
}
|
|
- component.update: tft
|
|
- id: show_next_page
|
|
then:
|
|
- lambda: id(page_index) = (id(page_index) + 1) % 2;
|
|
- component.update: tft
|
|
|
|
button:
|
|
- platform: template
|
|
name: "Zurück"
|
|
icon: mdi:arrow-left
|
|
on_press:
|
|
- script.execute: show_previous_page
|
|
- platform: template
|
|
name: "Modus"
|
|
icon: mdi:view-dashboard-outline
|
|
on_press:
|
|
- script.execute: toggle_page_mode
|
|
- platform: template
|
|
name: "Weiter"
|
|
icon: mdi:arrow-right
|
|
on_press:
|
|
- script.execute: show_next_page
|
|
|
|
binary_sensor:
|
|
- platform: wireguard
|
|
status:
|
|
name: 'WireGuard Status'
|
|
- platform: wireguard
|
|
enabled:
|
|
name: 'WireGuard Enabled'
|
|
# M5Stack Core button B (middle) toggles the alternate gauge view on page one.
|
|
- platform: gpio
|
|
pin:
|
|
number: GPIO38
|
|
mode: INPUT
|
|
inverted: true
|
|
id: button_middle
|
|
on_press:
|
|
- script.execute: toggle_page_mode
|
|
|
|
# M5Stack Core button A (left). GPIO39 is input-only and has an external pull-up.
|
|
- platform: gpio
|
|
pin:
|
|
number: GPIO39
|
|
mode: INPUT
|
|
inverted: true
|
|
id: button_left
|
|
on_press:
|
|
- script.execute: show_previous_page
|
|
# M5Stack Core button C (right). GPIO37 is input-only and has an external pull-up.
|
|
- platform: gpio
|
|
pin:
|
|
number: GPIO37
|
|
mode: INPUT
|
|
inverted: true
|
|
id: button_right
|
|
on_press:
|
|
- script.execute: show_next_page
|
|
|
|
text_sensor:
|
|
- platform: wireguard
|
|
address:
|
|
name: 'WireGuard Address'
|
|
|
|
# M5CORE is the mipi_spi preset for the M5Stack Core's integrated display.
|
|
display:
|
|
- platform: mipi_spi
|
|
id: tft
|
|
model: M5CORE
|
|
cs_pin: GPIO14
|
|
dc_pin: GPIO27
|
|
reset_pin: GPIO33
|
|
invert_colors: true
|
|
update_interval: 1s
|
|
lambda: |-
|
|
it.fill(Color::BLACK);
|
|
|
|
if (id(sntp_time).now().is_valid()) {
|
|
it.strftime(234, 10, id(font_small), Color::WHITE, TextAlign::TOP_RIGHT,
|
|
"%H:%M", id(sntp_time).now());
|
|
} else {
|
|
it.printf(234, 10, id(font_small), Color::WHITE, TextAlign::TOP_RIGHT, "--:--");
|
|
}
|
|
|
|
// Native API connectivity; Home Assistant is normally the connected client.
|
|
const bool home_assistant_online = id(api_server).is_connected();
|
|
const Color home_assistant_color = home_assistant_online ? Color(64, 255, 64) : Color(255, 80, 80);
|
|
it.printf(260, 8, id(font_icon), home_assistant_color, TextAlign::TOP_RIGHT, "\U000F07D0");
|
|
|
|
// Green means the WireGuard peer is online; red means it is offline.
|
|
const bool wireguard_online = id(energymonitor_wireguard).is_peer_up();
|
|
const Color wireguard_color = wireguard_online ? Color(64, 255, 64) : Color(255, 80, 80);
|
|
it.printf(286, 8, id(font_icon), wireguard_color, TextAlign::TOP_RIGHT, "\U000F0306");
|
|
|
|
// Render the requested MDI Wi-Fi strength glyph in green or red when disconnected.
|
|
const bool wifi_online = id(wifi_connected);
|
|
const Color wifi_color = wifi_online ? Color(64, 255, 64) : Color(255, 80, 80);
|
|
const char *wifi_icon = "\U000F092D"; // mdi:wifi-strength-off
|
|
if (wifi_online) {
|
|
if (id(wifisignal).has_state()) {
|
|
const float rssi = id(wifisignal).state;
|
|
wifi_icon = rssi >= -55 ? "\U000F0928" : rssi >= -67 ? "\U000F0925"
|
|
: rssi >= -75 ? "\U000F0922" : "\U000F091F";
|
|
} else {
|
|
wifi_icon = "\U000F091F";
|
|
}
|
|
}
|
|
it.printf(312, 8, id(font_icon), wifi_color, TextAlign::TOP_RIGHT, "%s", wifi_icon);
|
|
|
|
if (id(page_index) == 0 && !id(page_one_gauge_view)) {
|
|
it.printf(12, 12, id(font_small), Color::WHITE, "Netzleistung");
|
|
if (id(grid_power).has_state()) {
|
|
const Color grid_power_color = id(grid_power).state >= 0
|
|
? Color(143, 190, 223) // #8FBEDF: positive grid power
|
|
: Color(150, 110, 217); // #966ED9: negative grid power
|
|
it.printf(308, 32, id(font_value), grid_power_color, TextAlign::TOP_RIGHT,
|
|
"%+.0f W", id(grid_power).state);
|
|
} else {
|
|
it.printf(308, 42, id(font_value), Color::WHITE, TextAlign::TOP_RIGHT,
|
|
"-- W");
|
|
}
|
|
|
|
|
|
if (id(grid_import_energy_daily).has_state()) {
|
|
it.printf(12, 96, id(font_small), Color::WHITE, "Bezug: %.2f kWh",
|
|
id(grid_import_energy_daily).state);
|
|
} else {
|
|
it.printf(12, 96, id(font_small), Color::WHITE, "Bezug: -- kWh");
|
|
}
|
|
if (id(grid_export_energy_daily).has_state()) {
|
|
it.printf(12, 116, id(font_small), Color::WHITE, "Einspeisung: %.2f kWh",
|
|
id(grid_export_energy_daily).state);
|
|
} else {
|
|
it.printf(12, 116, id(font_small), Color::WHITE, "Einspeisung: -- kWh");
|
|
}
|
|
|
|
it.printf(12, 138, id(font_small), Color::WHITE, "Balkonkraftwerk");
|
|
// Daily solar energy is aligned to the left of the current solar-power reading.
|
|
if (id(solar_energy_daily).has_state()) {
|
|
it.printf(12, 180, id(font_small), Color(255, 170, 40), "Solar: %.2f kWh",
|
|
id(solar_energy_daily).state);
|
|
} else {
|
|
it.printf(12, 180, id(font_small), Color(255, 170, 40), "Solar: -- kWh");
|
|
}
|
|
if (id(balcony_solar_power).has_state()) {
|
|
it.printf(308, 158, id(font_value), Color(255, 170, 40), TextAlign::TOP_RIGHT,
|
|
"%.0f W", id(balcony_solar_power).state);
|
|
} else {
|
|
it.printf(308, 168, id(font_value), Color(255, 170, 40), TextAlign::TOP_RIGHT,
|
|
"-- W");
|
|
}
|
|
} else if (id(page_index) == 0) {
|
|
// One shared gauge with two parallel curved bars: grid on the outside, solar inside.
|
|
const Color gauge_background = Color(42, 42, 42);
|
|
const int gauge_center_x = 160;
|
|
const int gauge_center_y = 188;
|
|
|
|
// The outer band represents the absolute grid-power value with its signed color.
|
|
float grid_power_value = id(grid_power).has_state() ? id(grid_power).state : 0.0f;
|
|
float grid_power_magnitude = grid_power_value < 0.0f ? -grid_power_value : grid_power_value;
|
|
int grid_gauge_percent = static_cast<int>(grid_power_magnitude * 100.0f / ${grid_power_gauge_max_w});
|
|
if (grid_gauge_percent > 100) grid_gauge_percent = 100;
|
|
const Color grid_gauge_color = grid_power_value >= 0.0f
|
|
? Color(143, 190, 223) // #8FBEDF
|
|
: Color(150, 110, 217); // #966ED9
|
|
it.filled_gauge(gauge_center_x, gauge_center_y, 140, 122, 100, gauge_background);
|
|
it.filled_gauge(gauge_center_x, gauge_center_y, 140, 122, grid_gauge_percent, grid_gauge_color);
|
|
|
|
// The inner band is solar power, normalized against the 800 W inverter maximum.
|
|
float solar_power_value = id(balcony_solar_power).has_state() ? id(balcony_solar_power).state : 0.0f;
|
|
int solar_gauge_percent = static_cast<int>(solar_power_value * 100.0f / ${balcony_solar_gauge_max_w});
|
|
if (solar_gauge_percent < 0) solar_gauge_percent = 0;
|
|
if (solar_gauge_percent > 100) solar_gauge_percent = 100;
|
|
const Color solar_gauge_color = Color(255, 170, 40); // #FFAA28
|
|
it.filled_gauge(gauge_center_x, gauge_center_y, 116, 98, 100, gauge_background);
|
|
it.filled_gauge(gauge_center_x, gauge_center_y, 116, 98, solar_gauge_percent, solar_gauge_color);
|
|
|
|
it.printf(gauge_center_x, 102, id(font_small), Color::WHITE, TextAlign::TOP_CENTER,
|
|
"Netzleistung");
|
|
if (id(grid_power).has_state()) {
|
|
it.printf(gauge_center_x, 120, id(font_title), grid_gauge_color, TextAlign::TOP_CENTER,
|
|
"%+.0f W", grid_power_value);
|
|
} else {
|
|
it.printf(gauge_center_x, 120, id(font_title), Color::WHITE, TextAlign::TOP_CENTER, "-- W");
|
|
}
|
|
it.printf(gauge_center_x, 151, id(font_small), Color::WHITE, TextAlign::TOP_CENTER,
|
|
"Balkonkraftwerk");
|
|
if (id(balcony_solar_power).has_state()) {
|
|
it.printf(gauge_center_x, 169, id(font_title), solar_gauge_color, TextAlign::TOP_CENTER,
|
|
"%.0f W", solar_power_value);
|
|
} else {
|
|
it.printf(gauge_center_x, 169, id(font_title), solar_gauge_color, TextAlign::TOP_CENTER, "-- W");
|
|
}
|
|
} else {
|
|
// Page 2 lists nine consumers at once in two columns; button B changes group.
|
|
const int device_count = 18;
|
|
const int usage_group = id(device_usage_view);
|
|
const int page_start = usage_group * 9;
|
|
const char *device_names[device_count] = {
|
|
"Raumduft", "Keller", "Anrichte", "Musik", "Bett", "HK Schlafzimmer",
|
|
"Deko", "Schreibtisch", "Serverraum", "HK Wohnzimmer", "Spieleschrank",
|
|
"Kühlschrank", "Kaffeemaschine", "Wäschetrockner", "Waschmaschine", "Arcade",
|
|
"Spülmaschine", "Mikrowelle"};
|
|
float device_values[device_count] = {
|
|
id(hallway_fragrance_power).state, id(cellar_power).state, id(sideboard_power).state,
|
|
id(kitchen_music_power).state, id(bedroom_bed_power).state, id(bedroom_cinema_power).state,
|
|
id(desk_decoration_power).state, id(desk_power).state, id(server_room_power).state,
|
|
id(living_room_cinema_power).state, id(games_cabinet_power).state, id(refrigerator_power).state,
|
|
id(coffee_machine_power).state, id(dryer_power).state, id(washing_machine_power).state,
|
|
id(arcade_power).state, id(dishwasher_power).state, id(microwave_power).state};
|
|
bool device_has_state[device_count] = {
|
|
id(hallway_fragrance_power).has_state(), id(cellar_power).has_state(), id(sideboard_power).has_state(),
|
|
id(kitchen_music_power).has_state(), id(bedroom_bed_power).has_state(), id(bedroom_cinema_power).has_state(),
|
|
id(desk_decoration_power).has_state(), id(desk_power).has_state(), id(server_room_power).has_state(),
|
|
id(living_room_cinema_power).has_state(), id(games_cabinet_power).has_state(), id(refrigerator_power).has_state(),
|
|
id(coffee_machine_power).has_state(), id(dryer_power).has_state(), id(washing_machine_power).has_state(),
|
|
id(arcade_power).has_state(), id(dishwasher_power).has_state(), id(microwave_power).has_state()};
|
|
Color device_colors[device_count] = {
|
|
Color(255, 170, 40), Color(69, 189, 255), Color(170, 116, 217), Color(93, 207, 140),
|
|
Color(238, 112, 112), Color(107, 139, 255), Color(255, 130, 180), Color(122, 204, 174),
|
|
Color(240, 195, 90), Color(151, 120, 220), Color(91, 189, 193), Color(212, 132, 82),
|
|
Color(143, 190, 223), Color(233, 106, 142), Color(132, 205, 92), Color(255, 181, 80),
|
|
Color(111, 167, 229), Color(190, 137, 226)};
|
|
|
|
// The title uses the free area to the left of the status indicators.
|
|
it.printf(12, 10, id(font_device), Color::WHITE, "Geräteverbrauch");
|
|
for (int display_index = 0; display_index < 9; display_index++) {
|
|
const int index = page_start + display_index;
|
|
const int column = display_index % 2;
|
|
const int row = display_index / 2;
|
|
const int x = column == 0 ? 12 : 166;
|
|
const int y = 43 + row * 25;
|
|
if (device_has_state[index]) {
|
|
it.printf(x, y, id(font_device), device_colors[index], "%s: %.0f W",
|
|
device_names[index], device_values[index]);
|
|
} else {
|
|
it.printf(x, y, id(font_device), device_colors[index], "%s: -- W", device_names[index]);
|
|
}
|
|
}
|
|
|
|
// The stacked bar always represents all 18 consumer devices, not only this view's rows.
|
|
const int bar_x = 12;
|
|
const int bar_y = 180;
|
|
const int bar_width = 296;
|
|
const int bar_height = 18;
|
|
float usage_sum = 0.0f;
|
|
int last_active = -1;
|
|
for (int index = 0; index < device_count; index++) {
|
|
if (device_has_state[index] && device_values[index] > 0.0f) {
|
|
usage_sum += device_values[index];
|
|
last_active = index;
|
|
}
|
|
}
|
|
it.filled_rectangle(bar_x, bar_y, bar_width, bar_height, Color(42, 42, 42));
|
|
if (usage_sum > 0.0f) {
|
|
int cursor_x = bar_x;
|
|
for (int index = 0; index < device_count; index++) {
|
|
if (!device_has_state[index] || device_values[index] <= 0.0f) continue;
|
|
const int segment_width = index == last_active
|
|
? bar_x + bar_width - cursor_x
|
|
: static_cast<int>(device_values[index] * bar_width / usage_sum);
|
|
if (segment_width > 0) {
|
|
it.filled_rectangle(cursor_x, bar_y, segment_width, bar_height, device_colors[index]);
|
|
cursor_x += segment_width;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Labels correspond to the left (A) and right (C) hardware buttons.
|
|
it.printf(8, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_LEFT,
|
|
"< Zurück");
|
|
it.printf(312, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_RIGHT,
|
|
"Weiter >");
|
|
if (id(page_index) == 0) {
|
|
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
|
"Ansicht · 1 / 2");
|
|
} else {
|
|
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
|
"Geräte %d / 2", id(device_usage_view) + 1);
|
|
}
|