paint-brush
Kỹ sư đảo ngược đèn LED với Python để chuyển đổi màn hình máy tính của bạn thành màn hình xung quanhtừ tác giả@reaminated
2,021 lượt đọc
2,021 lượt đọc

Kỹ sư đảo ngược đèn LED với Python để chuyển đổi màn hình máy tính của bạn thành màn hình xung quanh

từ tác giả Reaminated12m2023/03/31
Read on Terminal Reader

dài quá đọc không nổi

Chuyển đổi màn hình bình thường của bạn thành Màn hình xung quanh với đèn thay đổi cho phù hợp với màu hiện có trên màn hình. Sử dụng đèn Python và RGB, đây là hướng dẫn chỉ ra cách nâng cao trải nghiệm chơi game và phim của bạn.
featured image - Kỹ sư đảo ngược đèn LED với Python để chuyển đổi màn hình máy tính của bạn thành màn hình xung quanh
Reaminated HackerNoon profile picture
0-item

Giới thiệu

Đối với những người không quen thuộc với Ambient TV, đó là một cách để giảm bớt bước nhảy từ mép màn hình TV và môi trường xung quanh ngay lập tức để mang lại trải nghiệm sống động hơn. Tôi có một số đèn LED nằm xung quanh và quyết định xem liệu có thể điều khiển đèn thông qua mã hay không và biến màn hình máy tính của tôi thành một màn hình xung quanh. Mặc dù tôi muốn sử dụng nó cho màn hình của mình, nhưng nó có thể được sử dụng ở bất kỳ đâu và với bất kỳ màu nào bạn có thể gửi, bao gồm các tính năng khác mà đèn của bạn có thể có, chẳng hạn như phản ứng âm thanh hoặc mẫu ngẫu nhiên. Tôi đã định viết bài đăng này được một thời gian vì tôi đã sử dụng nó trên một màn hình cũ, nhưng tôi chưa bao giờ có ý định thêm nó vào màn hình mới của mình, vì vậy tôi đã ghi lại nó khi tôi thực hiện cho bất kỳ ai có thể tìm thấy nó hữu ích. Vì vậy, hay thực hiện ngay bây giơ! (Xin lưu ý, đèn LED có thể là Bluetooth Low Energy (BLE), vì vậy máy tính của bạn cần hỗ trợ BLE để tương tác với chúng). Mã đầy đủ có trên GitHub .

Các bước cấp cao

  • Tìm hiểu những lệnh mà bộ thu Bluetooth của đèn LED chấp nhận
  • Gửi lệnh tới đèn LED qua Bluetooth của máy tính của tôi
  • Lấy màu chủ đạo của màn hình hiện tại
  • Gửi màu chủ đạo cho đèn LED

điều kiện tiên quyết

  • Đèn LED RGB hỗ trợ Bluetooth và ứng dụng đi kèm (Tôi đang sử dụng Android, iOS có thể sẽ yêu cầu một phương pháp thay thế so với phương pháp được mô tả ở đây, nhưng có thể sử dụng Wireshark trực tiếp để theo dõi lưu lượng Bluetooth). Tôi đã gắn những đèn này vào mặt sau của màn hình
  • cá mập
  • Công cụ SDK của Android (cụ thể là adb.exe)
  • Các công cụ dành cho nhà phát triển (Tôi sẽ sử dụng Python 3.10, mặc dù mọi phiên bản 3.x đều hoạt động, nhưng các nguyên tắc phải giống với bất kỳ ngôn ngữ nào bạn thích)
  • Một thiết bị để gửi các lệnh BLE từ (ví dụ: máy tính xách tay hỗ trợ BLE)


Lấy dữ liệu Bluetooth

Bước đầu tiên chúng ta cần làm là đảm bảo rằng ứng dụng đi kèm với đèn hoạt động như mong đợi. Có thể dễ dàng kiểm tra điều này bằng cách chạy ứng dụng gốc của đèn và đảm bảo rằng đèn phản ứng tương ứng tùy thuộc vào các nút bật/tắt/chiếu sáng mà bạn đang nhấn trên ứng dụng của mình. Chúng tôi làm điều này vì chúng tôi sẽ sớm nhấn và phát hiện các mã cụ thể được gửi tới bộ thu Bluetooth trên đèn.


Có hai cách tiếp cận mà tôi có thể thực hiện. Một là dịch ngược tệp JAR của ứng dụng và tìm các mã đã được gửi, nhưng tôi muốn tìm hiểu thêm về giao thức Bluetooth, vì vậy tôi đã chọn ghi nhật ký tất cả hoạt động Bluetooth trên Android của mình và trích xuất nó từ đó. Đây là cách:


  1. Bật Tùy chọn nhà phát triển trên thiết bị Android của bạn.


  2. Bật nhật ký rình mò Bluetooth HCI (HCI là viết tắt của Giao diện điều khiển máy chủ). Bạn có thể tìm thấy tùy chọn này trong Cài đặt > Hệ thống > Nhà phát triển hoặc tìm kiếm nó trong cài đặt như trong hình bên dưới.


Kích hoạt nhật ký Snoop HCI


  1. Bây giờ, chúng ta cần thực hiện các hành động cụ thể để có thể xác định mỗi hành động sẽ gửi thông tin gì đến bộ thu Bluetooth của đèn. Tôi sẽ giữ cho nó đơn giản là Bật/Đỏ/Xanh lá cây/Xanh lam/Tắt, theo thứ tự đó, nhưng nếu đèn của bạn hỗ trợ các tính năng khác, bạn cũng có thể chơi đùa với những tính năng đó.


  2. Chạy ứng dụng và nhấn Bật, Đỏ, Lục, Lam và Tắt. Cũng có thể hữu ích khi theo dõi thời gian gần đúng để lọc dễ dàng hơn nếu bạn có nhiều hoạt động Bluetooth trên thiết bị của mình.


Ghi lệnh để gửi


  1. Tắt Bluetooth để chúng tôi không nhận thêm bất kỳ tiếng ồn nào. Trong các bước sau, chúng tôi sẽ phân tích các lệnh Bluetooth và khi biết thứ tự của những gì chúng tôi đã nhấn, chúng tôi có thể tìm ra giá trị nào tương ứng với nút nào được nhấn.


  2. Bây giờ chúng ta cần truy cập nhật ký Bluetooth trên điện thoại. Có một số cách để làm điều này, nhưng tôi sẽ tạo và xuất báo cáo lỗi. Để thực hiện việc này, hãy bật Gỡ lỗi USB trong Cài đặt của điện thoại, kết nối điện thoại với máy tính và sử dụng công cụ dòng lệnh adb.exe.


     adb bugreport led_bluetooth_report


  3. Thao tác này sẽ tạo một tệp zip trên thư mục cục bộ trên máy tính của bạn với tên tệp “ led_bluetooth_report.zip ”. Bạn có thể chỉ định đường dẫn nếu muốn (ví dụ: C:\MyPath\led_bluetooth_report”)


  4. Trong zip này là nhật ký mà chúng tôi cần. Điều này có thể khác nhau giữa các thiết bị (vui lòng nhận xét nếu bạn tìm thấy nó ở nơi khác trên thiết bị của mình). Trên điện thoại Google Pixel của tôi, nó nằm trong FS\data\misc\bluetooth\logs\btsnoop_hci.log


  5. Bây giờ chúng tôi có các tệp nhật ký, hãy phân tích chúng! Để làm điều này, tôi quyết định sử dụng Wireshark, vì vậy hãy khởi động Wireshark và đi tới Tệp...Mở...và chọn tệp nhật ký btsnoop_hci.


Mặc dù điều này có vẻ khó khăn, nhưng chúng ta hãy giúp bản thân dễ dàng tìm thấy những gì chúng ta đang tìm kiếm bằng cách lọc BTL2CAP trên 0x0004, là Giao thức thuộc tính trong mã nguồn Wireshark . Giao thức thuộc tính xác định cách hai thiết bị BLE giao tiếp với nhau, vì vậy đây là thứ chúng ta cần để giúp tìm ra cách ứng dụng giao tiếp với đèn. Bạn có thể lọc nhật ký trong Wireshark bằng cách nhập btl2cap.cid == 0x0004 vào thanh “ Áp dụng bộ lọc hiển thị ” gần trên cùng và nhấn Enter


Lọc các bản ghi để làm cho nó dễ dàng hơn


Bây giờ chúng tôi đã lọc nhật ký; nó sẽ làm cho việc tìm kiếm các lệnh dễ dàng hơn. Chúng ta có thể xem dấu thời gian (Vào View…Time Display Format…Time of Day để chuyển đổi thời gian nếu định dạng sai). Chúng tôi muốn xem nhật ký Lệnh ghi đã gửi vì đó là những nhật ký mà chúng tôi đã gửi một giá trị cho đèn. Giả sử thời gian gần đây nhất của bạn ở dưới cùng, hãy cuộn xuống năm sự kiện gần đây nhất. Các thứ tự này phải là Bật, Đỏ, Xanh lục, Xanh lam và Tắt theo thứ tự đó, với Tắt là cuối cùng.


BS_ADDR và Giá trị - Thông tin chúng tôi cần!


Hãy lưu ý Đích đến BD_ADDR, vì chúng tôi sẽ sớm cần đến nó và đội mũ Sherlock Holmes của bạn lên, vì đây là nơi chúng tôi cần mở khóa mẫu về cách mã hóa màu sắc và các lệnh bật/tắt trong tin nhắn. Điều này sẽ khác nhau tùy thuộc vào nhà sản xuất đèn nhưng đây là danh sách các giá trị tôi nhận được cho thiết bị của mình:


  • Bật: 7e0404f00001ff00ef
  • Đỏ: 7e070503ff000010ef
  • Màu xanh lục: 7e07050300ff0010ef
  • Màu xanh lam: 7e0705030000ff10ef
  • Tắt: 7e0404000000ff00ef


Đây rõ ràng là các giá trị thập lục phân và nếu xem xét kỹ, bạn sẽ thấy có một số mẫu cố định. Hãy tách các mẫu ra vì điều này sẽ làm cho mọi thứ rõ ràng hơn nhiều.


  • Bật: 7e0404 f00001 ff00ef
  • Đỏ: 7e070503 ff0000 10ef
  • Màu xanh lục: 7e070503 00ff00 10ef
  • Xanh lam: 7e070503 0000ff 10ef
  • Tắt: 7e0404 000000 ff00ef


Đối với những người quen thuộc với các giá trị thập lục phân của màu đỏ thuần, xanh lá cây và xanh lam, bạn sẽ biết rằng các giá trị lần lượt là #FF000, #00FF00 và #0000FF, chính xác như những gì chúng ta có thể thấy ở trên. Điều này có nghĩa là bây giờ chúng ta biết định dạng để thay đổi màu sắc thành bất cứ thứ gì chúng ta muốn! (hoặc ít nhất là khả năng của đèn). Chúng ta cũng có thể thấy rằng Bật và Tắt có định dạng khác với màu sắc và tương tự nhau, với Bật có f00001 và Tắt có 00000.

Đó là nó! Bây giờ chúng tôi có đủ thông tin để bắt đầu viết mã và tương tác với đèn.

Kết nối với đèn LED

Có ba điều quan trọng chúng ta cần:


  • Địa chỉ của thiết bị (đây là Đích BD_ADDR ở trên)

  • Các giá trị để gửi đến thiết bị (các giá trị thập lục phân thu được ở trên)

  • Các đặc tính chúng tôi muốn thay đổi. Đặc tính Bluetooth LE là cấu trúc dữ liệu về cơ bản xác định dữ liệu có thể được gửi giữa máy chủ và thiết bị Bluetooth của máy khách. Chúng ta cần tìm đặc tính (UUID 16 bit hoặc 128 bit) đề cập đến đèn. Có một số số được chỉ định thường được sử dụng có thể được tìm thấy ở đây nhưng trừ khi thiết bị tuân thủ các số đó, chúng có thể đang sử dụng UUID tùy chỉnh. Vì đèn của tôi không có trong danh sách số được chỉ định, hãy tìm nó qua mã.


Tôi đang sử dụng Python 3.10 và Bleak 0.20.1 . Đảm bảo Bluetooth trên máy tính của bạn được bật (không cần ghép nối với thiết bị, chúng tôi sẽ kết nối với thiết bị thông qua mã).


 # Function to create a BleakClient and connect it to the address of the light's Bluetooth reciever async def init_client(address: str) -> BleakClient: client = BleakClient(address) print("Connecting") await client.connect() print(f"Connected to {address}") return client # Function we can call to make sure we disconnect properly otherwise there could be caching and other issues if you disconnect and reconnect quickly async def disconnect_client(client: Optional[BleakClient] = None) -> None: if client is not None : print("Disconnecting") if characteristic_uuid is not None: print(f"charUUID: {characteristic_uuid}") await toggle_off(client, characteristic_uuid) await client.disconnect() print("Client Disconnected") print("Exited") # Get the characteristic UUID of the lights. You don't need to run this every time async def get_characteristics(client: BleakClient) -> None: # Get all the services the device (lights in this case) services = await client.get_services() # Iterate the services. Each service will have characteristics for service in services: # Iterate and subsequently print the characteristic UUID for characteristic in service.characteristics: print(f"Characteristic: {characteristic.uuid}") print("Please test these characteristics to identify the correct one") await disconnect_client(client)


Tôi đã nhận xét về mã, vì vậy nó sẽ dễ hiểu nhưng về cơ bản, chúng tôi kết nối với đèn và tìm tất cả các đặc điểm mà nó thể hiện. đầu ra của tôi là:


Đặc tính: 00002A00-0000-1000-8000-00805F9B34FB Đặc tính: 00002A01-0000-1000-8000-00805F9B34FB Đặc tính: 0000FFF3-0000-1000-8000-0080


Google nhanh chóng về hai UUID đầu tiên cho thấy điều này đề cập đến tên và hình thức của dịch vụ, điều này không liên quan đến chúng tôi. Tuy nhiên, thứ ba và thứ tư có vẻ phù hợp nhất với thứ ba ( 0000fff3-0000-1000-8000-00805f9b34fb ) là đặc tính ghi theo trang này . Tuyệt vời, giờ đây chúng tôi có đặc điểm mà chúng tôi cần cho thiết bị cụ thể này để ghi vào một giá trị (hệ thập lục phân màu).

Điều khiển đèn LED

Chúng tôi cuối cùng đã có tất cả những mảnh này chúng tôi cần. Ở giai đoạn này, bạn có thể sáng tạo với đầu vào màu nào bạn muốn sử dụng. Ví dụ: bạn có thể kết nối đèn với API thị trường giao dịch để thay đổi màu sắc tùy theo tình hình hoạt động của danh mục đầu tư của bạn. Trong trường hợp này, chúng tôi muốn làm cho màn hình của chúng tôi nhận biết được môi trường xung quanh, vì vậy chúng tôi cần lấy màu chủ đạo của màn hình và gửi màu đó qua.


Có nhiều cách để làm điều này, vì vậy hãy thoải mái thử nghiệm bất kỳ thuật toán nào bạn muốn. Một trong những cách tiếp cận đơn giản nhất là lặp lại mỗi X số pixel trên màn hình và lấy giá trị trung bình trong khi các giải pháp phức tạp hơn sẽ tìm kiếm màu sắc mà mắt người cho là nổi trội hơn. Hãy bình luận về bất kỳ phát hiện nào bạn muốn chia sẻ!

Vì lợi ích của bài đăng trên blog này, tôi sẽ làm cho nó đơn giản bằng cách sử dụng phương thức get_dominant_color của thư viện fast_colorthief .


 ''' Instead of taking the whole screensize into account, I'm going to take a 640x480 resolution from the middle. This should make it faster but you can toy around depending on what works for you. You may, for example, want to take the outer edge colours instead so it the ambience blends to the outer edges and not the main screen colour ''' screen_width, screen_height = ImageGrab.grab().size #get the overall resolution size region_width = 640 region_height = 480 region_left = (screen_width - region_width) // 2 region_top = (screen_height - region_height) // 2 screen_region = (region_left, region_top, region_left + region_width, region_top + region_height) screenshot_memory = io.BytesIO(b"") # Method to get the dominant colour on screen. You can change this method to return whatever colour you like def get_dominant_colour() -> str: # Take a screenshot of the region specified earlier screenshot = ImageGrab.grab(screen_region) ''' The fast_colorthief library doesn't work directly with PIL images but we can use an in memory buffer (BytesIO) to store the picture This saves us writing then reading from the disk which is costly ''' # Save screenshot region to in-memory bytes buffer (instead of to disk) # Seeking and truncating fo performance rather than using "with" and creating/closing BytesIO object screenshot_memory.seek(0) screenshot_memory.truncate(0) screenshot.save(screenshot_memory, "PNG") # Get the dominant colour dominant_color = fast_colorthief.get_dominant_color(screenshot_memory, quality=1) # Return the colour in the form of hex (without the # prefix as our Bluetooth device doesn't use it) return '{:02x}{:02x}{:02x}'.format(*dominant_color)


Đoạn mã được nhận xét, vì vậy, hy vọng rằng nó sẽ rõ ràng về những gì đang xảy ra, nhưng chúng tôi đang lấy một vùng màn hình nhỏ hơn từ giữa và sau đó lấy màu chủ đạo từ vùng đó. Lý do tôi lấy một khu vực nhỏ hơn là để thực hiện; ít pixel hơn sẽ cần được phân tích.


Chúng ta gần đến rồi! Bây giờ chúng tôi biết những gì để gửi nó và gửi nó ở đâu. Hãy hoàn thành phần quan trọng cuối cùng của thử thách này là gửi nó đi. May mắn thay, với thư viện Bleak, điều này khá đơn giản.


 async def send_colour_to_device(client: BleakClient, uuid: str, value: str) -> None: #write to the characteristic we found, in the format that was obtained from the Bluetooth logs await client.write_gatt_char(uuid, bytes.fromhex(f"7e070503{value}10ef")) async def toggle_on(client: BleakClient, uuid: str) -> None: await client.write_gatt_char(uuid, bytes.fromhex(ON_HEX)) print("Turned on") async def toggle_off(client: BleakClient, uuid: str) -> None: await client.write_gatt_char(uuid, bytes.fromhex(OFF_HEX)) print("Turned off")


Như chúng tôi đã phát hiện ra từ nhật ký, mỗi màu có một mẫu cố định, vì vậy chúng tôi có thể sử dụng chuỗi f để mã hóa cứng phần chung và chỉ cần chuyển một hệ thập lục phân của một màu cho giá trị ở giữa. Điều này có thể được gọi từ vòng lặp của chúng tôi. Bật và Tắt có các hệ thập lục phân duy nhất, vì vậy tôi đã tạo các hàm riêng lẻ và chuyển vào một giá trị không đổi có chứa hệ thập lục phân có liên quan.


 while True: # send the dominant colour to the device await send_colour_to_device(client, characteristic_uuid, get_dominant_colour()) # allow a small amount of time before update time.sleep(0.1)


Và chúng tôi đã có nó; đèn LED Bluetooth của chúng tôi hiện được điều khiển bằng màu sắc trên màn hình, tạo Màn hình xung quanh của riêng chúng tôi.


Bạn có thể xem mã đầy đủ trên GitHub , có một lượng nhỏ mã cơ sở hạ tầng không dành riêng cho bài đăng này. Tôi đã cố gắng nhận xét mã để tự giải thích nhưng vui lòng đặt bất kỳ câu hỏi nào hoặc đưa ra đề xuất.


Hy vọng rằng điều này mang lại cho bạn ý tưởng về cách bạn có thể bắt đầu sáng tạo với đèn LED của mình.

Những cải tiến trong tương lai

  • Nhiều màu - Các dải đèn của tôi chỉ có thể có một màu tại một thời điểm, nhưng tôi có một bộ khác có thể có bốn góc phần tư, mỗi góc có màu riêng. Điều này có nghĩa là có thể có các phần của màn hình khớp với bốn phần trên màn hình để tạo ra cài đặt môi trường chính xác hơn nữa. Những đèn đó chạy bằng Wifi thay vì Bluetooth và có thể là một dự án trong tương lai.
  • Độ sáng – để đơn giản, tôi chỉ tìm các lệnh thay đổi màu sắc và bật tắt. Tuy nhiên, điều này có thể dễ dàng được cải thiện bằng cách phát hiện các lệnh điều khiển độ sáng và đưa lệnh đó vào thuật toán màu.
  • Hiệu suất - Vì chúng tôi muốn đèn thay đổi theo thời gian thực nên hiệu suất là rất quan trọng. Có một số thuật toán phức tạp để phát hiện màu nào sẽ được coi là chiếm ưu thế nhất, đặc biệt là khi con người cảm nhận được (dẫn đến cả một thế giới chuyển đổi màu sắc). Tuy nhiên, vì điều này cần chạy khá nhanh nên cần có sự cân bằng giữa hiệu suất và độ chính xác. Một cải tiến trong tương lai có thể là thử và truy cập trực tiếp vào card đồ họa để đọc từ bộ đệm thay vì phân tích trực tiếp các pixel trên màn hình. Nếu có thể, bạn cũng sẽ loại bỏ thời gian chuyển từ bộ đệm đồ họa sang màn hình để có thể tối ưu hóa phản ứng của đèn.


Vui lòng bình luận bên dưới nếu bạn có bất kỳ phản hồi hoặc câu hỏi nào.


Cũng được xuất bản ở đây