Image default
Máy Tính

Giải mã đèn Govee H615B: Điều khiển qua Bluetooth mà không cần ứng dụng chính thức

Trong hành trình không ngừng tối ưu và tích hợp các thiết bị vào hệ thống nhà thông minh của riêng mình, tôi đã nỗ lực tìm kiếm những phương pháp mới để đưa các phần cứng cũ vào Home Assistant. Đối với nhiều thiết bị Tuya, việc này tương đối thuận lợi nhờ Local Tuya, và tôi cũng có nhiều dịch vụ tự host hữu ích để cung cấp thông tin cho Home Assistant. Tuy nhiên, tôi đã gặp phải một trở ngại lớn với dải đèn Govee H615B của mình. Điều này đã dẫn tôi vào một cuộc “đào bới” sâu sắc để reverse engineering, nhằm tích hợp chúng giống như cách tôi đã làm với mọi thiết bị khác.

Govee cung cấp cả API dựa trên nền tảng web và API cục bộ. Nếu API web đủ tốt, có lẽ tôi đã chấp nhận và sử dụng nó. Thế nhưng, API này bị giới hạn tốc độ truy cập rất nhanh. Chỉ cần vài lần thay đổi trạng thái để điều chỉnh độ sáng, bạn sẽ bị khóa truy cập trong một phút. Về phần API cục bộ, mặc dù về mặt kỹ thuật nó tồn tại, nhưng tôi không thể kích hoạt nó trên đèn Govee của mình. Tôi không hiểu lý do vì sao. Tôi đã thử mọi cách nhưng tùy chọn này bị làm mờ hoàn toàn trong cài đặt ứng dụng Govee.

Sau khi nghiên cứu một số dự án reverse engineering liên quan đến các thiết bị chiếu sáng Govee khác, tôi nghĩ rằng việc thử reverse engineering thiết bị của mình là điều đáng làm. Thế là hành trình của tôi bắt đầu, trang bị Wireshark và Python, để tìm hiểu cách những chiếc đèn này hoạt động và liệu tôi có thể điều khiển chúng từ bất kỳ thiết bị Bluetooth nào chứ không chỉ ứng dụng chính thức.

Tất cả mã nguồn được sử dụng trong bài viết này đều là mã nguồn mở và bạn có thể tìm thấy ở cuối bài viết!

Đánh giá vấn đề và Chuẩn bị Công cụ

Bước đầu tiên: Xác định mục tiêu và tập hợp tài nguyên

Bước đầu tiên khi thực hiện reverse engineering là đánh giá vấn đề, xác định các công cụ có sẵn và mục tiêu cuối cùng của bạn. Tôi đã có các công cụ sau:

  • Một chiếc MacBook M4 Pro
  • Một chiếc Google Pixel 8 Pro
  • Ứng dụng Govee (có thể điều khiển đèn khi không có kết nối mạng thông qua Bluetooth LE)
  • Wireshark
  • Bleak, một framework Bluetooth trong Python
  • Một chiếc Milk-V Duo S (bộ vi điều khiển với cả nhân Arm và RISC-V có khả năng chạy Python, cùng với hỗ trợ Wi-Fi/Bluetooth tích hợp)

Mô hình MacBook Pro M4 Pro 16 inch, một công cụ quan trọng trong dự án reverse engineering đèn GoveeMô hình MacBook Pro M4 Pro 16 inch, một công cụ quan trọng trong dự án reverse engineering đèn Govee

Tôi muốn tránh việc root chiếc Google Pixel 8 Pro của mình nếu có thể, và tôi nghĩ rằng điều đó là khả thi, vì vậy tôi đã bỏ qua phương án này trừ khi không còn lựa chọn nào khác. Mọi thứ dường như rất khả thi, và nếu không có xác thực để điều khiển những chiếc đèn này, tôi có thể truyền lệnh của riêng mình tới đèn để điều khiển chúng.

Mục tiêu cuối cùng là viết một script Python có thể chạy trên chiếc Milk-V Duo S của tôi, với một máy chủ chấp nhận lệnh từ Home Assistant để sau đó truyền đến đèn.

Thu thập Dữ liệu Bluetooth

Ghi lại gói tin Bluetooth HCI trên Google Pixel

Để tìm ra cách điều khiển những chiếc đèn này từ xa, tôi nghĩ cách tốt nhất là chỉ cần ghi lại các gói tin Bluetooth được gửi đi và nhận về. Đó là lý do tại sao tôi sử dụng Google Pixel 8 Pro. Có phần cứng bạn có thể sử dụng để sniff các gói Bluetooth đang truyền tải, nhưng tôi không có phần cứng đó. Android có một bộ ghi Bluetooth HCI tích hợp sẵn hoạt động rất tốt. HCI là viết tắt của Host Controller Interface, và nhật ký này là một bản ghi rất cấp thấp của mọi thứ điện thoại của bạn gửi đi và mọi thứ điện thoại của bạn nhận được. Bạn có thể bật chức năng này trong tùy chọn nhà phát triển (developer options).

Điện thoại Google Pixel 8 Pro, được sử dụng để ghi lại gói tin Bluetooth HCI cho việc phân tích đèn GoveeĐiện thoại Google Pixel 8 Pro, được sử dụng để ghi lại gói tin Bluetooth HCI cho việc phân tích đèn Govee

Câu hỏi tiếp theo là tại sao tôi lại sử dụng Google Pixel 8 Pro mà không phải Oppo Find N5 hay bất kỳ thiết bị nào khác. Nhật ký được lưu trữ ở đâu? Cách thông thường để truy cập nhật ký này hiện nay là thông qua adb; bạn có thể khởi tạo một báo cáo lỗi (bug report) được lưu vào máy tính của mình, và nhật ký nên được lưu trong thư mục nhật ký Bluetooth, nhưng điều đó không đúng trên chiếc Oppo Find N5 của tôi. Tôi một tệp, nhưng nó trống rỗng. Vì nhiều công ty thực hiện sửa đổi đối với các hệ thống như thế này, sau đó tôi đã chọn Google Pixel 8 Pro để tránh bất kỳ “trò đùa” nào của OEM. Chuyển sang Google Pixel 8 Pro đã giúp tôi thu được một nhật ký Bluetooth HCI thực sự chứa dữ liệu.

Tuy nhiên, trước khi lấy nhật ký, cần phải điền dữ liệu hữu ích vào đó. Tôi đã cài đặt ứng dụng Govee và đăng nhập vào tài khoản của mình, sau đó tắt Wi-Fi. Điều này có nghĩa là ứng dụng buộc phải sử dụng Bluetooth để điều khiển đèn, vì vậy dữ liệu sẽ được lưu trong nhật ký. Sau khi thay đổi độ sáng và màu sắc, cũng như bật tắt đèn nhiều lần, đã đến lúc cắm Pixel 8 Pro vào máy tính xách tay của tôi và lấy báo cáo lỗi bằng adb.

Phân tích Dữ liệu cơ bản với Wireshark

Bắt đầu từ những chi tiết nhỏ nhất

Nhật ký Bluetooth HCI sẽ nằm trong FS/data/misc/bluetooth/logs và sẽ có phần mở rộng .cfa sau khi bạn giải nén tệp báo cáo lỗi. Tệp .cfa là một tệp BTSnoop chứa các gói L2CAP. Những gói này trình bày chi tiết 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 các thiết bị Bluetooth Low Energy sử dụng để giao tiếp. Trong trường hợp cụ thể này, khá dễ dàng để khám phá thiết bị nào tôi cần xem xét, 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.

Giao diện terminal hiển thị quá trình quét thiết bị Bluetooth Low Energy (BLE) bằng thư viện Bleak trong Python, giúp xác định đèn Govee H615B và các đặc tính của nóGiao diện terminal hiển thị quá trình quét thiết bị Bluetooth Low Energy (BLE) bằng thư viện Bleak trong Python, giúp xác định đèn Govee H615B và các đặc tính của nó

Tôi đã viết một bộ quét đơn giản bằng Bleak, và khi tôi xác định được UUID của thiết bị mình cần, tôi có thể truy vấn nó để biết các đặc tính (characteristics). Đặc tính về cơ bản là các hồ sơ. Một client có thể khởi tạo lệnh nhắm mục tiêu một đặc tính và có thể nhận phản hồi, và một máy chủ có thể chấp nhận các lệnh đó và thực hiện chúng. Nói thêm, 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 không sao, nhưng đó là điều cần lưu ý nếu bạn đang viết mã để chuyển sang 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 nhật ký.

Trong quá trình quét của tôi, khá dễ dàng để phát hiện Govee H615B. Tôi đã có thể xác định một số đặc tính, cung cấp cho tôi thông tin cần thiết để nghiên cứu sâu hơn trong nhật ký mà tôi đã sao chép từ điện thoại của mình. Đó là:

  • 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, 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 của tôi về cá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 các chuỗi này.

Đây là một bối cảnh khác khi nói đến các thiết bị này: mặc dù tất cả chúng đều tương tự nhau về cách tương tác, nhưng dường như chúng có sự khác biệt nhỏ về 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 (để chúng có thể được điều khiển riêng lẻ), và một số có 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 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ó kiểm tra nào về thiết bị nào đang gửi lệnh. Đây có phần là một lỗ hổng bảo mật (mặc dù khá không gây hại trên bề mặt), nhưng chúng ta sẽ có thể tận dụng nó.

Giao diện Wireshark phân tích gói tin Bluetooth Low Energy (BLE) cho thấy gói thông báo từ đặc tính (characteristic) b10 của đèn Govee H615BGiao diện Wireshark phân tích gói tin Bluetooth Low Energy (BLE) cho thấy gói thông báo từ đặc tính (characteristic) b10 của đèn Govee H615B

Một điều tôi tìm thấy trong quá trình kết nối ban đầu giữa điện thoại và đèn đã trở thành một phần quan trọng để làm cho tất cả những điều này hoạt động. Bạn có nhớ đặc tính tôi đã đề cập kết thúc bằng “b10” không? Thiết bị client (Pixel 8 Pro trong trường hợp này) được bộ điều khiển Bluetooth thuộc về đèn gửi một thông báo, 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.

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

Đây dường như là các gói “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. Những gói này được gửi khoảng hai giây một lần và chiếm phần lớn nhật ký. 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 hệ nào khác trong thời gian dài ngoài việc gửi các giá trị tương tự như vậy, tôi nghĩ rằng chúng phải là các gói 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 dữ liệu là gì sau “aa”, byte cuối cùng của chuỗi (hai ký tự cuối cùng) rất quan trọng. Các gói Bluetooth LE có độ dài 20 byte (không có MTU mở rộng), và có vẻ như các gói này sử dụng đệm bằng 0 để đáp ứng độ dài gói đó. Chúng ta có thể kết luận tại thời điểm này rằng các gói bắt đầu bằng 0xaa biểu thị một gói keep-alive, nhưng còn hai chữ số cuối cùng thì sao? Chúng ta sẽ tìm hiểu chúng sau.

Gói tin Bluetooth Low Energy (BLE) "Power On" của đèn Govee H615B được ghi lại bằng Wireshark, hiển thị các byte lệnh điều khiểnGói tin Bluetooth Low Energy (BLE) "Power On" của đèn Govee H615B được ghi lại bằng Wireshark, hiển thị các byte lệnh điều khiể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 nhật ký, phần dữ liệu đầu tiên được truyền và không giống như một gói keep-alive hoặc liên quan đến kết nối ban đầu là gói trên. 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ú. Sau đó, tôi có thể suy ra rằng hai giá trị để bật và tắt đèn như sau:

  • Bật đèn: 3301010000000000000000000000000000000033
  • Tắt đèn: 3301000000000000000000000000000000000032

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 0 và hai byte khác nhau ở cuối. Để giải thích sơ lược, chúng ta nói “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 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 đèn, cho thấy rằng một giá trị boolean ở byte thứ ba điều khiển trạng thái bật/tắt. Chúng ta đã có đủ để kiểm tra bằng cách thiết lập bộ thu thông báo của chúng ta 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 cho chúng ta, nhưng chúng ta cũng sẽ tìm hiểu cách tính toán của riêng mình.

Thiết lập Màu sắc và Độ sáng cho Đèn Govee

Điều khiển nâng cao hơn: Không chỉ bật/tắt

Hiện tại, chúng ta đã có thể bật và tắt đèn 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 và tắt. Tôi đã điều tra màu sắc và độ sáng, vì tôi cũng đã thay đổi 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 chúng ta đ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 như một mã hex màu. 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 đã đổi đèn của mình thành 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 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 cho đèn, và chúng sẽ thực hiện lại các lệnh mà chúng ta đã thấy trong nhật ký của mì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 là hoán đổi các giá trị hex màu đó bằng của riêng mình. Lý do chúng ta không thể là do sự hiện diệ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 điều kiện 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 nối vào cuối gói tin thành byte thứ 20, và điều này được gửi đến thiết bị. Cuối cùng, thiết bị thực hiện phép toán XOR của riêng 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 có, nó biết dữ liệu đã đến như dự định và an toàn để thực thi.

Giao diện Wireshark cho thấy gói tin Bluetooth Low Energy (BLE) điều khiển màu sắc toàn bộ của đèn Govee H615B, bao gồm mã màu RGB và checksumGiao diện Wireshark cho thấy gói tin Bluetooth Low Energy (BLE) điều khiển màu sắc toàn bộ của đèn Govee H615B, bao gồm mã màu RGB và checksum

Hãy thử thay đổi đèn sang màu đỏ tía (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à những gì chúng ta muốn tính toán. Chúng ta bắt đầu với giá trị của một byte rỗng, hoặc 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 báo với byte trước đó.
  • Kết quả cuối cùng: 0xff (nhị phân: 11111111).

Chúng ta tiếp tục tính toán qua chuỗi cho đến khi đến cuối cùng. Điều này tạo ra 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 cho đèn của mình 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 sắc của đèn. Thay vào đó, chúng ta có thể tự động hóa quá 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, 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 giản là triển khai phép tính chúng ta đã thực hiện ở 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 sắc.

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 rất dễ thực hiện khi chúng ta đã tìm ra cách tính toán nó. Ví dụ, để đặt độ sáng thành 100%, 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ể thực hiện 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, giải quyết 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.

Hành trình Reverse Engineering đầy Thử thách và Thú vị

Giao diện điều khiển web tự tạo cho phép điều khiển đèn Govee H615B trực tiếp từ trình duyệt, thể hiện thành quả của quá trình reverse engineeringGiao diện điều khiển web tự tạo cho phép điều khiển đèn Govee H615B trực tiếp từ trình duyệt, thể hiện thành quả của quá trình reverse engineering

Reverse engineering có thể 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 rất có thể, nếu bạn đang reverse engineering một thứ gì đó, thì bạn đang làm điều đó vì chưa ai làm trước đó. Tôi đã có thể lấy tất cả dữ liệu mình thu thập được thông qua quá trình này và 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 khi việc xác định những gì cần thay đổi và cách thay đổi tương đối đơn giản.

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 reverse engineering 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ự Govee H615B yêu cầu một ứng dụng độc quyền để điều khiển chúng. Tuy nhiên, việc tìm ra cách chúng hoạt động không phải là không thể, và với hàng giờ hoặc thậm chí hàng ngày mày mò bên 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 nhỏ vui vẻ đã biến thành một cuộc “khám phá” đầy gian nan 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 cơ chế điều khiển từ Home Assistant là điều đơn giản để bạn có thể sử dụng những chiếc đèn này giống như bất kỳ chiếc đèn 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 input slider hoặc một điều khiển Lovelace tùy chỉnh cho nguồn điệ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 ra bởi nó khi quét.

Nếu bạn sở hữu những chiếc đèn này, bạn có thể kiểm tra kho lưu trữ GitHub của tôi để tự điều khiển chúng một cách thoải mái từ bất kỳ thiết bị hỗ trợ Bluetooth nào của bạn. Tất cả những gì bạn cần là địa chỉ MAC (mà 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. Đó 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 những gì làm chúng hoạt động và cách bạn có thể tự mình kiểm soát chúng!

Tài liệu tham khảo:

Related posts

Notion Có Thực Sự Tốt Không? Lý Do Tôi Dừng Sử Dụng Sau Nhiều Năm Gắn Bó

Administrator

NAS TerraMaster F4-210: Giải Pháp Lưu Trữ Đa Năng Giá Tốt Nhất

Administrator

Plex Media Server: 4 Lý Do Nổi Bật Để Xây Dựng Thư Viện Media Cá Nhân Tại Gia

Administrator