paint-brush
Python で LED ライトをリバース エンジニアリングして、コンピューター モニターをアンビエント モニターに変換する@reaminated
2,401 測定値
2,401 測定値

Python で LED ライトをリバース エンジニアリングして、コンピューター モニターをアンビエント モニターに変換する

Reaminated12m2023/03/31
Read on Terminal Reader

長すぎる; 読むには

通常のモニターを、現在画面に表示されている色に合わせてライトが変化するアンビエント モニターに変換します。 Python と RGB ライトを使用して、映画やゲームの体験を向上させる方法を示すチュートリアルです。
featured image - Python で LED ライトをリバース エンジニアリングして、コンピューター モニターをアンビエント モニターに変換する
Reaminated HackerNoon profile picture
0-item

序章

アンビエント TV に慣れていない人にとっては、テレビ画面の端とそのすぐ近くからのジャンプを和らげて、より没入感のある体験を提供する方法です。私はいくつかの LED ライトを置いていたので、コードを介してライトを制御し、コンピューターの画面をアンビエント モニターにすることができるかどうかを確認することにしました。モニターに使用したかったのですが、音声反応やランダムパターンなど、ライトが持つ可能性のある他の機能を含め、どこでも使用でき、送信できる色は何でも使用できます.以前のモニターで使用していたので、しばらくこの投稿を書くつもりでしたが、新しいモニターに追加することはできませんでした。それは便利です。それでは始めましょう! (注意してください、LED ライトは Bluetooth Low Energy (BLE) である可能性が高いため、それらを操作するにはコンピュータが BLE をサポートしている必要があります)。完全なコードはGitHubにあります。

高レベルの手順

  • LED ライトの Bluetooth レシーバーが受け入れるコマンドを調べる
  • コンピューターの Bluetooth 経由で LED ライトにコマンドを送信する
  • 現在の画面のドミナント カラーを取得する
  • ドミナント カラーを LED ライトに送信する

前提条件

  • Bluetooth 対応の RGB LED ライトと付随するアプリ (私は Android を使用しています。iOS では、ここで説明する方法以外の方法が必要になる可能性がありますが、Wireshark を直接使用して Bluetooth トラフィックを監視できるはずです)。これらのライトをモニターの背面に取り付けました
  • ワイヤーシャーク
  • Android の SDK ツール(具体的には adb.exe)
  • 開発者ツール (私は Python 3.10 を使用しますが、どの 3.x バージョンでも動作するはずですが、原則は好きな言語で同じでなければなりません)
  • BLE コマンドを送信するデバイス (BLE をサポートするラップトップなど)


Bluetooth データの取得

最初に行う必要があるのは、ライトに付属のアプリが期待どおりに機能していることを確認することです。これは、ライトの元のアプリを実行し、アプリで押しているオン/オフ/照明ボタンに応じてライトが反応することを確認することで簡単にテストできます。これを行うのは、ライトの Bluetooth レシーバーに送信された特定のコードをすぐに押して検出するためです。


私が取ることができる2つのアプローチがあります。 1 つは、アプリの JAR ファイルを逆コンパイルして、送信されたコードを見つけることでしたが、Bluetooth プロトコルについてもっと知りたかったので、Android ですべての Bluetooth アクティビティをログに記録し、そこから抽出することにしました。方法は次のとおりです。


  1. Android デバイスで開発者向けオプションを有効にします。


  2. Bluetooth HCI スヌープ ログを有効にします (HCI は Host-Controller Interface の略です)。このオプションは、[設定] > [システム] > [開発者]で見つけるか、下の画像のように設定で検索できます。


HCI スヌープ ログの有効化


  1. 次に、特定のアクションを実行して、各アクションがライトの Bluetooth レシーバーに送信するものを識別できるようにする必要があります。オン/レッド/グリーン/ブルー/オフの順に簡単に説明しますが、ライトが他の機能をサポートしている場合は、それらをいじることもできます。


  2. アプリを実行し、[オン]、[赤]、[緑]、[青]、および [オフ] を押します。デバイスで多くの Bluetooth アクティビティが発生している場合は、フィルタリングを容易にするために、おおよその時間を監視することも役立つ場合があります。


送信するコマンドの記録


  1. ノイズが入らないように Bluetooth をオフにします。次の手順では、Bluetooth コマンドを分析します。押した順序がわかっているので、どの値がどのボタンの押下に対応しているかを調べることができます。


  2. 次に、電話機の Bluetooth ログにアクセスする必要があります。これを行うにはいくつかの方法がありますが、バグ レポートを生成してエクスポートします。これを行うには、電話機の設定で USB デバッグを有効にし、電話機をコンピュータに接続して、adb.exe コマンド ライン ツールを使用します。


     adb bugreport led_bluetooth_report


  3. これにより、コンピューターのローカル ディレクトリに「 led_bluetooth_report.zip 」というファイル名の zip ファイルが生成されます。必要に応じてパスを指定できます (例: C:\MyPath\led_bluetooth_report")


  4. この zip 内には、必要なログがあります。これはデバイスによって異なる場合があります (デバイスの他の場所で見つかった場合はコメントしてください)。私のGoogle Pixel電話では、 FS\data\misc\bluetooth\logs\btsnoop_hci.logにありました


  5. ログファイルができたので、分析してみましょう。これを行うために、Wireshark を使用することにしたので、Wireshark を起動し、File...Open... に移動して、btsnoop_hci ログ ファイルを選択します。


難しそうに見えるかもしれませんが、 BTL2CAP0x0004 でフィルタリングすることで、探しているものを簡単に見つけられるようにしましょう。これは、 Wireshark ソース コードの属性プロトコルです。属性プロトコルは、2 つの BLE デバイスが互いに通信する方法を定義するため、アプリがライトと通信する方法を見つけるために必要なものです。上部近くの「 Apply a display filter 」バーにbtl2cap.cid == 0x0004 と入力して Enter キーを押すと、Wireshark でログをフィルタリングできます。


ログをフィルタリングして簡単にする


これで、ログがフィルタリングされました。コマンドの検索が容易になるはずです。タイムスタンプを確認できます (間違った形式の場合は、[表示] [時刻表示形式] [時刻] に移動して時刻を変換します)。 Sent Write Command ログは、ライトに値を送信したログです。最近の時間が一番下にあると仮定すると、最後の 5 つのイベントまで下にスクロールします。これらは、オン、赤、緑、青、オフの順で、オフが最後になります。


BS_ADDR と値 - 必要な情報!


すぐに必要になるので、Destination BD_ADDR をメモし、Sherlock Holmes の帽子をかぶってください。これは、メッセージ内で色とオン/オフ コマンドがどのようにエンコードされるかのパターンを解き放つ必要がある場所だからです。これはライトのメーカーによって異なりますが、私のデバイスで取得した値のリストは次のとおりです。


  • オン: 7e0404f00001ff00ef
  • 赤: 7e070503ff000010ef
  • 緑: 7e07050300ff0010ef
  • 青: 7e0705030000ff10ef
  • オフ: 7e0404000000ff00ef


これらは明らかに 16 進数値であり、注意深く見るといくつかの固定パターンがあることがわかります。物事がより明確になるはずなので、パターンを分割しましょう。


  • オン: 7e0404 f00001 ff00ef
  • 赤: 7e070503 ff0000 10ef
  • 緑: 7e070503 00ff00 10ef
  • 青: 7e070503 0000ff 10ef
  • オフ: 7e0404 000000 ff00ef


純粋な赤、緑、青の 16 進数値に精通している人は、値がそれぞれ #FF000、#00FF00、#0000FF であることを知っているでしょう。これはまさに上で見たものです。これは、色を好きなように変更するための形式がわかったということです。 (または、少なくともライト自体ができることまで)。また、On と Off は色の形式が異なり、互いに似ていることがわかります。On は f00001、Off は 00000 です。

それでおしまい!これで、コーディングとライトの操作を開始するのに十分な情報が得られました。

LEDライトへの接続

必要なものは次の 3 つです。


  • デバイスのアドレス (これは上記の宛先 BD_ADDR です)

  • デバイスに送信する値 (上記で取得した 16 進数値)

  • 変更したい特性。 Bluetooth LE 特性は、ホストとクライアントの Bluetooth デバイス間で送信できるデータを本質的に定義するデータ構造です。ライトを参照する特性 (16 ビットまたは 128 ビットの UUID) を見つける必要があります。ここで見つけることができる一般的に使用される割り当てられた番号がいくつかありますが、デバイスがそれらに準拠していない限り、カスタム UUID を使用している可能性があります。私のライトは割り当てられた番号のリストにないので、コードで見つけてみましょう。


私は Python 3.10 とBleak 0.20.1を使用しています。コンピューターの Bluetooth がオンになっていることを確認します (デバイスとペアリングする必要はありません。コードを使用して接続します)。


 # 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)


コードについてコメントしたので、一目瞭然ですが、本質的には、ライトに接続して、それが公開するすべての特性を見つけます。私の出力は次のとおりです。


Characteristic: 00002a00-0000-1000-8000-00805f9b34fb Characteristic: 00002a01-0000-1000-8000-00805f9b34fb Characteristic: 0000fff3-0000-1000-8000-00805f9b34fb Characteristic: 0000fff4-0000-1000-8000-00805f9b34fb


最初の 2 つの UUID を Google で簡単に調べると、これがサービスの名前と外観を参照していることがわかりますが、これは私たちには関係ありません。ただし、このページによると、3 番目 ( 0000fff3-0000-1000-8000-00805f9b34fb ) が書き込み特性であり、3 番目と 4 番目が最も適しているようです。これで、この特定のデバイスが値 (16 進数の色) を書き込むために必要な特性が得られました。

LED ライトの制御

ようやく必要な部品がすべてそろいました。この段階で、どの色の入力を使用するかを工夫することができます。たとえば、ライトをトレーディング マーケット API に接続して、ポートフォリオの状況に応じて色を変えることができます。この場合、モニターを周囲環境に対応させたいので、画面のドミナント カラーを取得して送信する必要があります。


これを行う方法はたくさんありますので、お好きなアルゴリズムを自由に試してみてください。最も単純なアプローチの 1 つは、画面全体で X 個のピクセルごとに反復して平均を取ることですが、より複雑なソリューションでは、人間の目がより支配的であると認識する色を探します。共有したい調査結果については、お気軽にコメントしてください。

このブログ投稿のために、 fast_colorthiefライブラリの get_dominant_color メソッドを使用して単純にします。


 ''' 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)


コードにはコメントが付けられているので、何が起こっているのかが明確になることを願っていますが、画面の中央から小さな領域を取得し、その領域から支配的な色を取得しています。小さいリージョンを使用する理由は、パフォーマンスのためです。より少ないピクセルを分析する必要があります。


もうすぐだ!これで、何を送信し、どこに送信するかがわかりました。実際に送信するという、この課題の最後の主要な部分を終了しましょう。幸いなことに、Bleak ライブラリを使用すると、これは非常に簡単です。


 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")


ログから発見したように、各色には固定のテンプレートがあるため、f-strings を使用して共通部分をハードコーディングし、中間の値に色の 16 進数を渡すだけです。これはループから呼び出すことができます。 On と Off には固有の 16 進数があるため、個別の関数を作成し、関連する 16 進数を含む定数値を渡しました。


 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)


そして、それがあります。 Bluetooth LED ライトは画面上の色によって制御され、独自のアンビエント モニターを作成します。


GitHubで完全なコードを確認できます。これには、この投稿に固有ではない少量のインフラストラクチャ コードが含まれています。コードにコメントを付けてわかりやすいようにしていますが、ご不明な点やご提案がございましたらお気軽にお寄せください。


願わくば、これにより、LED ライトで創造性を発揮する方法についてのアイデアが得られることを願っています。

今後の改善点

  • マルチカラー- ライト ストリップは一度に 1 つの色しか使用できませんが、それぞれ独自の色を持つ 4 つの象限を使用できる別のセットがあります。これは、モニターのセクションを画面上の 4 つのセクションと一致させて、より正確なアンビエンス設定を提供できることを意味します。これらのライトは Bluetooth ではなく Wifi で動作し、将来のプロジェクトになる可能性があります。
  • 明るさ- 簡単にするために、色の変化とオン/オフのコマンドを探しました。ただし、これは輝度制御コマンドを検出し、それをカラー アルゴリズムに投入することで簡単に改善できます。
  • パフォーマンス- ライトをリアルタイムで変化させたいので、パフォーマンスは重要です。特に人間が知覚した場合に、どの色が最も支配的であると見なされるかを検出するための複雑なアルゴリズムがいくつかあります (これは、色変換の全世界につながります)。ただし、これは非常に高速に実行する必要があるため、パフォーマンスと精度のバランスが必要です。将来の改善は、画面上のピクセルを直接分析するのではなく、グラフィック カードに直接アクセスしてバッファから読み取ることです。これが可能であれば、グラフィックス バッファから画面にかかる時間をなくして、ライトの反応を最適化することもできます。


フィードバックや質問がある場合は、以下にコメントしてください。


こちらにも掲載