アンビエント TV に慣れていない人にとっては、テレビ画面の端とそのすぐ近くからのジャンプを和らげて、より没入感のある体験を提供する方法です。私はいくつかの LED ライトを置いていたので、コードを介してライトを制御し、コンピューターの画面をアンビエント モニターにすることができるかどうかを確認することにしました。モニターに使用したかったのですが、音声反応やランダムパターンなど、ライトが持つ可能性のある他の機能を含め、どこでも使用でき、送信できる色は何でも使用できます.以前のモニターで使用していたので、しばらくこの投稿を書くつもりでしたが、新しいモニターに追加することはできませんでした。それは便利です。それでは始めましょう! (注意してください、LED ライトは Bluetooth Low Energy (BLE) である可能性が高いため、それらを操作するにはコンピュータが BLE をサポートしている必要があります)。完全なコードはGitHubにあります。
最初に行う必要があるのは、ライトに付属のアプリが期待どおりに機能していることを確認することです。これは、ライトの元のアプリを実行し、アプリで押しているオン/オフ/照明ボタンに応じてライトが反応することを確認することで簡単にテストできます。これを行うのは、ライトの Bluetooth レシーバーに送信された特定のコードをすぐに押して検出するためです。
私が取ることができる2つのアプローチがあります。 1 つは、アプリの JAR ファイルを逆コンパイルして、送信されたコードを見つけることでしたが、Bluetooth プロトコルについてもっと知りたかったので、Android ですべての Bluetooth アクティビティをログに記録し、そこから抽出することにしました。方法は次のとおりです。
Android デバイスで開発者向けオプションを有効にします。
Bluetooth HCI スヌープ ログを有効にします (HCI は Host-Controller Interface の略です)。このオプションは、[設定] > [システム] > [開発者]で見つけるか、下の画像のように設定で検索できます。
次に、特定のアクションを実行して、各アクションがライトの Bluetooth レシーバーに送信するものを識別できるようにする必要があります。オン/レッド/グリーン/ブルー/オフの順に簡単に説明しますが、ライトが他の機能をサポートしている場合は、それらをいじることもできます。
アプリを実行し、[オン]、[赤]、[緑]、[青]、および [オフ] を押します。デバイスで多くの Bluetooth アクティビティが発生している場合は、フィルタリングを容易にするために、おおよその時間を監視することも役立つ場合があります。
ノイズが入らないように Bluetooth をオフにします。次の手順では、Bluetooth コマンドを分析します。押した順序がわかっているので、どの値がどのボタンの押下に対応しているかを調べることができます。
次に、電話機の Bluetooth ログにアクセスする必要があります。これを行うにはいくつかの方法がありますが、バグ レポートを生成してエクスポートします。これを行うには、電話機の設定で USB デバッグを有効にし、電話機をコンピュータに接続して、adb.exe コマンド ライン ツールを使用します。
adb bugreport led_bluetooth_report
これにより、コンピューターのローカル ディレクトリに「 led_bluetooth_report.zip 」というファイル名の zip ファイルが生成されます。必要に応じてパスを指定できます (例: C:\MyPath\led_bluetooth_report")
この zip 内には、必要なログがあります。これはデバイスによって異なる場合があります (デバイスの他の場所で見つかった場合はコメントしてください)。私のGoogle Pixel電話では、 FS\data\misc\bluetooth\logs\btsnoop_hci.logにありました
ログファイルができたので、分析してみましょう。これを行うために、Wireshark を使用することにしたので、Wireshark を起動し、File...Open... に移動して、btsnoop_hci ログ ファイルを選択します。
難しそうに見えるかもしれませんが、 BTL2CAPを0x0004 でフィルタリングすることで、探しているものを簡単に見つけられるようにしましょう。これは、 Wireshark ソース コードの属性プロトコルです。属性プロトコルは、2 つの BLE デバイスが互いに通信する方法を定義するため、アプリがライトと通信する方法を見つけるために必要なものです。上部近くの「 Apply a display filter 」バーにbtl2cap.cid == 0x0004 と入力して Enter キーを押すと、Wireshark でログをフィルタリングできます。
これで、ログがフィルタリングされました。コマンドの検索が容易になるはずです。タイムスタンプを確認できます (間違った形式の場合は、[表示] [時刻表示形式] [時刻] に移動して時刻を変換します)。 Sent Write Command ログは、ライトに値を送信したログです。最近の時間が一番下にあると仮定すると、最後の 5 つのイベントまで下にスクロールします。これらは、オン、赤、緑、青、オフの順で、オフが最後になります。
すぐに必要になるので、Destination BD_ADDR をメモし、Sherlock Holmes の帽子をかぶってください。これは、メッセージ内で色とオン/オフ コマンドがどのようにエンコードされるかのパターンを解き放つ必要がある場所だからです。これはライトのメーカーによって異なりますが、私のデバイスで取得した値のリストは次のとおりです。
これらは明らかに 16 進数値であり、注意深く見るといくつかの固定パターンがあることがわかります。物事がより明確になるはずなので、パターンを分割しましょう。
純粋な赤、緑、青の 16 進数値に精通している人は、値がそれぞれ #FF000、#00FF00、#0000FF であることを知っているでしょう。これはまさに上で見たものです。これは、色を好きなように変更するための形式がわかったということです。 (または、少なくともライト自体ができることまで)。また、On と Off は色の形式が異なり、互いに似ていることがわかります。On は f00001、Off は 00000 です。
それでおしまい!これで、コーディングとライトの操作を開始するのに十分な情報が得られました。
必要なものは次の 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 進数の色) を書き込むために必要な特性が得られました。
ようやく必要な部品がすべてそろいました。この段階で、どの色の入力を使用するかを工夫することができます。たとえば、ライトをトレーディング マーケット 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 ライトで創造性を発揮する方法についてのアイデアが得られることを願っています。
フィードバックや質問がある場合は、以下にコメントしてください。
こちらにも掲載