JINS MEMEを買ったのでリアルタイムデータをPythonに取り込んでみた

NALGO研究部の笹川です。

先日JINS MEMEを買ってみたので、何かしらデータを取得して遊んでみようと思いました。

※JINS MEME ([公式サイト](https://jinsmeme.com/))は、メガネブランドJINSから販売されているセンサー付きのメガネ製品です。良い姿勢で作業した時間割合を記録する等、「ココロとカラダのセルフケアメガネ」としての機能があるみたいです！

最終目標は社内Web会議で手軽、低負荷に使えるアバターカメラです。[^vtuner]

[^vtuner]: 元々VTUNERを試してみたくて購入したが、Google Meet等に仮想カメラで入力するような機能は無かった。

ひとまずメガネをかけてドキュメントを読んだり、既存の記事を当たったりしてみると、Unityを使用したりiOSアプリをビルドしたりする物のようで、少し腰が重い感じ。

また、[現在はSDKが非公開](https://jins-meme.github.io/sdkdoc2/sdk_common.html)のようです。
> JINS MEMEのSDKは非公開になっています。ご利用を希望の方はサポート宛にお問い合わせください。

どうしたものか[^tsukkomi]と考えていると、[Loggerアプリによるデータ取得](https://jins-meme.github.io/sdkdoc2/logger/logger_app.html)という記載があり、どうやらJINS側で公開しているiOSアプリから任意のプログラムに対してWebSocketでデータを送ってくれるようです。今回はこちらを使います。

[^tsukkomi]: サポートに問い合わせれば良いのだけど

## 今回の構成

以下のように手持ちのスマホからデスクトップPC上のPythonプログラムにWebSocket接続します。Pythonを使う理由は特にないですが、たまたまWebSocketサーバーのサンプルプログラムを持っていたのでPythonにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238582/94042012-3e75-3755-1cc0-214a78f42b40.png)

## Pythonプログラム

こちらが今回のPythonプログラムです。

```py
import asyncio

from websockets import serve  # type: ignore


async def handler(websocket) -> None:
    """WebSocketのmain処理。"""
    while True:
        msg = await websocket.recv()
        print("msg received")
        print(f"{msg}")  # 現状は受信したメッセージをprintするだけ


async def start_server() -> None:
    """WebSocket待ち受けを開始する."""
    print("Start")
    await serve(
        handler,
        "0.0.0.0",
        19999,
    )


if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(start_server())
    asyncio.get_event_loop().run_forever()
```

このプログラムを実行し、LoggerアプリからIPアドレスを指定して接続すると、以下のようなメッセージがダーッと流れてきます。

```
{"sequenceNumber":160,"accZ":-15.375,"blinkSpeed":0,"yaw":0,"accY":8.3125,"eyeMoveDown":0,"fitError":0,"accX":-1.625,"noiseStatus":false,"pitch":0,"eyeMoveRight":0,"roll":0,"powerLeft":5,"walking":false,"blinkStrength":0,"eyeMoveLeft":0,"eyeMoveUp":0}
msg received
{"blinkSpeed":0,"eyeMoveLeft":0,"eyeMoveRight":0,"accX":-3.125,"pitch":0,"noiseStatus":false,"yaw":0,"roll":0,"fitError":0,"powerLeft":5,"accZ":-14.0625,"walking":false,"eyeMoveUp":0,"accY":7.9375,"blinkStrength":0,"sequenceNumber":161,"eyeMoveDown":0}
msg received
{"sequenceNumber":162,"accZ":-14.1875,"blinkSpeed":0,"yaw":0,"accY":7.0625,"eyeMoveDown":0,"fitError":0,"accX":-3.125,"noiseStatus":false,"pitch":0,"eyeMoveRight":0,"roll":0,"powerLeft":5,"walking":false,"blinkStrength":0,"eyeMoveLeft":0,"eyeMoveUp":0}
msg received
{"sequenceNumber":163,"accZ":-16.75,"blinkSpeed":0,"yaw":0,"accY":7.375,"eyeMoveDown":0,"fitError":0,"accX":0,"noiseStatus":false,"pitch":0,"eyeMoveRight":0,"roll":0,"powerLeft":5,"walking":false,"blinkStrength":0,"eyeMoveLeft":0,"eyeMoveUp":0}
msg received
{"blinkSpeed":0,"eyeMoveLeft":0,"eyeMoveRight":0,"accX":1.75,"noiseStatus":false,"pitch":0,"yaw":0,"powerLeft":5,"fitError":0,"walking":false,"accZ":-16.9375,"roll":0,"accY":7.75,"blinkStrength":0,"eyeMoveUp":0,"sequenceNumber":164,"eyeMoveDown":0}
msg received
{"roll":0,"yaw":0,"blinkStrength":0,"accZ":-16.75,"accY":8,"blinkSpeed":0,"fitError":0,"powerLeft":5,"walking":false,"eyeMoveLeft":0,"eyeMoveUp":0,"sequenceNumber":165,"accX":0.625,"noiseStatus":false,"pitch":0,"eyeMoveDown":0,"eyeMoveRight":0}
msg received
{"accY":6.0625,"eyeMoveLeft":0,"eyeMoveUp":0,"sequenceNumber":166,"eyeMoveRight":0,"noiseStatus":false,"blinkSpeed":0,"accX":-1.125,"pitch":0,"yaw":0,"accZ":-14.5625,"blinkStrength":0,"roll":0,"walking":false,"fitError":0,"powerLeft":5,"eyeMoveDown":0}
msg received
{"sequenceNumber":167,"accZ":-13.9375,"blinkSpeed":0,"yaw":0,"accY":3.1875,"eyeMoveDown":0,"fitError":0,"accX":-2.375,"noiseStatus":false,"pitch":0,"eyeMoveRight":0,"roll":0,"powerLeft":5,"walking":false,"blinkStrength":0,"eyeMoveLeft":0,"eyeMoveUp":0}
msg received
{"roll":0,"yaw":0,"blinkStrength":0,"accZ":-14.625,"accY":1.125,"blinkSpeed":0,"fitError":0,"powerLeft":5,"walking":false,"eyeMoveLeft":0,"eyeMoveUp":2,"sequenceNumber":168,"accX":-1.9375,"noiseStatus":true,"pitch":0,"eyeMoveDown":0,"eyeMoveRight":2}
msg received
{"sequenceNumber":169,"accZ":-14.9375,"blinkSpeed":0,"yaw":0,"accY":-0.0625,"eyeMoveDown":0,"fitError":0,"accX":-1.75,"noiseStatus":true,"pitch":0,"eyeMoveRight":0,"roll":0,"powerLeft":5,"walking":false,"blinkStrength":0,"eyeMoveLeft":0,"eyeMoveUp":0}
```

次回はこれらの値から頭や目の角度といった情報を取り出してみたいと思います。(姿勢推定のような計算が必要？)