M5UnitUnified について

# 概要
[M5 Japan Tour 2024 Autumn 東京](https://m5stack.connpass.com/event/329550/) にて発表された [M5UnitUnified](https://github.com/m5stack/M5UnitUnified) について[当日のスライド](https://speakerdeck.com/gob/m5stack-nise-na-m5yunitutowotunaidexi-uwei-noxin-tanaapuroti) では語れなかった事柄や、詳細について書いていきます。

なお改めて申し上げますが、各ユニットの M5UnitUnfied 対応ライブラリは新規に書き起こしされています。
従来の内製ライブラリや、依存している外部ライブラリでは対応していない機能の追加や、バグ修正、挙動のデータシートへの準拠を目標に作成しています。

さらにまだ**アルファ版であることにご留意ください。**
以下の構成や API は今後大幅に変更される可能性があります。

またご意見、ご要望、ご感想他フィードバックやレスポンスに大変飢えておりますので、色々不備もあるとは思いますが、お試しいただければ幸いです。

# ユニット対応状況
現時点( 2024/12/13 現在)で公開されているユニットについて見ていきましょう。
同系列なユニットはできる限り同じリポジトリに収める方針です。

## 公開済み

### [M5Unit-ENV](https://github.com/m5stack/M5Unit-ENV)
こちらは既存のライブラリに M5UnitUnified 関連のファイルが追加された形となっています。
ENV 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit CO2](https://docs.m5stack.com/en/unit/co2)
  - [Unit ENVIII](https://docs.m5stack.com/en/unit/envIII)
  - [Unit ENVPro](https://docs.m5stack.com/en/unit/ENV%20Pro%20Unit)
  - [Unit TVOC](https://docs.m5stack.com/en/unit/tvoc)
- 近日追加予定
  - [Unit ENVIV](https://docs.m5stack.com/en/unit/ENV%E2%85%A3%20Unit)

### [M5Unit-METER](https://github.com/m5stack/M5Unit-METER)
新規リポジトリ。 
Meter 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit Vmeter](https://docs.m5stack.com/en/unit/vmeter)
  - [Unit Ameter](https://docs.m5stack.com/en/unit/ameter)
  - [Unit KmeterISO](https://docs.m5stack.com/en/unit/KMeterISO%20Unit)

### [M5Unit-HUB](https://github.com/m5stack/M5Unit-HUB)
新規リポジトリ。 
Hub 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit PaHub2](https://docs.m5stack.switch-science.com/en/unit/pahub2)
- 追加予定(時期未定)
  - [Unit PbHub](https://docs.m5stack.com/en/unit/pbhub)

### [M5Unit-GESTURE](https://github.com/m5stack/M5Unit-GESTURE)
新規リポジトリ。 
- 対応ユニット
  - [Unit Gesture](https://docs.m5stack.com/en/unit/gesture)

### [M5Unit-HEART](https://github.com/m5stack/M5Unit-HEART)
新規リポジトリ。 
- 対応ユニット
  - [Unit Heart](https://docs.m5stack.com/en/unit/heart)

### [M5Unit-TOF](https://github.com/m5stack/M5Unit-TOF)
新規リポジトリ。 
ToF 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit ToF](https://docs.m5stack.com/en/unit/tof)
  - [Unit ToF4M](https://docs.m5stack.com/en/unit/Unit-ToF4M)

### [M5Unit-WEIGHT](https://github.com/m5stack/M5Unit-WEIGHT)
新規リポジトリ。 
Weight 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit WeightI2C](https://docs.m5stack.com/ja/unit/Unit-Weight%20I2C)

## 近日公開予定

### M5Unit-RFID
新規リポジトリ。 
RFID 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit RFID2](https://docs.m5stack.com/en/unit/rfid2)

### M5Unit-ANADIG
新規リポジトリ。 
ADC,DAC 系列のユニットに関するライブラリです。
- 対応ユニット
  - [Unit DAC](https://docs.m5stack.com/en/unit/dac)
  - [Unit DAC2](https://docs.m5stack.com/en/unit/Unit-DAC2)


# 基本的な使用の流れ
- ユニットの所属するライブラリをインストール
ArduinoIDE ならライブラリマネージャを使用 
PlatformIO なら platformio.ini に記述
```ini
; ToF の場合
lib_deps = m5stack/M5Unit-TOF
```
(依存する [M5UnitUnified](https://github.com/m5stack/M5UnitUnified) [M5Utility](https://github.com/m5stack/M5Utility) [M5HAL](https://github.com/m5stack/M5HAL) も自動でインストールされるはずですが、されない場合は手動でお願いします)
-  M5UnitUnified とユニットのインクルードファイルをインクルードします
さらに [M5Unified](https://github.com/m5stack/M5Unified) と組合せる事で、ソースが共通化できます。
```cpp
// ToF 場合
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedTOF.h>

namespace {
m5::unit::UnitUnified Units; // Unit 管理
m5::unit::UnitToF unit;      // ToF ユニット
}  // namespace

void setup()
{
    M5.begin();

    auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
    auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
    M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
    Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);

    if (!Units.add(unit, Wire) || !Units.begin()) { // 管理クラスに Add して開始!
        M5_LOGE("Failed to begin");
        while (true) {
            m5::utility::delay(10000);
        }
    }
}

void loop()
{
    M5.update();
    Units.update();       // ユニットの更新(定期計測機構があれば計測して蓄積)
    if (unit.updated()) { // 更新されたら
        M5_LOGI("\n>Range:%d\nStatus:%u", unit.range(), unit.range_status()); // データを使用
    }
}
```
各ユニットのリポジトリの examples も参照してください

# Unit クラスで共通している事柄

さて、既存の値取得系ライブラリの使用例では、以下のような loop() の書き方をよく見ます
```cpp
void loop() {
    if (foo.update()) { // 計測して値を取得 (取れるまでブロックする実装もある)
        printf("%f\n", foo.anyValue()); // 表示
    }
    delay(1000); // 適当な delay または delay なし
}
```

計測チップのほとんどには定期計測の為の機構があります。
定期計測機構を使用する場合、大抵のユニットではインターバル時の消費電力も少なくなり、バッテリー駆動時の稼働時間を増やせるのではないでしょうか?
M5UnitUnified ではその機構を積極的に使用し、更新関数では計測タイミングでなければ即時リターンする挙動をとります。
delay に関しては必要に応じてユーザー側で行うことができます。

- delay をしたい場合 (A)
```cpp
void loop() {
    Units.update();
	// Units.update() では計測タイミングでなければ即リターン
	if(unit.updated()) {
	    printf("%f\n", unit.anyValue());
        m5::utility::delay(unit.interval()); // 値が取れたら次の計測予想タイミングまで
    }
}
```
- delay をしたい場合 (B)
```cpp
void loop() {
    Units.update();
	// Units.update() では計測タイミングでなければ即リターン
	if(unit.updated()) {
	    printf("%f\n", unit.anyValue());
    }
    m5::utility::delay(1); // 最小 delay を常時
}
```

## クラス関数
使用感や、API のできる限りの統一を目指すという事で、各 Unit クラスは共通の基底クラス m5::unit::Component から派生されています。
またデータ蓄積機能を持つクラスの場合は、 m5::unit::PeriodicMeasurementAdapter も基底となります(インターフェイスクラス)
基底にあるクラス関数は派生された Unit クラスにおいて、もちろん使用できます。
各 Unit クラスにある API では補えない処理を独自に行いたい場合など、これらを使用すれば解決するかも知れません。
基底など M5UnitUnified 関連の[ドキュメントはこちら](https://m5stack.github.io/M5UnitUnified/)です。

### 基本ルール
- component_config 関数によって共通の設定の取得、設定が行える
- config 関数によってユニット独自の設定の取得、設定が行える
定期計測機構がある場合の初期値は、典型的な設定で定期計測が開始される状態
よって Units.begin() によって定期計測がスタートする
- readFoo は**ユニットにアクセス**して値を取得する
- writeFoo は**ユニットにアクセス**して値を書き込む
- startPeriodicMeasurement(ユニット毎に異なる引数) は定期計測を開始する (機構が存在すれば)
- stopPeriodicMeasurement() は定期計測を停止する (機構が存在すれば)
- measureSingleshot(ユニット毎に異なる引数) シングルショット計測による計測値の取得 (機構が存在すれば)
- 定期計測を持つユニットでは、任意の要素数を[環状バッファ](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%90%E3%83%83%E3%83%95%E3%82%A1)に蓄積できます
初期値は 1 なので、常に最新のデータのみにアクセスします
component_config で要素数を変更可能です

### Component の関数(一部)
#### プロパティ
- const char* deviceName() const;
ユニット名称
- m5::unit::types::uid\_t identifier() const;
一意なユニットクラス ID
- uint8\_t address() const;
使用している I2C アドレス

#### 定期計測関連
- bool inPeriodic() const;
定期計測中か?
- bool updated() const;
直近の更新によって値が取得できたか?
- m5::unit::types::elapsed\_time\_t updatedMillis() const;
値が取得できた時の時刻(ms)
- m5::unit::types::elapsed\_time\_t interval() const;
定期計測の間隔(ms)
#### Read
template によって 8/16 bit レジスタを識別
- m5::hal::error::error/\_t readWithTransaction(uint8\_t* data, const size\_t len);
読み込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,std::nullptr\_t>::type = nullptr> bool readRegister(const Reg reg, uint8\_t* rbuf, const size\_t len, const uint32\_t delayMillis,const bool stop = true);
レジスタ読み込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool readRegister8(const Reg reg, uint8\_t& result, const uint32\_t delayMillis, const bool stop = true);
レジスタ 8bit 読み込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool readRegister16(const Reg reg, uint16\_t& result, const uint32\_t delayMillis, const bool stop = true);
レジスタ 16bit 読み込み
#### Write
template によって 8/16 bit レジスタを識別
- m5::hal::error::error\_t writeWithTransaction(const uint8\_t* data, const size\_t len, const bool stop = true);
書き込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> m5::hal::error::error\_t writeWithTransaction(const Reg reg, const uint8\_t* data, const size\_t len, const bool stop = true);
レジスタ書き込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister(const Reg reg, const uint8\_t* buf = nullptr, const size\_t len = 0U, const bool stop = true);
レジスタ書き込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister8(const Reg reg, const uint8\_t value, const bool stop = true);
レジスタ 8bit 書き込み
- template <typename Reg, typename std::enable\_if<std::is\_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister16(const Reg reg, const uint16\_t value, const bool stop = true);
レジスタ 16bit 書き込み
bool generalCall(const uint8\_t* data, const size\_t len);
ジェネラルコール書き込み

### PeriodicMeasurementAdapter の関数(一部)
####  蓄積されたデータに対する操作
- size\_t available() const;
蓄積されているデータ数
- bool empty() const;
蓄積データは空か?
- bool full() const;
蓄積データは環状バッファの最大に達しているか?
- ユニット毎の計測データ型 oldest() const;
蓄積されている中で最も古いデータ
- ユニット毎の計測データ型 latest() const;
蓄積されている中で最も新しいデータ
- void discard();
蓄積されている中で最も古いデータを削除
- void flush();
全ての蓄積データを削除

蓄積データが複数の場合のアクセスの例
```cpp
void any_task(void*)
{
    for(;;) {
        Units.update();
        while(unit.available()) { // 蓄積データがある限り
            copy_data_function(unit.oldest().anyValue()); // 最も古い物をどこかへコピー
            unit.discard(); // 最も古い物を棄却 [これがないと永久ループ注意!]
        }
    }
}
```

### 計測データ
ユニット毎に独自のデータ型を持ちます。
生のデータと、生データからの値取得の関数を搭載しています。
```cpp
// ToF の場合
struct Data {
    std::array<uint8_t, 17> raw{};  //!< RAW data
    RangeStatus range_status() const;
    inline bool valid() const
    {
        return raw[0] == 9;
    }
    inline int16_t range() const
    {
        return valid() ? m5::types::big_uint16_t(raw[13], raw[14]).get() : -1;
    }
};
```
またユニット側には最も古いデータに対してのクイックアクセス API を搭載しています。
```cpp
// ToF の場合
class Unit UnitVL53L1X {
    // ... 前略
    inline int16_t range() const
    {
        return !empty() ? oldest().range() : -1;
    }
    inline bool valid() const
    {
        return !empty() && oldest().valid();
    }
    inline vl53l1x::RangeStatus range_status() const
    {
        return !empty() ? oldest().range_status() : vl53l1x::RangeStatus::Unknown255;
    }
    // ... 後略
};
``` 

その他ユニット毎のドキュメントは各リポジトリの README に GitHub Pages へのリンクがありますのでご参照ください。

# 今後
ユニット対応を進行しつつ、定期的に更新していく予定です。
M5HAL との統合時期は未定ですので、当分は TwoWire を直接使用する現状の形になると思われます。
GPIO / UART 接続ユニットについては 同様に Serial 等で代替する形で進行する予定です。

**ご意見、ご要望、ご感想他フィードバックやレスポンスに大変飢えておりますので、色々不備もあるとは思いますが、お試しいただければ幸いです。**