MacBook Pro M4 Pro 16 inch, một trong những công cụ để phân tích và lập trình cho đèn Govee
Máy Tính

Hướng dẫn Reverse Engineer đèn Govee H615B: Kiểm soát đèn thông minh qua Bluetooth không cần ứng dụng

Trong hành trình xây dựng một hệ thống nhà thông minh toàn diện, việc tích hợp các thiết bị cũ hoặc thiết bị từ các nhà sản xuất khác nhau thường gặp phải nhiều thách thức. Cá nhân tôi đã dành nhiều công sức để đưa các phần cứng sẵn có vào Home Assistant, và hầu hết các thiết bị Tuya cũ của tôi đều hoạt động tốt với Local Tuya, trong khi nhiều dịch vụ tự host khác cũng dễ dàng cung cấp thông tin cho Home Assistant. Tuy nhiên, một trở ngại lớn đã xuất hiện với dải đèn Govee H615B của tôi. Điều này đã dẫn tôi vào một hành trình nghiên cứu và “reverse engineer” sâu rộng để tích hợp chúng như mọi thiết bị khác.

Govee cung cấp cả API web và API cục bộ. Nếu API web đáp ứng đủ nhu cầu, có lẽ tôi đã dừng lại và sử dụng nó. Thế nhưng, API này bị giới hạn tốc độ truy cập rất nhanh. Bạn muốn điều chỉnh độ sáng chính xác? Chúc may mắn! Bạn sẽ bị khóa truy cập trong một phút chỉ sau vài lần thay đổi trạng thái. Về phần API cục bộ, dù trên lý thuyết là có tồn tại, nhưng nó không thể được kích hoạt trên dải đèn Govee của tôi. Tôi đã thử mọi cách nhưng tùy chọn này hoàn toàn bị vô hiệu hóa trong cài đặt ứng dụng Govee.

Sau khi tìm hiểu về các dự án “reverse engineer” liên quan đến các thiết bị chiếu sáng Govee khác, tôi nghĩ mình nên thử sức với thiết bị của mình. Từ đó, hành trình của tôi bắt đầu, với trang bị là Wireshark và Python, để tìm hiểu cách thức hoạt động của những chiếc đèn này 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 hay không. 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 chúng ở cuối bài viết!

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

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

Bước đầu tiên trong quá trình “reverse engineer” 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ó trong tay những công cụ sau:

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

MacBook Pro M4 Pro 16 inch, một trong những công cụ để phân tích và lập trình cho đèn GoveeMacBook Pro M4 Pro 16 inch, một trong những công cụ để phân tích và lập trình cho đè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 tin rằng điều đó là khả thi, vì vậy tôi tạm thời gạt phương án này sang một bên 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ó cơ chế xác thực phức tạp để điều khiển những chiếc đèn này, tôi hoàn toàn có thể tự phát các lệnh của riêng mình đến đèn để kiểm soát chúng.

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

Thu thập dữ liệu giao tiếp Bluetooth

Sử dụng tính năng Bluetooth HCI Logger trên Android 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à 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ó các phần cứng chuyên dụng để “ngửi” gói tin Bluetooth khi đang truyền tải, nhưng tôi không có những phần cứng đó, và Android có một tính năng Bluetooth HCI logger 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à bản ghi này là một bản ghi cấp thấp của mọi thứ điện thoại của bạn gửi đi và nhận về. Bạn có thể bật tính năng này trong tùy chọn nhà phát triển.

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. Bản ghi được lưu trữ ở đâu? Cách thông thường để truy cập nó ngày nay là 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à bản ghi nên được lưu trong thư mục nhật ký Bluetooth, nhưng điều này không xảy ra trên 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 nhà sản xuất gốc (OEM). Chuyển sang Google Pixel 8 Pro đã giúp tôi thu được một bản ghi Bluetooth HCI thực sự chứa dữ liệu.

Điện thoại Google Pixel 8 Pro được sử dụng để thu thập log Bluetooth HCI, dữ liệu quan trọng trong quá trình reverse engineer đèn GoveeĐiện thoại Google Pixel 8 Pro được sử dụng để thu thập log Bluetooth HCI, dữ liệu quan trọng trong quá trình reverse engineer đèn Govee

Tuy nhiên, trước khi lấy bản ghi, nó cần được điền đầy dữ liệu hữu ích. 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, do đó dữ liệu sẽ được lưu trong bản ghi. 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 của tôi vào laptop và kéo báo cáo lỗi bằng adb.

Phân tích cơ bản với Wireshark để hiểu giao thức

Bắt đầu từ những thông tin nhỏ nhất

Bản ghi Bluetooth HCI sẽ được đặt 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 tin L2CAP. Những gói tin này mô tả 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 (BLE) 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 Python với thư viện Bleak đang quét các thiết bị Bluetooth LE và truy vấn các characteristic, giúp xác định đèn Govee H615BGiao diện Python với thư viện Bleak đang quét các thiết bị Bluetooth LE và truy vấn các characteristic, giúp xác định đèn Govee H615B

Tôi đã viết một công cụ 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ó để lấy các “characteristic”. Các characteristic về cơ bản là các profile. Một client có thể khởi tạo các lệnh nhắm mục tiêu vào một characteristic và có thể nhận phản hồi, còn một server có thể chấp nhận các lệnh đó và thực hiện hành độ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 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 bản ghi.

Trong quá trình quét, khá dễ dàng phát hiện đèn Govee H615B. Tôi đã xác định được một số characteristic, cung cấp cho tôi thông tin cần thiết để nghiên cứu sâu hơn trong bản ghi mà tôi đã sao chép từ điện thoại. Chúng 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 characteristic kết thúc bằng “b10” và “b11” có khả năng liên quan đến nhau, tương tự như hai characteristic có “c1” và “c2”. Chúng ta sẽ tập trung vào hai characteristic 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, có vẻ như hai characteristic 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 điểm cần lưu ý khác khi nói đến các thiết bị này: mặc dù chúng đều tương tự nhau về cách tương tác, nhưng có vẻ như mỗi thiết bị lại hơi khác một chút về cách chấp nhận lệnh. Một số có các đoạn riêng 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ó 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 Google 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ó thể coi là một lỗ hổng bảo mật (mặc dù trên bề mặt không gây hại nhiều), nhưng chúng ta sẽ có thể tận dụng nó.

Khám phá các gói tin quan trọng

Gói tin Notification được ghi lại bằng Wireshark từ đèn Govee, cung cấp thông tin quan trọng về các characteristic để điều khiển trạng tháiGói tin Notification được ghi lại bằng Wireshark từ đèn Govee, cung cấp thông tin quan trọng về các characteristic để điều khiển trạng thái

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 mảnh ghép quan trọng để làm cho tất cả hoạt động. Bạn có nhớ characteristic tôi đã đề cập kết thúc bằng “b10” không? Thiết bị client (Google Pixel 8 Pro trong trường hợp này) được bộ điều khiển Bluetooth của đèn gửi một thông báo, và nó đến từ characteristic 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ó chứa giá trị sau:

  • aa010000000000000000000000000000000000ab

Đây dường như là các gói tin “keep-alive” (giữ kết nối), thông báo cho đèn Govee rằng chúng ta vẫn đang tìm kiếm để gửi và nhận thông tin. Chúng được gửi khoảng mỗi hai giây và chiếm phần lớn các bản ghi. 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 laptop của tôi chỉ trong vài giây. Với việc đ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ự như vậy, tôi cho rằng chúng phải là các gói tin keep-alive. Điều này cũng phù hợp với những gì các thiết bị Govee khác dường như thực hiện.

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 bằng số không để đạt được độ dài gói tin đó. Chúng ta có thể kết luận tại thời điểm này 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 chúng sau.

Phân tích gói tin bật đèn Govee trong Wireshark, hiển thị cấu trúc dữ liệu hex để kích hoạt thiết bịPhân tích gói tin bật đèn Govee trong Wireshark, hiển thị cấu trúc dữ liệu hex để kích hoạt thiết bị

Tiếp theo, chúng ta sẽ xem xét các lệnh mà tôi đã gửi. Khi tôi mở ứng dụng lần đầu tiên và kết nối với đèn, tôi đã bật và tắt chúng. Trong các bản ghi, phần dữ liệu đầu tiên được truyền đi 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 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 luận rằng hai giá trị để bật và tắt đèn là như sau:

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

Hãy chú ý đến cấu trúc của gói tin; một lần nữa, chúng ta có rất nhiều byte bằng không và hai byte khác nhau ở cuối. Để dễ hiểu, chúng ta dùng “0x” để biểu thị một giá trị thập lục phân (hexadecimal), sử dụng hệ 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 thập phân thông thường, và 0x33 là “51” trong 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 kiểm soát trạng thái bật/tắt. Chúng ta đã có đủ thông tin để thử nghiệm bằng cách thiết lập bộ nhận 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 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.

Thiết lập màu sắc và độ sáng đèn

Vượt qua giới hạn 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ó công tắc bật/tắt. Tôi đã nghiên cứu về 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 mình đang nhận một lệnh. Tôi không chắc về 050d, nhưng “fe0e1f” trông giống như một mã màu hex. Khi tôi chuyển đổi nó từ hex sang một màu thực tế, nó là màu đỏ rất nổi bật, và tôi đã thay đổ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ế “fe0e1f” 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 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 bản ghi 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 thay đổi các giá trị mã màu hex đó bằng 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 đó.

Gói tin điều chỉnh màu sắc đèn Govee được hiển thị trên Wireshark, cho thấy mã hex màu và cấu trúc gói tin cần checksumGói tin điều chỉnh màu sắc đèn Govee được hiển thị trên Wireshark, cho thấy mã hex màu và cấu trúc gói tin cần checksum

Byte cuối cùng đó là một checksum, về cơ bản xác nhận rằng dữ liệu đã đến trong trạng thái hoàn chỉnh và không bị lỗi. Nó được tính toán bằng cách thực hiện một phép toán XOR tích lũy trên mỗi byte. Một phép 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ả 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 của riêng mình 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 rằng dữ liệu đã đến như dự định và an toàn để thực thi.

Hãy thử thay đổi đèn sang màu đỏ tươi (magenta), mã hex #FF00FF. Gói tin 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 các phép toán trên chuỗi cho đến khi đạt được byte 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 đế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 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 đã thực hiện 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 một checksum để thêm vào phần còn lại của chuỗi. Tôi sẽ không làm bạn 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 đã 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 sắc.

Điều chỉnh độ sáng

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 duy nhất, dao động từ 00 (0) đến FF (255). Checksum một lần nữa 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 nó. Ví dụ, để đặt độ sáng lên 100% chỉ là:

  • 3304ff00000000000000000000000000000000c8

Thành quả

Bây giờ 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! Điều này 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 hệ thống nhà thông minh của mình.

Reverse Engineering: Hành trình dài và đầy thú vị

Giao diện điều khiển đèn Govee H615B từ trình duyệt web, minh họa thành quả của quá trình reverse engineering và tích hợp vào hệ thống nhà thông minhGiao diện điều khiển đèn Govee H615B từ trình duyệt web, minh họa thành quả của quá trình reverse engineering và tích hợp vào hệ thống nhà thông minh

Reverse engineering có thể khó khăn và bạn có thể gặp nhiều trở ngại trên đường đi. 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 engineer” một thứ gì đó, thì bạn đang làm điều đó vì chưa có ai khác làm trước đây. Tôi đã có thể tổng hợp tất cả dữ liệu thu thập được 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 là quá trình xác định những gì cần thay đổi và cách thay đổi tương đối đơn giản.

Mục đích 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 engineer” một thứ gì đó 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ể đột phá 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ẻ cuối cùng đã 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 là rất đơ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 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 (đang 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 này, bạn có thể truy cập kho lưu trữ GitHub của tôi để tự mình đ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 (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 ra cách chúng hoạt động và cách bạn có thể tự mình kiểm soát chúng!

Related posts

8 Mẹo Tiết Kiệm Thời Gian Và Tăng Năng Suất Vượt Trội Trên Windows

Administrator

CPU Usage Cao Khi Chơi Game: Không Phải Lúc Nào Cũng Là Bottleneck!

Administrator

Màn hình Samsung Odyssey Ark 55 inch: Cơ Hội Sở Hữu Với Deal Giảm Sốc 1200 USD

Administrator