IoT通過人数カウンター(ESPr + Azure + Power BI)

通過人数カウンターは様々な技術検証でよく出てくる例の一つです。

ここでは低消費電力、低予算を前提に超音波センサーとクラウドサービス(Azure)を用いた実装を試行したので、以下にその実現方法を簡単に紹介します。

前提はあまり幅の広くない一般的な室内の出入り口でのモニタリングを行うことで、出入りの方向も把握したい、精度はそれほど高くなくてOK、通信用にWiFiが既にあるという内容で設計してあります。

機器選定とハードウェア設計

製作に入る前に機材の選定が必要です。ここでは開発の手軽さと消費電力との兼ね合いから、中核となるマイコンにESPr Developerを用いることとしました。WiFiに手軽に接続できることと、Arduino的な使い方による開発の手軽さがその理由です。

センサーには超音波センサーを用いることとしました。ここでの説明は簡易化のために1つだけにしていますが、2つのセンサーを平行に設置することで、簡易的に進行方向を推定することにしました。センサーの反応が手前→奥なら入場、逆なら退場、ということです。

超音波センサーには、おそらく最も著名で入手性もよいHC-SR04を用います。ただし、これは5V駆動なので、ESPrとの通信部分(センサー→ESPr)は3つの抵抗を用いた分圧で対応します。

 

Azure上の設定

このシステムはAzure IoTを用いて実現します。自前のサーバーでのデータ受信、集計、表示も可能ですが、クラウド全盛期です、できるだけ自前での措置はやめましょう。そうしないと万が一、誰かに引き継ぐときにドキュメント作成の作業量が増えてしまいます。

Azureの無料利用枠ではIoT Hubでの受信は8,000/日となっています(2018/6現在)。このことは後で、マイコン側のソフトウェア設計時にも重要です。試行を超えるときには有償にすればよいですが、まずは無償の範囲で完結させることとします。

もし初めてAzure, Power BIを使う、という場合には先に以下を実施します。

  • Azure無料アカウントの取得
  • Power BIへのログイン(上のアカウントを使用。とにかく一度ログインすること)

一つ目はAzureサイトから「無料で始める」というところから開始できます。私の理解する限りでは認証の過程でクレジットカードが不可欠です。

二つ目はPower BIサイトから「サインイン」するだけです。上のAzureで作成したアカウントを使います。

Azureの設定についての大まかな流れは以下の通りです。

  1. Blob Storageの作成(なくても以下の範囲はできる)
  2. Iot Hubの作成と設定
  3. StreamAnalyticsの作成と設定
  4. Power BIでのグラフ作成と公開

上はマイクロソフト社のページにある通りにすればOKです。

Blobに格納しておくと、Azure Machine Learningからも利用できますので、とりあえずBlobとPower BIの両方へデータを流すようにしておくとよいのではないでしょうか。

超音波センサーからの人数カウント

基本的には通過によってセンサーの前が遮られたら1カウントとします。

ただしセンサーの設置位置は任意のために何もないときの距離は可変、かつセンサーから取得できる距離の値も一定の揺らぎがあります。

このため、ここでは移動平均を求めることで揺らぎの影響を軽減した上で、距離の変化の方向を見ることとしました。

移動平均を求めることで常にほぼフラットな波形になるはずです。次に通過すると数十cmは距離が変動するはずで、短くなってから長くなります。ここでは短くなりきったところでカウントすることとします。

 

マイコン上のソフトウェア

ここではArduino IDEで開発します。

ESPr Developerを使う際には少し設定が必要です。

ここではライブラリとして「AzureIoTHubMQTTClient」(Andri Yadi作。感謝!)を用いることとしました。Azure向けに幾つかのライブラリーがありましたが、今回の範囲を最も手軽に実装できそうなものを選びました。実際に、以下の例はこのライブラリーのサンプルコード「Thermostat」を修正することで実現しています。

サンプルは最初からWiFiを前提としていますので、それらのパラメーターは単に埋め込むだけで実現できます。同じくAzureの各アクセスパラメーターも同じです。これは上で設定時に取得した値をコピー&ペーストします。

const char *AP_SSID = "[YOUR_SSID_NAME]";
const char *AP_PASS = "[YOUR_SSID_PASS]";
#define IOTHUB_HOSTNAME         "[YOUR_IOTHUB_NAME].azure-devices.net"
#define DEVICE_ID               "[YOUR_DEVICE_ID]"
#define DEVICE_KEY              "[YOUR_DEVICE_KEY]"

ソースコードの変更といえるのは以下の部分だけです。

まずloop関数の中身から計測部分を取りのけることにしました。これにより、他のセンサーの場合でもloop関数は相違ないようにできます。

void loop() {
    //MUST CALL THIS in loop()
    client.run();

  float dt, nowL;
  readSensor(&dt, &nowL);

  if (prevCount < count) {
    Serial.print("change ");
    Serial.println(count);
    prevCount = count;
    
    if (client.connected()) {

        // Publish a message roughly every 3 second. Only after time is retrieved and set properly.
        if(millis() - lastMillis > 3000 && timeStatus() != timeNotSet) {
            lastMillis = millis();

            //Read the actual temperature from sensor

            //Get current timestamp, using Time lib
            time_t currentTime = now();

            // You can do this to publish payload to IoT Hub
            /*
            String payload = "{\"DeviceId\":\"" + String(DEVICE_ID) + "\", \"MTemperature\":" + String(temp) + ", \"EventTime\":" + String(currentTime) + "}";
            Serial.println(payload);

            //client.publish(MQTT::Publish("devices/" + String(DEVICE_ID) + "/messages/events/", payload).set_qos(1));
            client.sendEvent(payload);
            */

            //Or instead, use this more convenient way
            AzureIoTHubMQTTClient::KeyValueMap keyVal = {{"MCount", count}, {"MnowL", nowL}, {"DeviceId", DEVICE_ID}, {"EventTime", currentTime}};
            client.sendEventWithKeyVal(keyVal);
        }
    }
    else {
      Serial.println("client disconnected");
    }
  } else {
    Serial.print("not change ");
    Serial.println(count);
  }

  delay(200); // <- fixes some issues with WiFi stability
}

次にセンサーの読み出し部分を書きます。

float prevL = 0.0;
int direction = 0;
int count = 0;

void readSensor(float *dt, float *nowL) {
  digitalWrite(TRIGGER, LOW);
  delayMicroseconds(1);
  digitalWrite(TRIGGER, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER, LOW);

  *dt = pulseIn(ECHO, HIGH);

   if (*dt > 0) {
    // 計測
    float Lcm = *dt * 0.034 / 2.0;
  
    // LPF(移動平均)
    *nowL = 0.7 * Lcm + 0.3 * prevL;
    float dL = *nowL - prevL;
  
    if (abs(dL) < 10.0) {
      // 変化なしと見なす
      if (direction == -1) {
        count++;
      }
      direction = 0;
    } else if (dL > 0.0) {
      // 長さは伸びる方向
  
      direction = 1;
    } else {
      // 長さは縮む方向
  
      direction = -1;
    }
    
    prevL = *nowL;
  }
}

int prevCount = 0;

 

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です