Action  led paneel

Action led paneel

[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

Paars-minnende hacker, maker en lezer.