Trong bối cảnh nhà thông minh ngày càng phát triển, việc tích hợp các thiết bị IoT khác nhau vào một hệ sinh thái duy nhất như Home Assistant trở thành một nhu cầu thiết yếu. Tuy nhiên, không phải lúc nào mọi thứ cũng diễn ra suôn sẻ. Nhiều thiết bị cũ hơn hoặc đến từ các nhà sản xuất có chính sách API riêng biệt có thể gây ra những rào cản đáng kể. Điển hình là trường hợp của đèn LED Govee H615B, một thiết bị chiếu sáng thông minh phổ biến nhưng lại gây khó khăn trong việc tích hợp sâu rộng vào hệ thống tự động hóa cá nhân.
Mặc dù Govee cung cấp cả API dựa trên web và API cục bộ, nhưng cả hai đều có những hạn chế riêng. API web thường bị giới hạn về tốc độ truy cập (rate limit), khiến việc điều chỉnh độ sáng hoặc màu sắc nhanh chóng trở nên bất tiện, khóa bạn khỏi các thao tác chỉ sau vài lần thay đổi trạng thái. Trong khi đó, API cục bộ dù về lý thuyết là có tồn tại, nhưng trên nhiều thiết bị Govee, bao gồm cả H615B, tùy chọn này lại bị vô hiệu hóa hoàn toàn trong ứng dụng, khiến người dùng không thể kích hoạt. Chính những trở ngại này đã mở ra một hành trình khám phá sâu hơn, vượt ra ngoài những giải pháp sẵn có, để tìm kiếm một con đường kiểm soát hoàn toàn độc lập. Đó là hành trình kỹ thuật ngược giao thức Bluetooth Low Energy (BLE) của đèn Govee H615B, một thử thách đầy thú vị và mang lại quyền năng thực sự cho người dùng.
Đánh Giá Vấn Đề: Xác Định Công Cụ và Mục Tiêu
Chuẩn Bị Toàn Diện: Công Cụ và Mục Tiêu Kỹ Thuật Ngược
Bước đầu tiên trong bất kỳ dự án kỹ thuật ngược nào là đánh giá rõ ràng vấn đề, xác định các công cụ sẵn có và đặt ra mục tiêu cuối cùng. Đối với việc giải mã giao thức của đèn Govee H615B, tôi đã chuẩn bị một bộ công cụ đa dạng và thiết lập mục tiêu cụ thể:
- MacBook M4 Pro: Nền tảng chính để chạy các công cụ phân tích và viết mã.
- Google Pixel 8 Pro: Một chiếc điện thoại Android cao cấp, đóng vai trò quan trọng trong việc thu thập dữ liệu giao tiếp Bluetooth.
- Ứng dụng Govee chính thức: Cần thiết để điều khiển đèn ban đầu và ghi lại các gói tin Bluetooth.
- Wireshark: Phần mềm phân tích gói tin mạnh mẽ, không thể thiếu để giải mã dữ liệu Bluetooth đã thu thập.
- Bleak: Một framework Bluetooth dựa trên Python, giúp quét thiết bị và tương tác với các đặc tính (characteristics) của chúng.
- Milk-V Duo S: Một bộ vi điều khiển nhỏ gọn với hỗ trợ Wi-Fi và Bluetooth, lý tưởng để chạy script Python điều khiển đèn và tích hợp vào hệ thống nhà thông minh.
Chuyên gia đang đánh giá các công cụ kỹ thuật cần thiết trên MacBook Pro M4 Pro
Mục tiêu cốt lõi là tránh việc root chiếc Google Pixel 8 Pro, nếu có thể, để đảm bảo tính an toàn và tiện lợi. Giả định rằng sẽ không có cơ chế xác thực phức tạp nào khi điều khiển đèn qua Bluetooth, tôi tin rằng việc gửi các lệnh tùy chỉnh đến đèn là hoàn toàn khả thi.
Mục tiêu cuối cùng của dự án này là phát triển một script Python có khả năng chạy trên Milk-V Duo S. Script này sẽ được thiết kế để nhận các lệnh từ Home Assistant và sau đó truyền tải chúng đến đèn Govee H615B qua Bluetooth, từ đó cho phép người dùng kiểm soát đèn một cách liền mạch trong hệ sinh thái nhà thông minh của mình mà không cần phụ thuộc vào ứng dụng gốc hay các API giới hạn.
Thu Thập Dữ Liệu Bluetooth LE: Bí Quyết Từ Google Pixel
Ghi Log HCI: Cách Thức Và Lý Do Chọn Google Pixel
Để hiểu cách điều khiển đèn Govee từ xa, cách hiệu quả nhất là ghi lại toàn bộ các gói tin Bluetooth được gửi và nhận giữa ứng dụng Govee và đèn. Đây là lúc chiếc Google Pixel 8 Pro phát huy tác dụng. Mặc dù có các phần cứng chuyên dụng để “ngửi” (sniff) gói tin Bluetooth, nhưng Android lại có một tính năng ghi log Bluetooth HCI (Host Controller Interface) tích hợp sẵn, hoàn toàn đủ để thực hiện công việc này. Log HCI cung cấp một bản ghi cấp độ rất thấp của mọi thứ mà điện thoại của bạn gửi đi và nhận về qua Bluetooth. Tính năng này có thể được kích hoạt dễ dàng trong tùy chọn nhà phát triển (Developer Options) của Android.
Việc lựa chọn Google Pixel 8 Pro thay vì các thiết bị khác như Oppo Find N5 là có lý do. Vấn đề nằm ở nơi lưu trữ log và khả năng truy cập. Thông thường, bạn có thể tạo một báo cáo lỗi (bug report) thông qua ADB và log Bluetooth sẽ được lưu trữ trong thư mục logs. Tuy nhiên, trên một số thiết bị của các nhà sản xuất khác, tệp log này có thể trống rỗng hoặc không chứa dữ liệu hữu ích do các tùy biến hệ thống. Việc chuyển sang Google Pixel 8 Pro đã giải quyết vấn đề này, cho phép thu thập một log Bluetooth HCI thực sự chứa dữ liệu.
Trước khi trích xuất log, điều quan trọng là phải “lấp đầy” nó bằng dữ liệu hữu ích. Tôi cài đặt ứng dụng Govee, đăng nhập tài khoản và sau đó tắt kết nối Wi-Fi của điện thoại. Động thái này buộc ứng dụng phải sử dụng Bluetooth để điều khiển đèn, đảm bảo rằng mọi tương tác sẽ được ghi lại trong log. Sau khi thực hiện nhiều thao tác như thay đổi độ sáng, màu sắc, và bật/tắt đèn nhiều lần, tôi kết nối Pixel 8 Pro với máy tính xách tay và sử dụng lệnh ADB để lấy báo cáo lỗi.
Điện thoại Google Pixel 8 Pro, công cụ quan trọng để thu thập dữ liệu Bluetooth HCI
Giải Mã Giao Thức Bluetooth Cơ Bản Với Wireshark
Bắt Đầu Với Những Điều Nhỏ Nhất: Xác Định Đặc Tính và Gói Tin “Keep-Alive”
Tệp log Bluetooth HCI thu được từ báo cáo lỗi của Android sẽ nằm trong thư mục FS/data/misc/bluetooth/logs
và có phần mở rộng .cfa
sau khi giải nén tệp báo cáo lỗi. Tệp .cfa
này là một tệp BTSnoop, chứa các gói tin L2CAP. Các gói tin L2CAP là chi tiết về tất cả các giao tiếp mà thiết bị của bạn thực hiện qua Bluetooth và thường được sử dụng bởi các thiết bị Bluetooth Low Energy (BLE) để trao đổi thông tin. Trong trường hợp này, việc xác định thiết bị cần xem xét khá dễ dàng, nhưng tôi cũng sử dụng thư viện Python Bleak để quét các thiết bị và ID của chúng.
Tôi đã viết một công cụ quét đơn giản bằng Bleak. Khi xác định được UUID của thiết bị cần thiết (Govee H615B), tôi có thể truy vấn nó để lấy các đặc tính (characteristics). Đặc tính về cơ bản là các hồ sơ (profiles) hoặc kênh giao tiếp. Một máy khách (client) có thể khởi tạo lệnh nhắm mục tiêu một đặc tính và nhận phản hồi, trong khi một máy chủ (server) có thể chấp nhận các lệnh đó và thực thi chúng. Một lưu ý nhỏ: macOS và framework CoreBluetooth của nó sẽ cung cấp cho bạn một UUID cho các thiết bị Bluetooth Low Energy thay vì địa chỉ MAC để giao tiếp. Điều này ổn, nhưng cần ghi nhớ nếu bạn đang viết mã để chuyển sang một thiết bị khác sau này. Địa chỉ MAC mà bạn cần sử dụng (thay vì UUID) sẽ có trong log.
Trong quá trình quét của tôi, việc phát hiện Govee H615B khá dễ dàng. Tôi đã xác định được một số đặc tính, cung cấp thông tin cần thiết để điều tra sâu hơn trong log đã sao chép từ điện thoại. Các đặc tính này bao gồm:
- Handle: 0x0009, UUID: 00010203-0405-0607-0809-0a0b0c0d2b10
- Handle: 0x000d, UUID: 00010203-0405-0607-0809-0a0b0c0d2b11
- Handle: 0x0012, UUID: f000ffc1-0451-4000-b000-000000000000
- Handle: 0x0016, UUID: f000ffc2-0451-4000-b000-000000000000
Lưu ý rằng hai đặc tính kết thúc bằng “b10” và “b11” có khả năng liên quan đến nhau, cũng như hai đặc tính có “c1” và “c2”. Chúng ta sẽ tập trung vào hai đặc tính kết thúc bằng b10 và b11, vì theo nghiên cứu từ các dự án kỹ thuật ngược thiết bị Govee khác, dường như hai đặc tính này liên quan đến việc thiết lập trạng thái cho đèn, và các thiết bị khác cũng khớp chính xác với các chuỗi này.
Giao diện ứng dụng Bleak Python hiển thị quá trình quét và truy vấn các đặc tính Bluetooth LE của thiết bị Govee
Một điều quan trọng cần lưu ý khi làm việc với các thiết bị Govee là: mặc dù chúng có thể tương tác theo cách tương tự, nhưng có vẻ như mỗi thiết bị lại hơi khác nhau trong cách chấp nhận lệnh. Một số có các phân đoạn cho các phần khác nhau của dải đèn (để có thể điều khiển riêng lẻ), và một số có cơ chế xác thực trong quá trình ghép nối. Tôi đặc biệt lo lắng về bước xác thực này, nhưng tôi phát hiện ra rằng khi chiếc Pixel 8 Pro của tôi kết nối (và đó là lần đầu tiên tôi kết nối nó), không có bất kỳ kiểm tra nào về thiết bị đang gửi lệnh. Đây có phần là một lỗ hổng bảo mật (dù trên bề mặt là khá ít gây hại), nhưng chúng ta sẽ có thể tận dụng nó.
Trong quá trình kết nối ban đầu giữa điện thoại và đèn, tôi đã tìm thấy một chi tiết then chốt để làm cho toàn bộ quá trình này hoạt động. Còn nhớ đặc tính kết thúc bằng “b10” tôi đã đề cập chứ? Thiết bị khách (Pixel 8 Pro trong trường hợp này) được gửi một thông báo bởi bộ điều khiển Bluetooth thuộc về đèn, và nó đến từ đặc tính kết thúc bằng b10. Giờ đây chúng ta biết rằng chúng ta cần lắng nghe dịch vụ này, vì vậy chúng ta sẽ ghi nhớ điều đó khi viết mã để kết nối với nó sau này.
Giao diện Wireshark hiển thị gói tin thông báo từ đặc tính Bluetooth LE của đèn Govee H615B
Tiếp theo, tôi nhận thấy nhiều gói tin được gửi từ Pixel của tôi đến đèn có giá trị sau:
aa010000000000000000000000000000000000ab
Những gói tin này dường như là gói tin “keep-alive”, thông báo cho đèn Govee rằng chúng ta vẫn đang tìm cách gửi và nhận thông tin. Chúng được gửi khoảng hai giây một lần và chiếm phần lớn log. Tôi nhận thấy rằng khi tôi kết nối với đèn bình thường bằng Bleak, chúng sẽ ngắt kết nối khỏi máy tính xách tay của tôi chỉ trong vài giây. Vì điện thoại của tôi dường như không có liên lạc nào khác trong thời gian dài ngoài việc gửi các giá trị tương tự, tôi cho rằng chúng phải là gói tin “keep-alive”. Điều này cũng khớp với những gì các thiết bị Govee khác dường như làm.
Mặc dù tôi không chắc chắn dữ liệu sau “aa” là gì, nhưng byte cuối cùng của chuỗi (tức là hai ký tự cuối cùng) rất quan trọng. Các gói tin Bluetooth LE có độ dài 20 byte (không có MTU mở rộng), và có vẻ như các gói tin này sử dụng đệm zero để đạt được độ dài gói tin đó. Tại thời điểm này, chúng ta có thể kết luận rằng các gói tin bắt đầu bằng 0xaa
biểu thị một gói tin “keep-alive”, nhưng còn hai chữ số cuối cùng thì sao? Chúng ta sẽ tìm hiểu về chúng sau.
Giải Mã Lệnh Bật/Tắt Đèn Govee: Bước Đột Phá Đầu Tiên
Tiếp theo, chúng ta sẽ xem xét các lệnh mà tôi đã gửi. Khi tôi lần đầu mở ứng dụng và kết nối với đèn, tôi đã bật và tắt chúng. Trong log, phần dữ liệu đầu tiên được truyền và không giống gói tin “keep-alive” hoặc liên quan đến kết nối ban đầu là gói tin dưới đây. Tôi đã tắt chúng một lần nữa, và tôi tìm thấy một giá trị rất giống với giá trị trên nhưng hơi khác, và điều này khớp với thời gian tôi ghi chú. Tôi có thể suy luận rằng hai giá trị để bật và tắt đèn là như sau:
- Bật đèn:
3301010000000000000000000000000000000033
- Tắt đèn:
3301000000000000000000000000000000000032
Phân tích gói tin lệnh bật đèn Govee H615B trong Wireshark, hiển thị giá trị hexadecimal của lệnh
Lưu ý cấu trúc của gói tin; một lần nữa, chúng ta có rất nhiều byte zero và hai byte khác nhau ở cuối. Để dễ hiểu, chúng ta sử dụng “0x” để biểu thị một giá trị thập lục phân (hexadecimal), sử dụng cơ số 16. “0x” làm rõ rằng chúng ta đang nói về thập lục phân, chứ không phải một số thập phân thông thường, và 0x33
là “51” trong hệ thập phân. 0x33
dường như biểu thị một lệnh, với dữ liệu theo sau đưa ra hướng dẫn để thực thi. Trong trường hợp này, chúng ta có 0x33
, 0x01
, và 0x01
để bật đèn và 0x33
, 0x01
, và 0x00
để tắt chúng, cho thấy rằng một giá trị boolean ở byte thứ ba kiểm soát trạng thái bật/tắt. Chúng ta đã có đủ để thử nghiệm bằng cách thiết lập bộ nhận thông báo và về cơ bản lặp lại các hướng dẫn tương tự cho đèn. Chúng ta không cần lo lắng về hai chữ số cuối cùng vì chúng đã được tính toán sẵn cho chúng ta, nhưng chúng ta cũng sẽ tìm cách tính toán của riêng mình.
Đặt Màu Sắc và Độ Sáng: Nâng Cao Kiểm Soát Đèn Govee
Từ Màu Đỏ Đến Hex: Giải Mã Lệnh Thay Đổi Màu (Và Checksum)
Hiện tại, chúng ta đã có thể bật và tắt đèn thông qua Bluetooth. Đó là một bước tiến khá lớn, nhưng những chiếc đèn thông minh đầy màu sắc như thế này không chỉ có một công tắc bật/tắt. Tôi đã điều tra các lệnh liên quan đến màu sắc và độ sáng, vì tôi cũng đã điều chỉnh cả hai trong ứng dụng để xem chúng sẽ trông như thế nào ở cấp độ gói tin. Tôi đã tìm thấy gói tin sau:
33050dfe0e1f00000000000000000000000d4
Lại là byte bắt đầu 0x33
đó, vì vậy chúng ta biết mình đang nhận được một lệnh. Tôi không chắc chắn về 050d
, nhưng “fe0e1f” trông giống một mã màu hex. Khi tôi chuyển đổi nó từ hex sang một màu thực tế, nó nổi bật là màu đỏ, và tôi đã thay đổi đèn của mình sang màu đỏ trong quá trình thử nghiệm. Tôi muốn kiểm tra xem liệu tôi có thể thay thế “fe0e1” bằng màu của riêng mình hay không, nhưng có một vấn đề. Trước đây, chúng ta có thể chỉ cần phát lại các gói tin đã thấy trong log, và chúng sẽ thực hiện lại các lệnh đó. Làm thế nào để chúng ta tạo ra các lệnh mới? Chúng ta không thể chỉ đơn giản thay thế các giá trị màu hex đó bằng giá trị của riêng mình. Lý do chúng ta không thể là do sự xuất hiện của byte cuối cùng.
Byte cuối cùng đó là một checksum, về cơ bản xác nhận rằng dữ liệu đã đến trong tình trạng hoàn chỉnh và không bị lỗi. Nó được tính toán bằng cách thực hiện phép toán XOR tích lũy trên mỗi byte. Một XOR là một loại cổng logic tạo ra ‘1’ khi hai giá trị đầu vào khác nhau. Mỗi byte sau đó được XOR với byte trước đó cho đến khi đạt đến byte thứ 19. Kết quả tính toán cuối cùng được thêm vào cuối gói tin vào byte thứ 20, và đây là gói tin được gửi đến thiết bị. Cuối cùng, thiết bị thực hiện phép toán XOR riêng của nó trên 19 byte đầu tiên, kiểm tra xem byte cuối cùng có khớp với những gì nó đã tính toán hay không. Nếu khớp, nó biết dữ liệu đã đến như dự định và an toàn để thực thi.
Hãy thử thay đổi đèn sang màu magenta, mã hex #FF00FF. Điều này sẽ trông như sau:
33050d[ff00ff]00000000000000000000000d4[checksum]
ff00ff
(đặt trong dấu ngoặc vuông ở trên để dễ hiểu) là mã màu của chúng ta, và [checksum]
là thứ chúng ta muốn tính toán. Chúng ta bắt đầu với giá trị của một byte trống, hay 00000000
.
- Bắt đầu với
0x00
(nhị phân:00000000
). - XOR với
0x33
(00110011
), kết quả:0x33
(00110011
). - XOR với
0x05
(00000101
), kết quả:0x36
(00110110
). - Tiếp tục XOR từng byte trong thông điệp với byte trước đó.
- Kết quả cuối cùng:
0xff
(nhị phân:11111111
).
Chúng ta thực hiện tính toán qua chuỗi cho đến khi đạt đến cuối cùng. Điều này dẫn đến giá trị ff
, với giá trị nhị phân là 11111111
. Giá trị cuối cùng mà chúng ta sẽ gửi đến đèn là:
33050dff00ff00000000000000000000000d4ff
Nhưng việc tính toán này không tiện lợi mỗi khi chúng ta muốn thay đổi màu đèn. Thay vào đó, chúng ta có thể tự động hóa quy trình này, điều mà tôi đã làm trong Python. Tôi đã triển khai một phương thức nhận các giá trị hex RGB, chèn chúng vào chuỗi và sau đó tính toán checksum để thêm vào phần còn lại của chuỗi. Tôi sẽ không làm bạn nhàm chán với các chi tiết, vì nó chỉ đơn thuần triển khai phép tính chúng ta đã làm ở trên một cách lập trình để có được một checksum mới mỗi khi chúng ta gửi lệnh thay đổi màu.
Cuối cùng, hãy xem xét độ sáng. Sử dụng quy trình tương tự, tôi phát hiện ra rằng việc đặt độ sáng dường như là lệnh sau:
3304[brightness]00000000000000000000000000000000[checksum]
Độ sáng là một byte đơn, dao động từ 00
(0) đến FF
(255). Checksum lại phải được tính toán, nhưng điều này dễ dàng thực hiện vì chúng ta đã tìm ra cách tính toán nó. Để đặt độ sáng 100%, ví dụ, chỉ cần:
3304ff00000000000000000000000000000000c8
Giờ đây, chúng ta đã hoàn toàn tìm ra cách điều khiển đèn của mình! Chúng ta có thể:
- Bật và tắt đèn H615B.
- Đặt màu sắc.
- Đặt độ sáng.
Và chúng ta có thể làm tất cả những điều này mà không cần sử dụng ứng dụng chính thức! Nó bỏ qua một API dựa trên đám mây, vượt qua vấn đề không thể sử dụng API cục bộ, và có nghĩa là chúng ta có thể tự động hóa việc điều khiển chúng từ một thiết bị khác bằng cách tích hợp chúng vào nhà thông minh của mình.
Kết Luận Hành Trình Kỹ Thuật Ngược Và Ứng Dụng Thực Tiễn
Thách Thức và Phần Thưởng Của Kỹ Thuật Ngược IoT
Kỹ thuật ngược có thể là một quá trình khó khăn, và bạn có thể gặp nhiều rào cản trong quá trình thực hiện. Có vô số tài nguyên ngoài kia để cố gắng giúp đỡ bạn, nhưng khả năng cao là nếu bạn đang thực hiện kỹ thuật ngược một thứ gì đó, thì bạn đang làm điều đó bởi vì chưa ai khác làm điều đó. Tôi đã có thể tận dụng tất cả dữ liệu đã thu thập được để xây dựng một trang web để điều khiển những chiếc đèn này trong trình duyệt của mình, nhưng tôi đã may mắn vì đó là một quá trình tương đối đơn giản để xác định những gì tôi cần thay đổi và cách thức.
Giao diện điều khiển đèn Govee H615B tùy chỉnh trên trình duyệt web, minh họa khả năng kiểm soát sau kỹ thuật ngược
Mục tiêu của bài viết này là hướng dẫn bạn các bước cần thiết khi thực hiện kỹ thuật ngược một thứ như thế này. Có rất nhiều thiết bị thông minh giá rẻ trên thị trường tương tự như Govee H615B yêu cầu một ứng dụng độc quyền để điều khiển chúng. Tuy nhiên, không phải là không thể tìm ra cách chúng hoạt động, và với hàng giờ hoặc thậm chí hàng ngày lao động miệt mài trước máy tính, đôi khi bạn có thể vượt qua và tìm ra cách tự mình điều khiển chúng. Đó chính xác là những gì tôi đã làm. Điều bắt đầu như một dự án cuối tuần vui vẻ nhỏ bé đã kết thúc trở thành một công việc kéo dài nhiều ngày mà tôi biết mình muốn tìm hiểu đến cùng.
Từ thời điểm này, việc triển khai một cơ chế điều khiển từ Home Assistant trở nên đơn giản để bạn có thể sử dụng những chiếc đèn này như bất kỳ thiết bị nào khác của mình. Ví dụ, bạn có thể triển khai một REST API trong Flask và sau đó sử dụng tích hợp rest_command
trong Home Assistant để gửi lệnh. Cuối cùng, tạo một script sẽ bật hoặc tắt nó, và bạn có thể xây dựng một template switch, một thanh trượt đầu vào, hoặc một điều khiển Lovelace tùy chỉnh cho nguồn, độ sáng và màu sắc. Đây là những gì tôi đã làm khi triển khai nó trên Milk-V Duo S của mình, và nó hoạt động hoàn hảo. Mặc dù không được khám phá trong bài viết này, bạn cũng có thể đánh giá trạng thái hiện tại của đèn Govee (đã bật hay tắt) bằng cách in dữ liệu được phát bởi nó khi quét.
Nếu bạn đang sở hữu những chiếc đèn Govee này và muốn tự mình kiểm soát chúng từ bất kỳ thiết bị hỗ trợ Bluetooth nào, bạn có thể tham khảo kho lưu trữ GitHub của tôi để tìm mã nguồn. Tất cả những gì bạn cần là địa chỉ MAC (bạn có thể lấy bằng một ứng dụng như nRF Connect trên điện thoại của mình), và phần còn lại sẽ hoạt động bình thường. Đây là một quá trình học hỏi vô cùng bổ ích, và tôi hy vọng rằng điều này sẽ truyền cảm hứng cho bạn để xem xét kỹ hơn các thiết bị xung quanh mình để tìm hiểu xem điều gì khiến chúng hoạt động và làm thế nào bạn có thể tự mình kiểm soát chúng!