[9 nov 2025] De action heeft een LED paneel (ze noemen het een LED pixel board, van het merk B.K. light, 3217439). Het pixel paneeltje lijkt als twee druppels water op een hub75 led matrix paneel. Ik heb van BlackDragon van Revspace begrepen dat de binennkant niks te hacken zit, dus ik heb de binennkant de binnenkant gelaten, en een diffusor in de vorm van een paars-transparant panneel gelasercut bij Hackalot. Deze heb ik vastgezet met wat UV epoxy op de hoekjes. [update! 🙂 ]
In de toekomst wil ik dit aansturen met home assistant, al lijkt er nog geen integratie op dit moment.



[13 nov 2025] So, there is an library! Yay! Thank you BlackDragon and Revspace! It is a slow library, good for pixelflut and banners, but not much more. Which is great for me 🙂 I want it to display the time, and have an icon if the trash will be collected the next day, green for green trash, blue for paper, and, I don’t know white for general trash? I will see. I also apparently switched to English. It is a day like that. To be continued!
[14 nov 2025] I tried the ESPhome variant of the library. I made some changes to get it to work on my machine. I have to look later if I did the right things, so I can make a pull request. But I have it working from home assistant. I’m so happy!
esp32_ble_tracker:
ble_client:
- id: ble_matrix
mac_address: 8C:6B:58:DC:1B:C2
esphome:
name: ipixel-esphome
friendly_name: ipixel-ESPHome
esp32:
board: esp32thing
framework:
type: esp-idf
logger:
captive_portal:
api:
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: Ipixel-Esphome Fallback Hotspot
password: !secret fallback_ap_password
number:
# Brightness
- platform: template
name: Brightness
id: brightness
icon: mdi:brightness-6
optimistic: true
min_value: 0
max_value: 100
initial_value: 100
step: 1
on_value:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
uint8_t b = (uint8_t) round(x);
std::vector<uint8_t> command = {0x05, 0x00, 0x04, 0x80, b};
return command;
button:
# Clear
- platform: template
name: Clear EEPROM
icon: mdi:delete-sweep
id: clear
on_press:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
std::vector<uint8_t> command = {0x04, 0x00, 0x03, 0x80};
return command;
- select.set:
id: clock_style
option: Disabled
- switch.turn_on: power_on
- number.set:
id: brightness
value: 100
- platform: template
name: Refresh Clock
id: refresh_clock
internal: true
on_press:
- if:
condition:
lambda: return id(clock_style).state != "Disabled" && id(clock_style).state
!= "Disabled";
then:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
// Build and send the set_clock command
std::vector<uint8_t> command = {0x0b, 0x00, 0x06, 0x01};
// Parse style from label "Style N" and send N-1 to device
std::string current_style = id(clock_style).state;
uint8_t style = 0;
size_t pos = current_style.find(' ');
if (pos != std::string::npos) {
int n = atoi(current_style.substr(pos + 1).c_str());
// Convert label numbering (1..9) to device 0-based index (0..8)
style = (uint8_t)((n > 0) ? n : 0);
}
command.push_back(style);
bool format24 = (id(use_24h).state);
command.push_back(format24 ? 0x01 : 0x00);
bool showdate = (id(show_date).state);
command.push_back(showdate ? 0x01 : 0x00);
uint8_t year, month, day, dow;
// time_set_mode switch: ON = Manuel, OFF = Automatique
if (!id(auto_timeset).state) {
// Parse date from DD/MM/YY format
std::string date_str = id(manual_date).state;
day = atoi(date_str.substr(0, 2).c_str());
month = atoi(date_str.substr(3, 2).c_str());
year = atoi(date_str.substr(6, 2).c_str());
// Map weekday text to number (1=Monday, 7=Sunday)
std::string weekday_str = id(weekday).state;
if (weekday_str == "Monday") dow = 1;
else if (weekday_str == "Tuesday") dow = 2;
else if (weekday_str == "Wednesday") dow = 3;
else if (weekday_str == "Thursday") dow = 4;
else if (weekday_str == "Friday") dow = 5;
else if (weekday_str == "Saturday") dow = 6;
else if (weekday_str == "Sunday") dow = 7;
else dow = 1; // default
} else {
// Automatic: get current date from Home Assistant time
auto time = id(homeassistant_time).now();
year = time.year % 100; // Get last 2 digits
month = time.month;
day = time.day_of_month;
dow = time.day_of_week; // 1=Sunday in ESPHome, need to adjust
dow = (dow == 1) ? 7 : dow - 1; // Convert: 1=Sun->7, 2=Mon->1, etc.
}
command.push_back(year);
command.push_back(month);
command.push_back(day);
command.push_back(dow);
return command;
- button.press:
id: set_time_button
- platform: template
name: Set Time
id: set_time_button
internal: true
on_press:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
uint8_t hour, minute, second;
// time_set_mode switch: ON = Manuel, OFF = Automatique
if (!id(auto_timeset).state) {
// Manuel: parse time from HH:MM format
std::string time_str = id(manual_time).state;
hour = atoi(time_str.substr(0, 2).c_str());
minute = atoi(time_str.substr(3, 2).c_str());
second = 0; // Default to 0 for manual input
} else {
// Automatic: get current time from Home Assistant time
auto time = id(homeassistant_time).now();
hour = time.hour;
minute = time.minute;
second = time.second;
}
// Validate ranges
if (hour > 23) hour = 23;
if (minute > 59) minute = 59;
if (second > 59) second = 59;
// Build command: 0x08 0x00 0x01 0x80 hour minute second 0x00
std::vector<uint8_t> command = {0x08, 0x00, 0x01, 0x80, hour, minute, second, 0x00};
return command;
- platform: template
name: Download and Send PNG
id: send_png
icon: mdi:download
on_press:
- http_request.get:
url: !lambda 'return id(png_url).state;'
capture_response: true
max_response_buffer_size: 65536
on_response:
- lambda: |-
// Function to switch endian
auto switch_endian = [](std::string hex_string) -> std::string {
if (hex_string.length() % 2 != 0) return hex_string;
std::vector<std::string> pairs;
for (size_t i = 0; i < hex_string.length(); i += 2) {
pairs.push_back(hex_string.substr(i, 2));
}
std::reverse(pairs.begin(), pairs.end());
std::string result;
for (auto& p : pairs) result += p;
return result;
};
// Function to compute CRC32
auto crc32 = [](const uint8_t* data, size_t length) -> uint32_t {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < length; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
return ~crc;
};
// Retrieve the body of the HTTP response (PNG bytes)
if (response->status_code != 200) {
return; // Error if not 200 OK
}
std::string png_body = body;
std::vector<uint8_t> png_bytes(png_body.begin(), png_body.end());
if (png_bytes.empty()) {
return;
}
// Convert to hex string (if necessary for the logic)
std::string png_hex;
for (uint8_t byte : png_bytes) {
char hex[3];
sprintf(hex, "%02x", byte);
png_hex += hex;
}
// Apply send_png logic
// Calculate size = png_bytes.size() in hex 8 chars
uint32_t png_size = png_bytes.size();
char size_str[9];
sprintf(size_str, "%08x", png_size);
std::string size_hex(size_str);
size_hex = switch_endian(size_hex);
// Calculate checksum CRC32 of png_bytes
uint32_t crc = crc32(png_bytes.data(), png_bytes.size());
char checksum_str[9];
sprintf(checksum_str, "%08x", crc);
std::string checksum_hex(checksum_str);
checksum_hex = switch_endian(checksum_hex);
// Build inner = "FFFF020000" + size_hex + checksum_hex + "0065" + png_hex
std::string inner = "FFFF020000" + size_hex + checksum_hex + "0065" + png_hex;
// Convert inner to bytes for length
std::vector<uint8_t> inner_bytes;
for (size_t i = 0; i < inner.length(); i += 2) {
if (i + 1 >= inner.length()) break;
std::string byteString = inner.substr(i, 2);
char* end;
uint8_t byte = strtol(byteString.c_str(), &end, 16);
if (*end != '\0') continue;
inner_bytes.push_back(byte);
}
uint32_t total_size = inner_bytes.size();
char total_size_str[5];
sprintf(total_size_str, "%04x", total_size);
std::string header_size(total_size_str);
header_size = switch_endian(header_size);
// Final command = header_size + "020000" + size_hex + checksum_hex + "0065" + png_hex
std::string command_hex = header_size + "020000" + size_hex + checksum_hex + "0065" + png_hex;
// Convert to bytes
std::vector<uint8_t> command;
for (size_t i = 0; i < command_hex.length(); i += 2) {
if (i + 1 >= command_hex.length()) break;
std::string byteString = command_hex.substr(i, 2);
char* end;
uint8_t byte = strtol(byteString.c_str(), &end, 16);
if (*end != '\0') continue;
command.push_back(byte);
}
// Store the command in the global variable
id(png_command) = command;
// Trigger BLE sending
id(send_png_ble).execute();
- select.set:
id: clock_style
option: Disabled
time:
- platform: homeassistant
id: homeassistant_time
############################
select:
# Clock Style
- platform: template
name: Clock Style
icon: mdi:clock-digital
id: clock_style
optimistic: true
options:
- Disabled
- Style 1
- Style 2
- Style 3
- Style 4
- Style 5
- Style 6
- Style 7
- Style 8
on_value:
# - if:
# condition:
# lambda: 'return x == "Disabled";'
# then:
# - button.press:
# id: clear
# Action to do when clock style is changed
# Do not clear as it creates looping issues
- if:
condition:
lambda: return !(x == "Disabled");
then:
- switch.turn_on:
id: power_on
- button.press:
id: refresh_clock
# Manual Weekday
- platform: template
id: weekday
name: Weekday
icon: mdi:calendar-week
disabled_by_default: true
optimistic: true
options:
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
- Sunday
on_value:
- button.press:
id: refresh_clock
- platform: template
name: Orientation
id: orientation
optimistic: true
restore_value: true
options:
- 0°
- 90°
- 180°
- 270°
on_value:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
std::vector<uint8_t> command = {0x05, 0x00, 0x06, 0x80};
if (x == "0°") {
command.push_back(0);
} else if (x == "90°") {
command.push_back(1);
} else if (x == "180°") {
command.push_back(2);
} else if (x == "270°") {
command.push_back(3);
}
return command;
text:
# Manual Date Input
- platform: template
id: manual_date
name: Date (DD/MM/YY)
icon: mdi:calendar-edit
disabled_by_default: true
optimistic: true
mode: text
initial_value: 01/01/25
on_value:
- button.press:
id: refresh_clock
# Manual Time Input
- platform: template
id: manual_time
name: Time (HH:MM)
icon: mdi:clock-in
disabled_by_default: true
optimistic: true
mode: text
initial_value: 00:00
on_value:
- button.press:
id: refresh_clock
- platform: template
name: PNG URL
id: png_url
mode: text
icon: mdi:web
optimistic: true
initial_value: https://i.ibb.co/GvSV4j12/test.png # Example URL
switch:
# Clock 24h Format
- platform: template
id: use_24h
name: Use 24h Format
icon: mdi:hours-24
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- button.press:
id: refresh_clock
on_turn_off:
- button.press:
id: refresh_clock
# Show Date
- platform: template
id: show_date
name: Show Date
icon: mdi:calendar-clock
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- button.press:
id: refresh_clock
on_turn_off:
- button.press:
id: refresh_clock
# Automatic / Manual Time Set
- platform: template
id: auto_timeset
name: Auto Date/Time set
icon: mdi:calendar-sync
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- button.press:
id: refresh_clock
on_turn_off:
- button.press:
id: refresh_clock
- platform: template
name: Power On
id: power_on
icon: mdi:power
optimistic: true
assumed_state: true
restore_mode: RESTORE_DEFAULT_ON
turn_on_action:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
std::vector<uint8_t> command = {0x05, 0x00, 0x07, 0x01, 0x01};
return command;
turn_off_action:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda |-
std::vector<uint8_t> command = {0x05, 0x00, 0x07, 0x01, 0x00};
return command;
globals:
- id: png_command
type: std::vector<uint8_t>
script:
- id: send_png_ble
then:
- ble_client.ble_write:
id: ble_matrix
service_uuid: 00FA
characteristic_uuid: 0000FA02-0000-1000-8000-00805F9B34FB
value: !lambda 'return id(png_command);'
http_request:
verify_ssl: true
