入力: 衝撃

### 背景
メイン基板には加速度センサが搭載されているようです。
どのICかはわかりませんでした。（BMI055？）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/5982e869-7d53-6bba-72da-c05214b56cc8.png)

https://www.amazon.co.jp/dp/B0DGQSV2SG


今回は衝撃のイベント検出機能を入力として扱うプログラミングを行いました。

### Scratch
衝撃を検出するセンサは取得した値を変数として扱いになっているので、値を取得する命令をするブロックがあるわけではないようです。
代わりにイベントとして割り込みをかけるブロックがありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/fbdacecd-6872-e41d-bd50-ccf2da44ebd7.png)

「起動後に100秒間、待つ。
　その間に振ったり衝撃を加えるとFrontのLEDがマゼンタ色に1秒間点灯する。」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/bd2b89d8-bb5a-eb69-01dc-7d1c7a2ec32d.png)


「プログラム開始」のブロックはスタビライゼーションオフにしてから100秒待機させました。
同時に「衝突時」のブロックが走っていて、衝撃が加わったらときの動作を指定しました。

なお、プログラムの終了後にスタビライゼーションは自動的にオンになるようなので、
spheroの向きが変わらないように置いてあげましょう。

### spheroV2
ドキュメントにあったOn Collisionを参考に同様な動作をするスクリプトを作成しました。

https://spherov2.readthedocs.io/en/latest/sphero_edu.html#on-collision

EventType.on_collisionイベントにon_collisionメソッドを登録しました。

```
import time

from spherov2 import scanner
from spherov2.sphero_edu import EventType, SpheroEduAPI
from spherov2.types import Color

def on_collision(api):
    """衝突イベントの処理"""
    print("Collision detected!")
    api.set_front_led(Color(r=255, g=255, b=255)) 　# 色が違った
    time.sleep(1) 
    api.set_front_led(Color(r=0, g=0, b=0)) 

print("Testing Starting...")
print("Connecting to Bolt...")
toy = scanner.find_BOLT()

if toy is not None:
    print("Connected.")
    with SpheroEduAPI(toy) as api:
        # 衝突イベントのリスナーを登録
        api.register_event(EventType.on_collision, on_collision)

        api.set_stabilization(False)

        try:
            print("Running program. shake the robot.")
            time.sleep(100)  # 100秒間イベントを待機
        except KeyboardInterrupt:
            print("\nProgram interrupted by user.")
else:
    print("Failed to connect to Sphero.")
```

しかしエラーが発生しました。

```
git\spherov2.py\spherov2\test>python BoltTest_collision.py
Testing Starting...
Connecting to Bolt...
Connected.
Running program. shake the robot.
Exception in thread Thread-64:
Traceback (most recent call last):
  File "C:\ProgramData\miniforge3\Lib\threading.py", line 1075, in _bootstrap_inner
    self.run()
  File "C:\ProgramData\miniforge3\Lib\threading.py", line 1012, in run
    self._target(*self._args, **self._kwargs)
  File "git\spherov2.py\spherov2\commands\sensor.py", line 116, in __collision_detected_notify_helper
Exception in thread Thread-65:
Traceback (most recent call last):
  File "C:\ProgramData\miniforge3\Lib\threading.py", line 1075, in _bootstrap_inner
    unpacked = struct.unpack('>3hB3hBL', packet.data)
Exception in thread Thread-66:
Traceback (most recent call last):
  File "C:\ProgramData\miniforge3\Lib\threading.py", line 1075, in _bootstrap_inner
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
struct.error: unpack requires a buffer of 18 bytes
    self.run()
    self.run()
...
```

メッセージを手掛かりにchatGPTを使いながらデバッグをしていたところ、衝突イベント時に返ってくる信号のバイト数が安定して18にならず、14だったり16だったり、まちまちなことが分かりました。

信号の末尾のタイムスタンプが返ってこないことを想定して、先頭の13バイトだけ使用するようにsensor.pyにある__collision_detected_notify_helperを変更しました。

```
def __collision_detected_notify_helper(listener, packet):
        try:
            # パケット全体の内容とサイズを出力
            # print(f"Received packet: {packet.data} (Size: {len(packet.data)} bytes)")

            # パケットデータの先頭13バイトを使用
            trimmed_data = packet.data[:13]  # speedとタイムスタンプ部分を無視
            # print(f"Trimmed packet data: {trimmed_data} (Size: {len(trimmed_data)} bytes)")

            # 13バイトデータをアンパック
            unpacked = struct.unpack('>3hB3h', trimmed_data)
            # print(f"Unpacked data: {unpacked}")

             # 加速度データをデバッグ出力
            acceleration_x = unpacked[0] / 4096
            acceleration_y = unpacked[1] / 4096
            acceleration_z = unpacked[2] / 4096

            # 衝突方向フラグを解析
            x_axis = bool(unpacked[3] & 1)
            y_axis = bool(unpacked[3] & 2)

            # 条件に応じて加速度を出力
            if x_axis:
                print(f"x_axis=True: Acceleration X = {acceleration_x}")
            if y_axis:
                print(f"y_axis=True: Acceleration Y = {acceleration_y}")

            # リスナーにデータを渡す
            listener(CollisionDetected(
                acceleration_x=unpacked[0] / 4096,
                acceleration_y=unpacked[1] / 4096,
                acceleration_z=unpacked[2] / 4096,
                x_axis=bool(unpacked[3] & 1),
                y_axis=bool(unpacked[3] & 2),
                power_x=unpacked[4],
                power_y=unpacked[5],
                power_z=unpacked[6],
                speed=0,     # 仮の値（speedフィールドを無視したため）
                time=0       # 仮の値（タイムスタンプ部分を無視したため）
            ))

        except struct.error as e:
            print(f"Error unpacking collision data: {e}")


    collision_detected_notify = (24, 18, 0xff), __collision_detected_notify_helper.__func__
```

その結果、衝突を検出することできるようになりました。
```
git\spherov2.py\spherov2\test>python BoltTest_collision.py
Testing Starting...
Connecting to Bolt...
Connected.
Running program. shake the robot
y_axis=True: Acceleration Y = 0.41748046875
Collision detected!
y_axis=True: Acceleration Y = 0.400634765625
Collision detected!
y_axis=True: Acceleration Y = -0.508544921875
Collision detected!
y_axis=True: Acceleration Y = -0.53564453125
Collision detected!
y_axis=True: Acceleration Y = -0.53564453125
Collision detected!
y_axis=True: Acceleration Y = -0.53564453125
Collision detected!
y_axis=True: Acceleration Y = -0.508056640625
Collision detected!
```

### まとめ
衝突イベントを入力に使ったプログラミングを実験しました。
spheroV2は衝撃の値の値自体を表示することもできたので、遊ぶ幅が広がりそうです。