IMU HAL 開発者向けドキュメント
1. アーキテクチャ概要
レイヤ構造図
graph TD
APP["アプリケーション層<br/>(CLI / テスト / ユーザーコード)"]
subgraph HAL["センサー HAL 層"]
MOCK_HAL["MockSensorHAL"]
ST_HAL["STMicroSensorHAL<br/>(LSM6DSO 等)"]
TDK_HAL["TDKSensorHAL<br/>(ICM-42688-P 等)"]
end
subgraph BUS["バスドライバ層"]
MOCK_BUS["MockBusDriver"]
I2C_BUS["I2CBusDriver"]
SPI_BUS["SPIBusDriver"]
end
APP -->|ISensorHAL| MOCK_HAL
APP -->|ISensorHAL| ST_HAL
APP -->|ISensorHAL| TDK_HAL
MOCK_HAL -->|IBusDriver| MOCK_BUS
ST_HAL -->|IBusDriver| I2C_BUS
ST_HAL -->|IBusDriver| SPI_BUS
TDK_HAL -->|IBusDriver| SPI_BUS
モジュール依存関係
graph LR
subgraph imu["imu/"]
ISensorHAL["interfaces.py<br/>ISensorHAL"]
Models["models.py<br/>SensorInfo / SensorEvent"]
Factory["factory.py"]
CLI["cli.py"]
subgraph hal["hal/"]
MockHAL["mock.py<br/>MockSensorHAL"]
StHAL["stmicro.py<br/>STMicroSensorHAL"]
TdkHAL["tdk.py<br/>TDKSensorHAL"]
end
end
subgraph bus["bus/"]
IBusDriver["interfaces.py<br/>IBusDriver"]
MockBus["mock.py<br/>MockBusDriver"]
I2CBus["i2c.py<br/>I2CBusDriver"]
SpiBus["spi.py<br/>SPIBusDriver"]
end
MockHAL --> ISensorHAL
StHAL --> ISensorHAL
TdkHAL --> ISensorHAL
MockHAL --> IBusDriver
StHAL --> IBusDriver
TdkHAL --> IBusDriver
MockBus --> IBusDriver
I2CBus --> IBusDriver
SpiBus --> IBusDriver
Factory --> MockHAL
Factory --> StHAL
Factory --> TdkHAL
Factory --> MockBus
Factory --> I2CBus
Factory --> SpiBus
CLI --> Factory
設計原則
本 IMU HAL は Android Sensors AIDL HAL の設計を参考にしており、以下の原則に従っています:
- 依存性逆転の原則 (DIP): アプリケーション層は
ISensorHALとIBusDriverの抽象インターフェースのみに依存する - 依存性注入 (DI): バスドライバは
initialize(bus)を通じてセンサー HAL に注入される - テスト容易性:
MockBusDriverとMockSensorHALによりハードウェアなしでテスト可能
2. クイックスタート
前提条件
# 依存パッケージのインストール
uv sync
MockSensorHAL + CLI による動作確認
# センサー一覧の表示 (mock HAL / mock バス)
uv run python -m ai_driven_development_labs.imu.cli list-sensors --hal mock --bus mock
# センサーデータの読み出し (3 回、1 秒間隔)
uv run python -m ai_driven_development_labs.imu.cli read --hal mock --bus mock --count 3 --interval 1.0
# JSON 形式で 1 回だけ読み出し
uv run python -m ai_driven_development_labs.imu.cli read-once --hal mock --bus mock --format json
Python コードからの利用
from ai_driven_development_labs.bus.mock import MockBusDriver
from ai_driven_development_labs.imu.hal.mock import MockSensorHAL
# バスドライバとセンサー HAL を生成
bus = MockBusDriver()
hal = MockSensorHAL()
# HAL を初期化 (バスドライバを DI で注入)
hal.initialize(bus)
# センサー一覧の取得
sensors = hal.get_sensor_list()
for sensor in sensors:
print(f" {sensor.name} (handle={sensor.sensor_handle})")
# センサーを有効化してデータ取得
for sensor in sensors:
hal.activate(sensor.sensor_handle, True)
events = hal.get_events()
for event in events:
print(f" {event.sensor_type.name}: {event.values}")
# リソース解放
hal.finalize()
STMicro HAL + MockBusDriver による動作確認
from ai_driven_development_labs.bus.mock import MockBusDriver
from ai_driven_development_labs.imu.hal.stmicro import STMicroSensorHAL
# LSM6DSO のレジスタマップを模倣
register_map = {
0x0F: 0x6C, # WHO_AM_I = LSM6DSO
0x1E: 0x03, # STATUS_REG: accel + gyro data ready
}
bus = MockBusDriver(register_map=register_map)
hal = STMicroSensorHAL()
hal.initialize(bus)
sensors = hal.get_sensor_list()
hal.finalize()
3. 新規ベンダー追加ガイド
ISensorHAL を実装して新しい IMU ベンダーを追加する手順を説明します。
ステップ 1: HAL クラスの作成
ai_driven_development_labs/imu/hal/ に新しいファイルを作成します。例: bosch.py
"""Bosch IMU HAL (BMI270 等) の実装。"""
import struct
import time
from ai_driven_development_labs.bus.interfaces import IBusDriver
from ai_driven_development_labs.imu.interfaces import ISensorHAL
from ai_driven_development_labs.imu.models import ReportingMode, SensorEvent, SensorInfo, SensorType
_ACCEL_HANDLE = 1
_GYRO_HANDLE = 2
# レジスタアドレス (BMI270 の例)
_REG_CHIP_ID = 0x00 # CHIP_ID
_REG_STATUS = 0x03 # STATUS
# スケール変換係数 (±2g, ±2000 dps の例)
_ACCEL_SENSITIVITY_G_PER_LSB = 1.0 / 16384.0
_GYRO_SENSITIVITY_DPS_PER_LSB = 1.0 / 16.4
class BoschSensorHAL(ISensorHAL):
"""Bosch 製 IMU (BMI270 等) 用 HAL。"""
SUPPORTED_DEVICES: dict[int, str] = {
0x24: "BMI270",
}
def __init__(self) -> None:
self._bus: IBusDriver | None = None
self._device_name: str = ""
self._active: dict[int, bool] = {}
def initialize(self, bus: IBusDriver) -> None:
bus.open()
self._bus = bus
chip_id = bus.read_register(_REG_CHIP_ID, 1)[0]
if chip_id not in self.SUPPORTED_DEVICES:
bus.close()
self._bus = None
raise RuntimeError(f"Unsupported device: CHIP_ID=0x{chip_id:02X}")
self._device_name = self.SUPPORTED_DEVICES[chip_id]
self._active = {_ACCEL_HANDLE: False, _GYRO_HANDLE: False}
def get_sensor_list(self) -> list[SensorInfo]:
return [
SensorInfo(
sensor_handle=_ACCEL_HANDLE,
name=f"{self._device_name} Accelerometer",
vendor="Bosch",
sensor_type=SensorType.ACCELEROMETER,
reporting_mode=ReportingMode.CONTINUOUS,
),
SensorInfo(
sensor_handle=_GYRO_HANDLE,
name=f"{self._device_name} Gyroscope",
vendor="Bosch",
sensor_type=SensorType.GYROSCOPE,
reporting_mode=ReportingMode.CONTINUOUS,
),
]
def activate(self, sensor_handle: int, enabled: bool) -> None:
self._active[sensor_handle] = enabled
def configure(self, sensor_handle: int, sampling_period_us: int, max_report_latency_us: int) -> None:
pass # ODR 設定を実装する
def flush(self, sensor_handle: int) -> None:
pass
def get_events(self) -> list[SensorEvent]:
if self._bus is None:
return []
# STATUS レジスタでデータ準備完了を確認し、出力レジスタを読み出す
# ... 実装省略 ...
return []
def finalize(self) -> None:
if self._bus is not None:
self._bus.close()
self._bus = None
self._active = {}
ステップ 2: factory.py への登録
ai_driven_development_labs/imu/factory.py の create_sensor_hal() に新しい HAL を追加します:
from ai_driven_development_labs.imu.hal.bosch import BoschSensorHAL
def create_sensor_hal(hal_type: str) -> ISensorHAL:
match hal_type:
case "mock":
return MockSensorHAL()
case "stmicro":
return STMicroSensorHAL()
case "tdk":
return TDKSensorHAL()
case "bosch": # 追加
return BoschSensorHAL()
case _:
raise ValueError(f"Unknown HAL type: {hal_type}")
ステップ 3: テストの追加
tests/test_imu/test_hal/ に新しいテストファイルを追加します:
# tests/test_imu/test_hal/test_bosch.py
from ai_driven_development_labs.bus.mock import MockBusDriver
from ai_driven_development_labs.imu.hal.bosch import BoschSensorHAL
from ai_driven_development_labs.imu.models import SensorType
class TestBoschSensorHAL:
def test_initialize_with_supported_device(self):
bus = MockBusDriver(register_map={0x00: 0x24}) # BMI270
hal = BoschSensorHAL()
hal.initialize(bus)
sensors = hal.get_sensor_list()
assert any(s.sensor_type == SensorType.ACCELEROMETER for s in sensors)
hal.finalize()
4. 新規バスドライバ追加ガイド
IBusDriver を実装して新しいペリフェラルバスを追加する手順を説明します。
ステップ 1: バスドライバクラスの作成
ai_driven_development_labs/bus/ に新しいファイルを作成します。例: uart.py
"""UART バスドライバの実装。"""
from ai_driven_development_labs.bus.interfaces import IBusDriver
class UARTBusDriver(IBusDriver):
"""UART を通じてセンサーと通信するバスドライバ。"""
def __init__(self, port: str = "/dev/ttyUSB0", baudrate: int = 115200):
self._port = port
self._baudrate = baudrate
self._serial = None # pyserial などを利用
def open(self) -> None:
"""UART ポートを開く。"""
# import serial
# self._serial = serial.Serial(self._port, self._baudrate)
...
def close(self) -> None:
"""UART ポートを閉じる。"""
if self._serial is not None:
# self._serial.close()
self._serial = None
def read_register(self, register: int, length: int) -> bytes:
"""レジスタから指定バイト数を読み出す。"""
# プロトコルに応じた実装
...
return bytes(length)
def write_register(self, register: int, data: bytes) -> None:
"""レジスタにデータを書き込む。"""
...
def transfer(self, data: bytes) -> bytes:
"""全二重転送を行う。"""
...
return bytes(len(data))
ステップ 2: factory.py への登録
ai_driven_development_labs/imu/factory.py の create_bus_driver() に追加します:
from ai_driven_development_labs.bus.uart import UARTBusDriver
def create_bus_driver(bus_type: str, bus_id: int = 0, device: int = 0) -> IBusDriver:
match bus_type:
case "mock":
return MockBusDriver()
case "i2c":
return I2CBusDriver(bus_id=bus_id, address=device)
case "spi":
return SPIBusDriver(bus=bus_id, device=device)
case "uart": # 追加
return UARTBusDriver(port=f"/dev/ttyUSB{bus_id}")
case _:
raise ValueError(f"Unknown bus type: {bus_type}")
ステップ 3: テストの追加
IBusDriver の各メソッドが仕様通りに動作することを確認するテストを追加します:
# tests/test_bus/test_uart_bus.py
from ai_driven_development_labs.bus.uart import UARTBusDriver
from ai_driven_development_labs.bus.interfaces import IBusDriver
class TestUARTBusDriver:
def test_is_instance_of_ibus_driver(self):
bus = UARTBusDriver()
assert isinstance(bus, IBusDriver)
5. API リファレンス
IBusDriver (抽象基底クラス)
モジュール: ai_driven_development_labs.bus.interfaces
ペリフェラルバスと通信するための抽象インターフェース。
| メソッド | シグネチャ | 説明 |
|---|---|---|
open |
() -> None |
バスを初期化して通信を開始する。 |
close |
() -> None |
バスを閉じてリソースを解放する。 |
read_register |
(register: int, length: int) -> bytes |
指定レジスタから length バイトを読み出す。 |
write_register |
(register: int, data: bytes) -> None |
指定レジスタにデータを書き込む。 |
transfer |
(data: bytes) -> bytes |
全二重転送を行い受信データを返す (SPI 用)。 |
ISensorHAL (抽象基底クラス)
モジュール: ai_driven_development_labs.imu.interfaces
Android ISensors.aidl に準拠したセンサー HAL の抽象インターフェース。
| メソッド | シグネチャ | 説明 |
|---|---|---|
initialize |
(bus: IBusDriver) -> None |
HAL を初期化する。バスドライバを DI で受け取る。 |
get_sensor_list |
() -> list[SensorInfo] |
利用可能なセンサー一覧を返す。 |
activate |
(sensor_handle: int, enabled: bool) -> None |
センサーを有効化/無効化する。 |
configure |
(sensor_handle: int, sampling_period_us: int, max_report_latency_us: int) -> None |
サンプリング周期と最大レポート遅延を設定する。 |
flush |
(sensor_handle: int) -> None |
FIFO バッファをフラッシュする。 |
get_events |
() -> list[SensorEvent] |
最新のセンサーイベントを取得する。 |
finalize |
() -> None |
HAL を終了しリソースを解放する。 |
SensorInfo (データクラス)
モジュール: ai_driven_development_labs.imu.models
Android SensorInfo.aidl に準拠したセンサー情報。frozen=True の不変データクラス。
| フィールド | 型 | 説明 |
|---|---|---|
sensor_handle |
int |
センサーを一意に識別するハンドル。 |
name |
str |
センサーの名前。 |
vendor |
str |
センサーのベンダー名。 |
sensor_type |
SensorType |
センサーの種別。 |
version |
int |
HAL バージョン (デフォルト: 1)。 |
max_range |
float |
センサーの最大計測範囲。 |
resolution |
float |
センサーの分解能。 |
power |
float |
消費電流 (mA)。 |
min_delay |
int |
最小サンプリング周期 (μs)。0 はオンデマンドを意味する。 |
max_delay |
int |
最大サンプリング周期 (μs)。 |
fifo_reserved_event_count |
int |
FIFO に予約されたイベント数。 |
fifo_max_event_count |
int |
FIFO の最大イベント数。 |
reporting_mode |
ReportingMode |
レポートモード。 |
SensorEvent (データクラス)
モジュール: ai_driven_development_labs.imu.models
Android Event.aidl に準拠したセンサーイベント。
| フィールド | 型 | 説明 |
|---|---|---|
sensor_handle |
int |
イベントを生成したセンサーのハンドル。 |
sensor_type |
SensorType |
センサーの種別。 |
timestamp_ns |
int |
イベントのタイムスタンプ (ナノ秒)。 |
values |
list[float] |
センサー計測値のリスト (例: [x, y, z])。 |
SensorType (IntEnum)
モジュール: ai_driven_development_labs.imu.models
| 値 | 定数 | 説明 |
|---|---|---|
| 1 | ACCELEROMETER |
加速度計 |
| 4 | GYROSCOPE |
ジャイロスコープ |
| 35 | ACCELEROMETER_UNCALIBRATED |
未補正加速度計 |
| 16 | GYROSCOPE_UNCALIBRATED |
未補正ジャイロスコープ |
ReportingMode (IntEnum)
モジュール: ai_driven_development_labs.imu.models
| 値 | 定数 | 説明 |
|---|---|---|
| 0 | CONTINUOUS |
一定周期でデータを報告する。 |
| 1 | ON_CHANGE |
値が変化したときにデータを報告する。 |
| 2 | ONE_SHOT |
一度だけデータを報告する。 |
| 3 | SPECIAL_TRIGGER |
特定のトリガー条件でデータを報告する。 |
MockBusDriver
モジュール: ai_driven_development_labs.bus.mock
メモリ上で仮想レジスタマップを管理するモックバスドライバ。テストやエミュレーション用途。
MockBusDriver(register_map: dict[int, int] | None = None)
register_map: 初期レジスタ値の辞書{register_addr: value}。未登録レジスタは0x00を返す。
MockSensorHAL
モジュール: ai_driven_development_labs.imu.hal.mock
ハードウェアなしで動作するモック HAL。ランダムまたは固定パターンのセンサーデータを生成する。
MockSensorHAL(
accel_range: float = 16.0,
gyro_range: float = 2000.0,
noise_stddev: float = 0.01,
)
accel_range: 加速度計のフルスケール範囲 (g)。gyro_range: ジャイロスコープのフルスケール範囲 (dps)。noise_stddev: センサーノイズの標準偏差。
STMicroSensorHAL
モジュール: ai_driven_development_labs.imu.hal.stmicro
STMicroelectronics 製 IMU (LSM6DSO / ISM330DHCX 等) 用 HAL。WHO_AM_I レジスタ (0x0F) でデバイスを識別する。
対応デバイス:
| WHO_AM_I | デバイス名 |
|---|---|
| 0x6C | LSM6DSO |
| 0x6B | ISM330DHCX |
TDKSensorHAL
モジュール: ai_driven_development_labs.imu.hal.tdk
TDK InvenSense 製 IMU (ICM-42688-P 等) 用 HAL。WHO_AM_I レジスタ (0x75) でデバイスを識別する。