additional esphome devices and fixes

This commit is contained in:
root
2026-05-23 23:53:04 +12:00
parent 5e941eea06
commit 7eb81efd93
61 changed files with 7756 additions and 354 deletions
+818
View File
@@ -0,0 +1,818 @@
#:########################################################################################:#
# TITLE: LED MATRIX 1
# zorruno.com layout v1.1 2026
# ESP32-C3 SuperMini MAX7219 LED matrix clock and scroll display.
# Original Project Reference https://github.com/RealDeco/matrixclock-esphome
#:########################################################################################:#
# REPO:
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-ledmatrix1.yaml
#:########################################################################################:#
# VERSIONS:
# V1.1 2026-05-23 Updated yaml to zorruno layout V1.1, cleaned comments, fixed SNTP time IDs,
# added current_mins fallback display, removed unused MQTT topic, and fixed
# blank scroll handling.
# V1.0 2026-05-23 Initial Version
#:########################################################################################:#
# HARDWARE:
# - ESP32-C3 SuperMini.
# - MAX7219 4-chip LED matrix display, typically 32 x 8 pixels.
# - SPI wiring configured below:
# - MOSI: GPIO8
# - CS: GPIO9
# - SCK: GPIO10
# - Power note: 4 MAX7219 modules may need a suitable external 5 V supply.
# - Keep ESP32-C3 GND and matrix power supply GND connected together.
#:########################################################################################:#
# FONTS:
# Dragon Font: https://www.freebestfonts.com/eight-bit-dragon-font
# 5x8 Font: https://developer.lasec.com.mx/armando/rpi-rgb-led-matrix/-/blob/master/fonts/5x8.bdf
#:########################################################################################:#
# OPERATION NOTES:
# - Displays the current time from common/sntp_common.yaml using id(sntp_time).
# - If SNTP is invalid, the display falls back to id(current_mins) from common/sntp_common.yaml.
# - 12h/24h display mode is selectable from Home Assistant or MQTT.
# - Display brightness is exposed as a Home Assistant light entity.
# - Brightness accepts values 0..15 where 0 turns the display off and 1..15 sets intensity.
# - Scroll Text is exposed as a Home Assistant text entity and clears itself after use.
# - Before any scroll starts, the clock slides down and off the display.
# - After scroll ends, the selected re-entry effect runs: Slide up, Slide down, or Pixel fall.
# - Pixel fall hides the clock until the effect finishes, avoiding a clock-under-particles look.
# - Scroll speed is adjustable using the Scroll Delay number entity.
#:########################################################################################:#
# MQTT COMMANDS:
# - ${mqtt_command_topic}/scroll : Text payload to scroll once across the display.
# - ${mqtt_command_topic}/mode : Payload 12 or 24 to select clock format.
# - ${mqtt_command_topic}/bright : Payload 0 turns display off, 1-15 sets brightness.
# - ${mqtt_status_topic}/status : Publishes online/offline status.
#:########################################################################################:#
# OFFLINE NOTES:
# a) Home Assistant offline, but WiFi/network and MQTT online:
# - Clock display should continue using SNTP if the NTP servers are reachable.
# - MQTT scroll, brightness, and 12h/24h mode commands should still work.
#
# b) MQTT offline, but WiFi/network and Home Assistant API online:
# - Home Assistant text, light, number, and select entities should still work.
# - Clock display should continue using SNTP if the NTP servers are reachable.
#
# c) Entire WiFi/network offline:
# - SNTP will not be available after boot.
# - Clock display will use the current_mins fallback from common/sntp_common.yaml.
# - Accurate time is needed for the clock, so fallback time will drift.
# - For a rough reset, power-cycle the device at 12:00 noon.
# - Home Assistant and MQTT controls will not be available while offline.
#:########################################################################################:#
#:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc in quotes.
#:########################################################################################:#
substitutions:
# Device Naming
device_name: "esp-ledmatrix1"
friendly_name: "LED Matrix Display"
description_comment: "LED Matrix Display (Layout V1.1)"
device_area: "Lounge" # Allows ESP device to be automatically linked to an Area in Home Assistant.
# Project Naming
project_name: "Generic.ESP32 Supermini"
project_version: "v1.1"
# Passwords & Secrets
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-ledmatrix1_ip # Substitutions cannot be used inside secret names.
mqtt_command_main_topic: !secret mqtt_command_main_topic
mqtt_status_main_topic: !secret mqtt_status_main_topic
# If changing IP addresses, update current_ip_address here, otherwise it remains static_ip_address.
# Do not forget to switch it back when the address change is complete.
current_ip_address: ${static_ip_address}
# MQTT LOCAL Controls
mqtt_device_name: "ledmatrix1"
mqtt_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}"
mqtt_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}"
# Device Settings
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, VERY_VERBOSE
update_interval: "60s"
# Matrix Display Settings
pin_mosi: GPIO8
pin_cs: GPIO9
pin_sck: GPIO10
num_chips: "4"
scroll_delay_ms: "15" # ms between scroll steps
y_offset: "0"
y_scroll_offset: "0"
#:########################################################################################:#
# PACKAGES: Included Common Packages
# https://esphome.io/components/packages.html
#:########################################################################################:#
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
common_wifi: !include
file: common/network_common.yaml
vars:
local_device_name: "${device_name}"
local_static_ip_address: "${static_ip_address}"
local_ota_pass: "${ota_pass}"
local_current_ip_address: "${current_ip_address}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#### MQTT ####
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
#### WEB PORTAL ####
common_webportal: !include common/webportal_common.yaml
#### SNTP (Needed for the clock display) ####
common_sntp: !include common/sntp_common.yaml
#### DIAGNOSTICS Sensors ####
diag_basic: !include common/include_basic_diag_sensors.yaml
#diag_more: !include common/include_more_diag_sensors.yaml
#diag_debug: !include common/include_debug_diag_sensors.yaml
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
#:########################################################################################:#
# ESPHOME:
# https://esphome.io/components/esphome.html
#:########################################################################################:#
esphome:
name: "${device_name}"
friendly_name: "${friendly_name}"
comment: "${description_comment}" # Appears on the ESPHome page in Home Assistant.
area: "${device_area}"
project:
name: "${project_name}"
version: "${project_version}"
on_boot:
priority: 600
then:
# Ensure Home Assistant shows the text box as empty rather than unknown.
- delay: 500ms
- lambda: |-
id(ha_scroll_text).publish_state("");
- component.update: matrix
#:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK:
# https://esphome.io/components/esp32/
#:########################################################################################:#
esp32:
variant: esp32c3
framework:
type: esp-idf
version: recommended
#:########################################################################################:#
# LOGGER: ESPHome Logging Enable
# https://esphome.io/components/logger.html
#:########################################################################################:#
logger:
level: "${log_level}" # INFO suggested, or DEBUG for testing.
#baud_rate: 0 # Set to 0 for no UART logging if UART is used for another device.
#esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64
#:########################################################################################:#
# GLOBALS:
# https://esphome.io/components/globals.html
#:########################################################################################:#
globals:
# Text currently being scrolled across the matrix.
- id: scroll_text
type: std::string
initial_value: ""
# Current X position of the scrolling text.
- id: scroll_x
type: int
initial_value: '0'
# True while a scroll message is active.
- id: scrolling
type: bool
initial_value: 'false'
# False = 24h mode, true = 12h mode.
- id: use_12h_mode
type: bool
restore_value: true
initial_value: 'false'
# Runtime scroll delay, controlled by the Scroll Delay number entity.
- id: scroll_delay_runtime
type: int
restore_value: true
initial_value: '${scroll_delay_ms}'
# MAX7219 intensity: 0=OFF, 1..15=ON. Driven by the HA light entity.
- id: matrix_intensity
type: int
restore_value: false
initial_value: '2'
# Brightness sync loop protection for HA light <-> MQTT brightness updates.
- id: suppress_mqtt_bright_sync
type: bool
restore_value: false
initial_value: 'false'
- id: target_mqtt_bright
type: int
restore_value: false
initial_value: '-1'
- id: last_mqtt_bright
type: int
restore_value: false
initial_value: '-1'
# Re-entry effect after scrolling: 0=Slide up, 1=Slide down, 2=Pixel fall.
- id: transition_mode
type: int
restore_value: true
initial_value: '0'
# Slide transition state.
- id: transition_active
type: bool
restore_value: false
initial_value: 'false'
- id: transition_y
type: int
restore_value: false
initial_value: '0'
# Pixel-fall transition state.
- id: pixel_fall_active
type: bool
restore_value: false
initial_value: 'false'
- id: pixel_fall_gen
type: int
restore_value: false
initial_value: '0'
#:########################################################################################:#
# FONT COMPONENT:
# https://esphome.io/components/font/
#:########################################################################################:#
font:
# Clock digits. Keep glyphs limited to reduce firmware size.
- file: "fonts/Eight-Bit-Dragon.ttf"
id: digit5
size: 8
glyphs: ["0123456789: -"]
# General 5x8 font for scroll text.
- file: "fonts/5x8.bdf"
id: font5x8
size: 8
#:########################################################################################:#
# SPI COMPONENT:
# https://esphome.io/components/spi.html
#:########################################################################################:#
spi:
clk_pin: ${pin_sck}
mosi_pin: ${pin_mosi}
#:########################################################################################:#
# MQTT: Device-specific MQTT command triggers
# This adds device-specific MQTT command triggers to the common MQTT configuration.
#:########################################################################################:#
mqtt:
discovery: false
birth_message:
topic: "${mqtt_status_topic}/status"
payload: "online"
qos: 0
retain: true
will_message:
topic: "${mqtt_status_topic}/status"
payload: "offline"
qos: 0
retain: true
on_message:
# Scroll a text payload once across the matrix.
- topic: "${mqtt_command_topic}/scroll"
then:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20; }), s.end());
// Ignore empty MQTT scroll commands completely.
if (s.empty()) return;
id(scroll_text) = s;
id(scroll_x) = ${num_chips} * 8;
id(start_scroll_sequence).execute();
# Set 12h or 24h clock mode from MQTT.
- topic: "${mqtt_command_topic}/mode"
then:
- lambda: |-
if (x == "12") {
id(use_12h_mode) = true;
} else if (x == "24") {
id(use_12h_mode) = false;
}
- component.update: clock_format_select
- component.update: matrix
# Brightness from MQTT. 0=OFF, 1..15=ON brightness level.
- topic: "${mqtt_command_topic}/bright"
then:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20 || c == 0x7F; }), s.end());
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
[](unsigned char ch){ return !std::isspace(ch); }));
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char ch){ return !std::isspace(ch); }).base(), s.end());
if (s.empty() || !std::all_of(s.begin(), s.end(),
[](unsigned char c){ return std::isdigit(c); })) return;
int v = atoi(s.c_str());
if (v < 0) v = 0;
if (v > 15) v = 15;
id(suppress_mqtt_bright_sync) = true;
if (v == 0) {
id(matrix_light).turn_off().perform();
} else {
auto call = id(matrix_light).turn_on();
call.set_brightness((float) v / 15.0f);
call.perform();
}
id(last_mqtt_bright) = v;
id(suppress_mqtt_bright_sync) = false;
#:########################################################################################:#
# TEXT COMPONENT:
# https://esphome.io/components/text/template/
#:########################################################################################:#
text:
- platform: template
id: ha_scroll_text
name: "${friendly_name} Scroll Text"
icon: "mdi:message-text-outline"
mode: text
max_length: 80
optimistic: false
set_action:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20; }), s.end());
// Publish the cleaned text so the HA UI updates immediately.
id(ha_scroll_text).publish_state(s.c_str());
// Empty text only clears the entity. It should not start a scroll.
if (s.empty()) return;
id(scroll_text) = s;
id(scroll_x) = ${num_chips} * 8;
id(start_scroll_sequence).execute();
id(clear_scroll_text_box).execute();
#:########################################################################################:#
# NUMBER COMPONENT:
# https://esphome.io/components/number/template/
#:########################################################################################:#
number:
- platform: template
id: scroll_delay_number
name: "${friendly_name} Scroll Delay"
icon: "mdi:timer-outline"
unit_of_measurement: "ms"
min_value: 1
max_value: 100
step: 1
restore_value: true
initial_value: ${scroll_delay_ms}
optimistic: true
set_action:
- lambda: |-
id(scroll_delay_runtime) = (int) x;
#:########################################################################################:#
# OUTPUT COMPONENT:
# https://esphome.io/components/output/
#:########################################################################################:#
output:
# Template output lets the HA light slider control MAX7219 intensity.
- platform: template
id: matrix_brightness_out
type: float
write_action:
- lambda: |-
int pub = 0;
if (state <= 0.001f) {
id(matrix_intensity) = 0;
id(matrix).turn_on_off(false);
pub = 0;
} else {
int v = (int) lroundf(state * 15.0f);
if (v < 1) v = 1;
if (v > 15) v = 15;
id(matrix_intensity) = v;
id(matrix).turn_on_off(true);
pub = v;
}
id(target_mqtt_bright) = pub;
- component.update: matrix
# Publish brightness back to MQTT unless this update came from MQTT.
- if:
condition:
lambda: |-
return !id(suppress_mqtt_bright_sync)
&& (id(target_mqtt_bright) != id(last_mqtt_bright));
then:
- mqtt.publish:
topic: "${mqtt_command_topic}/bright"
payload: !lambda |-
char buf[6];
snprintf(buf, sizeof(buf), "%d", id(target_mqtt_bright));
return std::string(buf);
qos: 0
retain: true
- lambda: |-
id(last_mqtt_bright) = id(target_mqtt_bright);
#:########################################################################################:#
# LIGHT COMPONENT:
# https://esphome.io/components/light/monochromatic/
#:########################################################################################:#
light:
- platform: monochromatic
id: matrix_light
name: "${friendly_name} Display"
icon: "mdi:brightness-6"
output: matrix_brightness_out
gamma_correct: 1.0
default_transition_length: 0s
restore_mode: RESTORE_DEFAULT_ON
#:########################################################################################:#
# SELECT COMPONENT:
# https://esphome.io/components/select/template/
#:########################################################################################:#
select:
- platform: template
id: clock_format_select
name: "${friendly_name} Clock Format"
icon: "mdi:clock-time-three-outline"
options:
- "24"
- "12"
lambda: |-
return esphome::optional<std::string>(std::string(id(use_12h_mode) ? "12" : "24"));
set_action:
- lambda: |-
id(use_12h_mode) = (x == "12");
- component.update: clock_format_select
- component.update: matrix
- platform: template
id: transition_select
name: "${friendly_name} Transition Effect"
icon: "mdi:transition"
options:
- "Slide up"
- "Slide down"
- "Pixel fall"
lambda: |-
if (id(transition_mode) == 2) return std::string("Pixel fall");
if (id(transition_mode) == 1) return std::string("Slide down");
return std::string("Slide up");
set_action:
- lambda: |-
if (x == "Pixel fall") id(transition_mode) = 2;
else if (x == "Slide down") id(transition_mode) = 1;
else id(transition_mode) = 0;
- component.update: transition_select
#:########################################################################################:#
# SCRIPT COMPONENT:
# https://esphome.io/components/script.html
#:########################################################################################:#
script:
# Clear the HA text box shortly after a scroll request has been accepted.
- id: clear_scroll_text_box
mode: restart
then:
- delay: 400ms
- lambda: |-
id(ha_scroll_text).publish_state("");
# Sequence: exit slide-down -> scroll -> selected re-entry.
- id: start_scroll_sequence
mode: restart
then:
- lambda: |-
id(scrolling) = false;
id(pixel_fall_active) = false;
- component.update: matrix
- script.execute: clock_exit_down
- script.wait: clock_exit_down
- lambda: |-
id(scrolling) = true;
- script.execute: do_scroll
# Scroll the stored text until it has moved fully off the left side.
- id: do_scroll
mode: restart
then:
- while:
condition:
lambda: |-
return id(scrolling);
then:
- lambda: |-
id(scroll_x) -= 1;
const int end_limit = - (int(id(scroll_text).length()) * 6);
if (id(scroll_x) < end_limit) {
id(scrolling) = false;
if (id(transition_mode) == 1) {
id(clock_slide_down_entry).execute();
} else if (id(transition_mode) == 2) {
id(clock_pixel_fall_transition).execute();
} else {
id(clock_slide_up_entry).execute();
}
}
- component.update: matrix
- delay: !lambda |-
return (uint32_t) id(scroll_delay_runtime);
# Exit: slide clock down off-screen and hold it there to prevent a one-frame flash.
- id: clock_exit_down
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = ${y_offset};
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) += 1;
- component.update: matrix
- delay: 45ms
- lambda: |-
id(transition_active) = true;
id(transition_y) = 8;
- component.update: matrix
# Re-entry: clock slides down into place from above.
- id: clock_slide_down_entry
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = -8;
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) += 1;
- component.update: matrix
- delay: 60ms
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
# Re-entry: clock slides up into place from below.
- id: clock_slide_up_entry
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = 8;
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) -= 1;
- component.update: matrix
- delay: 60ms
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
# Re-entry: falling pixels effect before the clock redraws.
- id: clock_pixel_fall_transition
mode: restart
then:
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
id(pixel_fall_active) = true;
id(pixel_fall_gen) += 1;
- repeat:
count: 18
then:
- component.update: matrix
- delay: 55ms
- lambda: |-
id(pixel_fall_active) = false;
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
#:########################################################################################:#
# DISPLAY COMPONENT:
# https://esphome.io/components/display/max7219digit/
#:########################################################################################:#
display:
- platform: max7219digit
id: matrix
cs_pin: ${pin_cs}
num_chips: ${num_chips}
intensity: 2
update_interval: 500ms
rotate_chip: 0
reverse_enable: false
flip_x: false
lambda: |-
// Apply brightness only when it changes to reduce flicker.
static int last_brightness = -1;
if (id(matrix_intensity) != last_brightness) {
if (id(matrix_intensity) > 0) {
int v = id(matrix_intensity);
if (v > 15) v = 15;
it.intensity((uint8_t) v);
}
last_brightness = id(matrix_intensity);
}
if (id(matrix_intensity) == 0) return;
it.clear();
// Scroll text has priority over clock display.
if (id(scrolling)) {
it.print(id(scroll_x), ${y_scroll_offset}, id(font5x8), id(scroll_text).c_str());
return;
}
// Pixel fall shows only particles while active.
if (id(pixel_fall_active)) {
static int last_gen = -1;
static int px[24];
static int py[24];
static uint32_t rng = 1;
auto rand_u32 = [&]() -> uint32_t {
rng = rng * 1664525u + 1013904223u;
return rng;
};
if (last_gen != id(pixel_fall_gen)) {
last_gen = id(pixel_fall_gen);
rng = 0xA5A5A5A5u ^ (uint32_t) id(pixel_fall_gen);
for (int i = 0; i < 24; i++) {
px[i] = (int)(rand_u32() % 32);
py[i] = - (int)(rand_u32() % 16);
}
}
for (int i = 0; i < 24; i++) {
int spd = 1 + (int)(rand_u32() % 2);
py[i] += spd;
if (py[i] > 10) {
px[i] = (int)(rand_u32() % 32);
py[i] = - (int)(rand_u32() % 10);
}
for (int t = 0; t < 2; t++) {
int yy = py[i] - t;
int xx = px[i];
if (xx >= 0 && xx <= 31 && yy >= 0 && yy <= 7) {
it.draw_pixel_at(xx, yy, Color::WHITE);
}
}
}
return;
}
// Prefer real SNTP time. Fall back to current_mins from common/sntp_common.yaml.
auto now = id(sntp_time).now();
bool time_valid = now.is_valid();
int raw_hour = 0;
int minute_to_show = 0;
int second_to_show = 0;
if (time_valid) {
raw_hour = now.hour;
minute_to_show = now.minute;
second_to_show = now.second;
} else {
int mins = id(current_mins);
if (mins < 0) mins = 0;
if (mins >= 1440) mins = mins % 1440;
raw_hour = mins / 60;
minute_to_show = mins % 60;
second_to_show = 0;
}
int hour_to_show = raw_hour;
if (id(use_12h_mode)) {
hour_to_show = raw_hour % 12;
if (hour_to_show == 0) hour_to_show = 12;
}
char hh[3], mm[3];
snprintf(hh, sizeof(hh), "%02d", hour_to_show);
snprintf(mm, sizeof(mm), "%02d", minute_to_show);
const int x_h1 = 3;
const int x_h2 = 9;
const int x_m1 = 17;
const int x_m2 = 23;
const int y = (id(transition_active) ? id(transition_y) : ${y_offset});
char ch[2] = { hh[0], 0 };
it.print(x_h1, y, id(digit5), ch);
ch[0] = hh[1];
it.print(x_h2, y, id(digit5), ch);
ch[0] = mm[0];
it.print(x_m1, y, id(digit5), ch);
ch[0] = mm[1];
it.print(x_m2, y, id(digit5), ch);
// In 12h mode, draw a tiny A or P indicator at the far right.
if (id(use_12h_mode)) {
const bool is_pm = raw_hour >= 12;
const uint8_t A_rows[4] = { 2, 5, 7, 5 };
const uint8_t P_rows[4] = { 6, 5, 6, 4 };
const uint8_t *L = is_pm ? P_rows : A_rows;
const int x_ampm = 29;
const int y_ampm = y + 4;
for (int r = 0; r < 4; r++) {
uint8_t row = L[r];
for (int c = 0; c < 3; c++) {
if (row & (1 << (2 - c))) {
int xx = x_ampm + c;
int yy = y_ampm + r;
if (yy >= 0 && yy <= 7 && xx >= 0 && xx <= 31) {
it.draw_pixel_at(xx, yy, Color::WHITE);
}
}
}
}
}
// Blink the colon when SNTP is valid. Keep it solid during fallback time.
const bool colon_on = time_valid ? ((second_to_show % 2) == 0) : true;
if (colon_on) {
const int x_col = (x_h2 + x_m1) / 2 + 2;
int y1 = y + 2;
int y2 = y + 5;
if (y1 >= 0 && y1 <= 7) it.draw_pixel_at(x_col, y1, Color::WHITE);
if (y2 >= 0 && y2 <= 7) it.draw_pixel_at(x_col, y2, Color::WHITE);
}