Gần đây, tôi đã sở hữu một chiếc Cheap Yellow Display (CYD) được trang bị ESP32. Với mức giá khoảng 20 USD, bạn có một module ESP32 kết nối với màn hình, đi kèm khe cắm thẻ SD và một số chân GPIO có thể sử dụng cho các dự án bổ sung. Trong khi ESPHome đã có một số công cụ tích hợp sẵn khá tốt để tạo giao diện người dùng, thư viện đồ họa Light and Versatile Graphics Library (LVGL) đã được thêm hỗ trợ cách đây vài tháng. Đây là một thư viện đồ họa chứa đầy các widget được xây dựng sẵn, và chúng trông đẹp mắt hơn nhiều so với bất kỳ thứ gì tôi có thể tạo trong ESPHome.
Tôi đã xây dựng một dashboard để điều khiển các thiết bị nhà thông minh cục bộ bằng LVGL, và những gì khởi đầu chỉ là để điều khiển đèn trong nhà và phát đa phương tiện giờ đây đã trở thành một bảng điều khiển giám sát PC bên cạnh tất cả những tính năng đó. Dù sao thì nó cũng nằm trên bàn làm việc của tôi, nơi có PC, vì vậy việc tích hợp thêm tính năng này là điều hợp lý. Giờ đây, tôi có thể xem thời gian khởi động cuối cùng, cửa sổ đang hoạt động, mức sử dụng bộ nhớ và mức sử dụng CPU, tất cả đều hiển thị trên màn hình nhỏ này.
Tất nhiên, việc hiển thị cửa sổ đang hoạt động không quá hữu ích khi PC của tôi ngay trước mặt, nhưng đây chỉ là một minh chứng ý tưởng, cho thấy loại dữ liệu bạn có thể trích xuất. Đối với tôi, việc sử dụng nó trong ngữ cảnh này không có ý nghĩa, nhưng bạn, người đọc, có thể có lý do để muốn hiển thị cửa sổ đang hoạt động của PC trên một bảng điều khiển. Hơn nữa, nó yêu cầu một cách hiển thị dữ liệu hơi khác so với một số cảm biến khác, vì vậy đây là cách để minh họa một phương pháp khác để hiển thị văn bản trên màn hình.
Để thực hiện điều này, tôi đang sử dụng HASS.Agent trên Windows để gửi dữ liệu đến máy chủ MQTT, cùng với ESPHome dưới dạng một tiện ích bổ sung trong Home Assistant. Sau khi kết hợp, dữ liệu sẽ đi từ PC của tôi đến máy chủ MQTT, đến Home Assistant, và sau đó đến CYD của tôi. Thật tuyệt vời, và các khả năng dường như là vô tận khi nói đến Home Assistant và ESPHome.
Cách thức hoạt động của các đoạn mã ESPHome và việc triển khai nút bấm đầu tiên
Để triển khai dự án này, chúng ta sẽ giả định bạn đã cài đặt HASS.Agent, kết nối với một MQTT broker và đã cài đặt ESPHome dưới dạng tiện ích bổ sung cho Home Assistant. Nếu bạn đang sử dụng Home Assistant Container (thay vì HAOS), bạn có thể cài đặt ESPHome dưới dạng một container riêng biệt, với tích hợp ESPHome được cài đặt trong Home Assistant để liên kết mọi thứ.
Phần cơ bản của dự án này sử dụng dự án ESPHome LVGL của Ryan Ewen trên GitHub. Dự án này đã chứng minh giá trị vô cùng to lớn trong việc giúp tôi học LVGL và đã hỗ trợ tôi đáng kể trong suốt quá trình phát triển dashboard này. Tôi đã tích hợp điều khiển đèn và điều khiển đa phương tiện, và bước tiếp theo là lấy dữ liệu từ Home Assistant và hiển thị nó trong các “card” như hình trên. Các chỉ số này được hiển thị trên một trang riêng biệt, và tôi có thể sử dụng các phím mũi tên ở phía dưới để di chuyển trái phải giữa chúng.
Để đọc thời gian khởi động cuối cùng, trước tiên tôi tạo một text sensor trong ESPHome đọc cảm biến “lastboot” từ thực thể MQTT của tôi. Đoạn mã sẽ trông như thế này, hãy đảm bảo bạn sử dụng đúng cách thụt lề:
- platform: homeassistant
id: pc_last_boot_raw
entity_id: sensor.desktop_73d9nef_lastboot
internal: true
on_value:
then:
- lvgl.widget.refresh: pc_last_boot_raw_on
Đoạn mã trên khá dễ hiểu, ngoại trừ phần “lvgl.widget.refresh”. Điều này được thực hiện vì màn hình sẽ vẽ các thành phần trước khi chúng được cung cấp các giá trị thực, do nó khởi động nhanh hơn và hiển thị màn hình chính nhanh hơn so với thời gian kết nối mạng của tôi. Phần đó đảm bảo rằng nó làm mới widget “pc_last_boot_raw_on”, mà tôi sẽ giải thích tiếp theo.
widgets:
- button:
width: 232
height: 75
widgets:
- label:
id: pc_last_boot_raw_on
text: !lambda |-
if (id(pc_last_boot_raw).state.length() < 19)
return std::string("");
std::string ts = id(pc_last_boot_raw).state;
std::string date = ts.substr(0, 10);
std::string time = ts.substr(11, 8);
return date + "n" + time;
text_font: roboto_md
align: CENTER
- label:
id: pc_last_boot_raw_off
text: "PC off"
text_font: roboto_md
align: BOTTOM_LEFT
hidden: true
Đoạn mã trên có thể hơi khó hiểu, vì vậy tôi sẽ giải thích cách nó hoạt động từ đầu đến cuối. “width” và “height” được lấy từ các widget nút 320×240 của Ewen, vì các giá trị này đã được tính toán để phù hợp với một “nút rộng” trên màn hình này, cho phép ba hàng nút. Tiếp theo, chúng ta tạo một widget lồng nhau chứa hai nhãn riêng biệt: pc_last_boot_raw_on và pc_last_boot_raw_off. Tôi chưa tích hợp đúng logic cho nhãn sau (và “hidden: true” ẩn nó khỏi màn hình), nhưng tôi sẽ giải thích tác dụng của nó sau khi giải thích pc_last_boot_raw_on.
Trong pc_last_boot_raw_on
, chúng ta sử dụng !lambda
để xử lý văn bản từ text sensor. Mặc dù các cấu hình ESPHome được viết bằng YAML, lambda
cho ESPHome biết rằng khối văn bản tiếp theo là mã C++. Đây cũng là lý do chúng ta sử dụng |-
, vì nó cho ESPHome biết rằng khối thụt lề tiếp theo là văn bản thuần túy. lambda
rất mạnh mẽ trong ESPHome, vì bạn có thể cấu hình mọi thứ bạn muốn bằng cú pháp YAML, và sau đó chèn mã C++ bên trong các khối cụ thể để tùy chỉnh hành vi hoặc thay đổi động các thành phần.
lambda
này kiểm tra xem độ dài của khối có ngắn hơn 19 ký tự hay không. Đây là một cách làm hơi “hacky” và tôi có thể điều chỉnh tốt hơn, nhưng HASS.Agent báo cáo thời gian khởi động ở định dạng 2025-06-20T10:12:29, dài 19 ký tự. Về cơ bản, chúng ta đang kiểm tra tính hợp lệ của phản hồi để đảm bảo rằng nó khớp với độ dài của kết quả mong đợi. Sẽ tốt hơn nếu thay đổi trạng thái bên ngoài khối và phản ứng tương ứng, nhưng cách này hoạt động và đặt khối về “” nếu chúng ta nhận được một giá trị không mong muốn, giữ cho nó trống.
Hiển thị thời gian khởi động PC chi tiết trên màn hình ESP32 CYD với dữ liệu từ HASS.Agent
Tuy nhiên, nếu nó vượt qua, chúng ta sao chép giá trị của text sensor (“state”) vào một chuỗi tạm thời nội bộ, được gọi là ts
. Trong C++ (và trong C), một chuỗi là một mảng ký tự động, vì vậy chúng ta có thể sao chép các phần tử mảng sang một chuỗi khác (bằng cách tạo một chuỗi con) để tái tạo các phần chúng ta muốn giữ. Chúng ta cũng biết, vì chuỗi sẽ luôn có độ dài chuẩn, các vị trí của các ký tự chúng ta muốn trong mọi trường hợp. Chúng ta sử dụng phương thức “substr” để tạo một chuỗi con, chỉ định nó bắt đầu từ vị trí 0 và thu thập 10 ký tự tiếp theo để lưu vào chuỗi ngày của chúng ta. Chúng ta làm tương tự để lấy thời gian, chỉ định substr
bắt đầu từ vị trí 11 (bỏ qua ký tự “T” trong chuỗi) và thu thập 8 ký tự tiếp theo để lưu vào chuỗi thời gian của chúng ta.
Cuối cùng, chúng ta trả về:
date + "n" + time
“n” là ký tự xuống dòng, và chúng ta phân tách ngày và thời gian mà không có dấu ngoặc kép vì các biến đó đã đại diện cho các chuỗi. Dấu ngoặc kép biểu thị một chuỗi bạn đang nhập trong mã, vì vậy đoạn mã trên về cơ bản nói “lấy biến từ ngày, nối thêm ký tự xuống dòng vào đó, sau đó nối thêm biến từ thời gian vào chuỗi tổng thể.”
Bởi vì tất cả điều này nằm trong khối “text”, ESPHome về cơ bản tạo nút với thông tin nói, “tạo nút này, đặt giá trị ‘text’ mà nút yêu cầu thành đầu ra của bất kỳ thứ gì mã này thực hiện.” Bằng cách này, chúng ta có thể hiển thị văn bản một cách động, thay vì phải đặt các giá trị tĩnh hoặc cố gắng đặt các giá trị động từ bên ngoài nơi chúng được tạo ban đầu.
Nhưng còn “lvgl.widget.refresh: pc_last_boot_raw_on”? Chúng ta đã đặt tên cho nút là “pc_last_boot_raw_on”, vì vậy chúng ta về cơ bản vẽ lại nút nếu một giá trị mới được nhận. Điều này cho phép nó cập nhật sau khi khởi động, và hoạt động tốt với các cảm biến khác mà chúng ta sẽ nói đến tiếp theo.
Cảm biến ngày giờ khởi động là cảm biến phức tạp nhất trong số tất cả, vì vậy nếu bạn đã theo dõi đến đây, mọi việc sẽ trở nên dễ dàng hơn nhiều.
Cấu hình hiển thị dữ liệu RAM, CPU và cửa sổ đang hoạt động
Giao diện cấu hình ESPHome trong Home Assistant để tích hợp ESP32 CYD với dữ liệu PC
Chúng ta định nghĩa các text sensor trong ESPHome để lấy dữ liệu từ Home Assistant theo cách tương tự như chúng ta đã làm để lấy ngày giờ khởi động, vì vậy bạn chỉ cần sửa đổi text sensor đó để lấy các biến khác mà bạn cần. Sau khi bạn đã tạo một text sensor cho RAM, CPU và cửa sổ đang hoạt động (tham chiếu đến các thực thể chính xác), chúng ta có thể chuyển sang tạo các nút của mình.
- button:
width: 114
height: 75
widgets:
- label:
id: pc_mem_on
text: !lambda |-
std::string mem = id(pc_mem_raw).state;
return "Mem: " + mem + "%";
text_font: roboto_md
align: CENTER
- label:
id: pc_mem_off
text: "PC off"
text_font: roboto_md
align: BOTTOM_LEFT
hidden: true
Chúng ta đã xem xét phần lớn điều này, với những thay đổi duy nhất bên ngoài khối lambda
là chiều rộng của nút để cho phép hai nút trên một hàng. Trong lambda
của chúng ta, chúng ta chỉ cần sao chép giá trị của pc_mem_raw
vào biến “mem” của chúng ta. Cuối cùng, chúng ta trả về “Mem: %”. Bạn có thể thấy cú pháp của câu lệnh return
của chúng ta hoạt động như thế nào, và lưu ý khoảng trống sau dấu hai chấm. Nó sẽ trả về chính xác những gì bạn cung cấp cho nó, vì vậy nếu bạn không đặt khoảng trống bên trong dấu ngoặc kép, nó sẽ dán giá trị của mem
trực tiếp cạnh ký tự dấu hai chấm.
Chúng ta có thể lặp lại điều tương tự cho CPU và cửa sổ đang hoạt động, mặc dù cửa sổ đang hoạt động chỉ là một return
thông thường mà không có bất kỳ phép nối chuỗi nào. Cửa sổ đang hoạt động trong ví dụ của tôi cũng là một nút rộng hơn như nút thời gian khởi động của chúng ta.
ESPHome: Một nền tảng vô cùng mạnh mẽ cho các dự án IoT
Màn hình Cheap Yellow Display (CYD) với ESP32, thể hiện tiềm năng của ESPHome trong các dự án IoT và nhà thông minh
Tôi vẫn đang học cách sử dụng ESPHome, và các thư viện như LVGL hoàn toàn mới mẻ đối với tôi. Đây có thể không phải là cách tốt nhất để đạt được những gì tôi đã xây dựng, nhưng nó hoạt động, và tôi có thể lặp lại và cải thiện nó trong tương lai. Những gì ESPHome đã cho phép tôi xây dựng một cách dễ dàng, từ trình duyệt của tôi, với các bản cập nhật có thể được gửi không dây, thực sự rất tuyệt vời, và tôi vô cùng kinh ngạc về trải nghiệm liền mạch của nó.
Tất nhiên, có những cách khác để gửi dữ liệu giám sát đến Home Assistant, nhưng tôi đã sử dụng HASS.Agent, và dữ liệu tôi nhận được ở đây là đủ để hiển thị một bản thử nghiệm hoạt động của loại dữ liệu tôi có thể thu thập và hiển thị. Nếu bạn thiết lập điều này và sau đó quyết định muốn thay đổi nguồn dữ liệu của mình, bạn chỉ cần thay đổi text sensor để lấy từ thực thể mới. Bạn không cần phải đại tu toàn bộ, trừ khi định dạng dữ liệu thay đổi đáng kể để ảnh hưởng đến các lambda
của bạn.
Nếu bạn có một số thiết bị ESP32 nằm rải rác, bạn có thể bắt đầu sử dụng ESPHome ngay lập tức. Kết nối nó với PC của bạn, mở công cụ xây dựng thiết bị ESPHome, và nhấn cài đặt. Nó sẽ đẩy một thiết lập cơ bản, mặc định sẽ kết nối ESP32 với Wi-Fi của bạn và bật cập nhật OTA. Từ đó, bạn có thể làm bất cứ điều gì bạn muốn, và những thiết bị nhỏ này có thể làm mọi thứ từ hiển thị thông tin thực sự hữu ích đến cho phép bạn vỗ bàn để bật PC. Thật thú vị và bạn sẽ học được rất nhiều điều.