[日記] [C++] Observerパターンを使ってBLEアドバタイズパケットのデータを受け取る

以前からBLE機器からの情報を配信するアプリを作っているが、今回は複数のBLE機器を扱えるように更新した

https://qiita.com/futail2a/items/1fabf50826cd91682e7e

https://qiita.com/futail2a/items/e8f3013c3f5018156b11

https://qiita.com/futail2a/items/ecced089fb830bc51315

# 変更方針

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/9c339b52-0c4e-45c7-b27e-ccc1259517ef.png)

* 複数のBLE機器からのアドバタイズパケットをモニタリングできるようにしたい
* 具体的にはBluezAbstructLayerはSensorDataHandlerを複数持てるようにして、センサーからの変更があれば各ハンドラに通知するようにしたい
* そのためSensorDataHandlerにObserverパターンを適用して、アドバタイズパケットがあればBluezAbstructLayerが各SensorDataHandlerのupdate関数を呼び出すように変更したい

## SensorDataHandler
* SensorDataHandlerにデータの更新を受け取るためのupdate関数と、その中で呼ばれるコールバック関数を設定できるset_update_cbを用意した
* 後に説明するが、このコールバック関数は更新された値をMqttManagerに渡すために使われる
```cpp
using UpdateCb = std::function <void(std::vector<uint8_t>)>;

class SensorDataHandler
{
public:
:
    virtual void set_update_cb(UpdateCb cb)=0;
    virtual void update(DBusMessage* const reply) = 0;
:
};
```

## SensorDataHanlerサブクラス
* 今回新たにSwitchBotの人感センサを追加するので、SensorDataHanlderのサブクラスとしてMotionSensorDataHanlerを新たに追加した
* update関数ではアドバタイズパケットがDBusMessageとして渡されるので、必要な情報をパースしそのバイト列をコールバック関数へ渡すようにした
* 温湿度計のデータを取り扱うWoSensorTHDataHandlerにも同様に変更を加えた

```cpp
class MotionSensorDataHandler : public SensorDataHandler
{
public:
  void set_update_cb(UpdateCb cb) override { m_update_cb = cb; }
  void update(DBusMessage* const reply) override;

private:
  UpdateCb m_update_cb;
};

void MotionSensorDataHandler::update(DBusMessage* const reply)
{
    if (m_update_cb)
    {
        std::vector<uint8_t> data = parse_reply(reply);
        m_update_cb(data);
    }
}
```

## BluezAbstructLayer
* 複数のSensorDataHandlerをvecotrで持つように変更した
* また外部からSensorDataHandlerを追加するためのadd_sensor_data_handler()を用意した
```cpp
class BluezAbstructLayer
{
public:
:
    void add_sensor_data_handler(std::shared_ptr<SensorDataHandler> sensorDataHandler);
:
private:
    std::vector<std::shared_ptr<SensorDataHandler>> m_sensorDataHandlers;
 :
};
```

* BlueZにアドバタイズパケットを確認する際、各SensorDataHandlerに対して確認するように変更した
* パケットが見つかった場合はそのSensorDataHanlerに対して通知を行うようにした
```cpp
void BluezAbstructLayer::check_adv_data()
{
    std::vector<uint8_t> byte_data={};
    // Iterate vector of SensorDataHandler 
    for(auto itr = m_sensorDataHandlers.begin(); itr != m_sensorDataHandlers.end(); ++itr)
    {
        (*itr)->get_device_mac();
        // Obtain MAC address from SensorDataHandler
        std::string device_path = m_crate_device_path((*itr)->get_device_mac());
        DBusMessage* msg = dbus_message_new_method_call(BLUEZ_SERVICE.c_str(), device_path.c_str(), DBUS_PROPERTIES.c_str(), METHOD_GET_ALL.c_str());
        :
        const char* interface_name = BLUEZ_DEVICE.c_str();
        dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface_name, DBUS_TYPE_INVALID);
        :
        DBusMessage* reply = dbus_connection_send_with_reply_and_block(m_conn, msg, -1, &err);
        dbus_message_unref(msg);
        :
        if (!reply)
        {
            std::cerr << "Failed to get properties for " << device_path << "\n";
            break;
        }

        // Notify BLE data via SensorDataHandler::update()
        (*itr)->update(reply);
        dbus_message_unref(reply);
    }
}
```

## IotDeviceHubManager
* BluezAbstructLayerにモニタリングしたいBLE機器のSensorDataHandlerを追加した
* またアドバタイズパケットを見つけたらMQTTでPusblishできるようにするため、コールバック関数を追加した
```cpp
bool IotDeviceHubManager::init()
{
    :
    m_th_sensor_data_handler = std::make_shared<WoSensorTHDataHandler>();
    m_th_sensor_data_handler->set_update_cb(std::bind(&IotDeviceHubManager::on_th_update, this, std::placeholders::_1));
    m_bluez->add_sensor_data_handler(m_th_sensor_data_handler);

    m_motion_sensor_data_handler = std::make_shared<MotionSensorDataHandler>();
    m_motion_sensor_data_handler->set_update_cb(std::bind(&IotDeviceHubManager::on_motion_update, this, std::placeholders::_1));
    m_bluez->add_sensor_data_handler(m_motion_sensor_data_handler);
    :
}
```

* このコールバック関数はアドバタイズパケットが見つかった時に、センサーの値をバイト列として渡される
* そのためバイト列からMQTTのPublishメッセージに変換するcreatePublishMessages関数をこのコールバック内で呼ぶように変更した
```cpp
void IotDeviceHubManager::on_th_update(std::vector<uint8_t> data)
{
    if (!data.empty())
    {
        std::vector<MqttMessage> messages = m_th_sensor_data_handler->createPublishMessages(data);
        for(auto message : messages)
        {
            m_mqtt->publishMessage(message.topic, message.message);
        }
    }
}

void IotDeviceHubManager::on_motion_update(std::vector<uint8_t> data)
{
    if (!data.empty())
    {
        std::vector<MqttMessage> messages = m_motion_sensor_data_handler->createPublishMessages(data);
        for(auto message : messages)
        {
            m_mqtt->publishMessage(message.topic, message.message);
        }
    }
    
```

# 実行結果
RasperryPi上にMQTTのSubscriberアプリを作り、温湿度計とモーションセンサーのトピックをサブスクライブしたところ、以下のようにデータがPublishされていることが確認できた
```
Message received on topic iot_device_hub/sensor_data/bed_room/temperature: Temperature: 21°C
Log: Client iot_device_client received PUBLISH (d0, q0, r0, m0, 'iot_device_hub/sensor_data/bed_room/humidity', ... (13 bytes))
Message received on topic iot_device_hub/sensor_data/bed_room/humidity: Humidity: 48%
Log: Client iot_device_client received PUBLISH (d0, q0, r0, m0, 'iot_device_hub/sensor_data/entrance/pir_utc', ... (39 bytes))
Message received on topic iot_device_hub/sensor_data/entrance/pir_utc: Since the last trigger PIR time (s): 32
Log: Client iot_device_client received PUBLISH (d0, q0, r0, m0, 'iot_device_hub/sensor_data/entrance/light_intensity', ... (23 bytes))
Message received on topic iot_device_hub/sensor_data/entrance/light_intensity: Light Intensity: bright
```

# まとめ
* Observerパターンを使って複数のBLE機器のアドバタイズパケットを配信できるようにした
* 少ない追加実装でBLE機器の追加ができるようになった

# 今後の課題
今はアドバタイズパケットを見るだけだが、GATTを通してデバイス対して何か操作できるようにしたい