水道水の漏水で市の水道局から40万円の請求書が来て驚いたことがありました。今回開発した水道水漏水検知システムは、目視確認する以外の方法がない水道水の漏水をセンサーで検知し、警報を発令するシステムです。
システム開発には、具体的な方法を種々検討し、結果的に安価なマイクロコントローラを使用し、自宅サーバーとの結合にて、自宅外からでも水道水使用状況がモニタリング出来る方法に決めました。
マイクロコントローラは、32ビットWiFi付きマイクロコントローラ(650円)、サーバーには64ビットのマイクロコントローラ(4,500円)を使い、低コストで高性能なシステム構築を実現することが出来ました。
システム開発の経緯
4年前の2013年、水道の漏水が原因で水道局から40万円もの請求書が来ました。その原因は漏水でしたが、何かチェックする方法は無いのかと市の水道局に質問すると、「漏水を検知するには、水道メーターを定期的に見てください」と言うのが答えでした。
そこで、水道メーターを目視確認する代わりに、センサーを取り付け、自動的に水道水の流水状況をチェックする方法をいろいろと考え、最終的にマイクロコントローラを用いた現在のシステムを考案しました。
漏水検知システム概要
水道は浄水場などの水道施設から給水配管を経由して、各家庭に送られます。この責任境界が水道メーターとなり、それ以降の漏水はコンシューマー(消費者)の責任となります。
水道メーターには、給水量を表すカウンターと流水時に回転するローターが付いており、もし漏水などがあれば、水道水を使っていない時でも常時このローターが回っていることで検知出来ます。
この目視にて確認する代わりに、ローターの回転を赤外線反射式センサーで検出し、流水が一定時間以上続いた場合、警報を発令する仕組みです。
システムの技術的要素
このシステムの技術要素としては、下記です。
- 漏水検知センサーと入力回路
- 32ビットマイクロコントローラと漏水検知システムソフトウェア開発
- 64ビットマイクロコントローラを用いた自宅サーバーの立上げ・運用
- ブラウザによるモニタリング(自宅LAN、およびVPNによる自宅外からのブラウジング)
システム立上げおよびデバッギング
システム立上げには大変時間がかかりました。32ビットマイクロコントローラーのバグ取りが主な作業時間です。
業務と違い情報が限られているので、試行錯誤があったりで、なかなか本質的な原因を突き詰められなかったのが理由です。
これらも、マイクロコントローラを深く知る一つのきっかけになっているので、良しとしております。
最終的には、24/7で動作を望んでいますので、エンドレスのデバッグになるかも知れません。
センサーと入力回路
どのようなセンサーを選ぶべきか、またマイクロコントローラへの入力回路はどう作るか。 外部信号の入力には、いろいろと気をつけなければならないことがあります。
今回は、水道メーターのローターの回転を検出するのが目的ですから、ローターへ光を当て、反射光を検出するセンサーを試しました。
次に入力回路ですが、入力側にはマイクロコントローラを使用しているので、3.3V。センサー取付場所は屋外の水道メーターボックス内なので、雨水等の対策に加え、配線のことも考える必要があります。
それから、反射光をフォトトランジスターで電流に変え、電気信号として如何にマイクロコントローラへ取り込むかなどが、主なポイントになります。
センサーの選定
センサーの選定としては、市販されている手頃な価格のものを探し、技術面で使用可否を検討し、実際に購入して試して見るのが現実的な方法です。
私は下記2つの光反射型センサーを購入して、試すことにしました。
- 反射型フォトインタラプタ GP2A200LCS
- フォトリフレクタ(反射タイプ)LBR-127HLD
- フォトリフレクタ(反射タイプ)TPR-105F Dランク
1は価格も350円で高く、一番本命として検討していた。
2と3はほぼ同程度の仕様で、金額も40〜50円だった。
1は、光信号をパルス状に送信して対ノイズ性を高めているが、それだけに反射波入力回路にも工夫がいると思われ、後回しにしていた。金額で選んだわけではないが、結局は2を使ってうまく行ったので、1を使うことはなかった。
フォトリフレクタ(反射タイプ)LBR-127HLDの仕様書を下記に示す。 LBR-127HLD PDFデータシート
入力回路
このセンサーは、赤外LED+フォトトランジスタを内蔵した構造となっており、仕様書を見て、LEDへの電流値を20mAに決め、DC5Vで235Ωの抵抗を繋いだ。(21.3mA) また、フォトトランジスタからの反射パルス入力回路は、トランジスタで受ける形とした。
各抵抗値は、R1=470Ω、R2=1〜11kΩ、R3=10kΩとし、R2は調整用に10kΩの可変抵抗をつけている。 入力側はマイクロコントローラのレベルに合わせて、動作電圧を3.3Vとした。
マイクロコントローラ WRoom-02 プログラム
水道メーターに取り付けたローター回転検出センサーから入力し、定期的にサーバーへデータを送信するプログラムを書いた。
入力はGPIOピン割込みにても可能であるが、10msecタイマー割込による処理に変更。
ローター回転検出割込処理プログラム
割込み処理ルーチンでは可能な限り処理時間を短くする必要がある。
ピン割込処理では、ローターの回転に比例して反射パルスの割込が発生するので、多い時で毎秒数十キロの頻度になることもあった。 そのせいか、WRoom-02のバックグラウンド処理に影響が出ている可能性も考えられたため、タイマー割込によるスキャン方式に変更した。
10msec毎にセンサー入力のGPIOピンの状態を調べ、ON/OFFの変化の度に回転カウント値をインクリメントするプログラムとなっている。
水道水の通水・停止判断プログラム
次に、ローターの回転状況から、水道水が通水状態の時、連続通水経過時間、通水強度(流量)などを計算するプログラムが必要である。 このプログラムもまた、タイマー割込で処理している。 こちらは、毎秒1回、下記の処理を行う。
インターバルタイマーによる定期処理
- ローター回転カウント数が前回より増えていれば、通水状態と判断する。
- 通水状態、停止状態の時間経過を測る。
- 各状態の時間経過を予め設定しておいた設定時間と比較し、超過した時に警報発生フラグをセットする。
- 通水状態から停止状態、またはその逆の停止状態から通水状態へ変化した時に、イベント発生フラグをセットする。この時、警報発生であれば、フラグをリセットする。
- 特に停止状態を判断する場合、1秒間でのカウント変化の有無では短か過ぎるため、10秒間の設定で一切カウント値に変化のない場合に停止と判断する。
メインプログラム(サーバーへのデータ送信等)
上記の割込み処理で得られたカウント値、経過時間、警報フラグ、イベントフラグなどを定期的または、事象発生時にWiFi経由でサーバーに送るプログラムである。
定期的な処理は、メインプログラムで設定した送信タイマーを1秒毎のタイマー割込にてデクリメントして行った結果、ゼロとなったタイミングで行う。
警報発生、イベント発生などの事象発生フラグを立て、メインプログラムでフラグチェックを行い、対応した処理を行う。
WiFi送信は、WRoom-02の標準的なライブラリを使用して、サンプルプログラムを参考に作成している。
特に、サーバーとの時間合わせは、送信時にサーバーの時間を返信し、そのデータから次回送信タイミングを送信タイマーとしてセットする方法を取っている。
<pre class="C++">
#include #include #define LED_BLINK 13 #define InteruptInputPin 4 Ticker ticker1; Ticker ticker2; uint32_t TimeDisplay = 5; uint32_t AlarmDisplay = 0; uint32_t Flow_Count = 0; uint32_t Q_FlowTime = 0; uint32_t Previous_Flow_Count = 0; bool Flow_Status = false; uint32_t Previous_Time; const int32_t Alarm_Set_Time_Flow = 60*60; // 60*60;sec const int32_t Alarm_Set_Time_Stop = 120*60; //120*60;sec bool Alarm_FlowTimeExess = false; bool Alarm_StopTimeExess = false; uint32_t Meter_Rotate=0; const uint32_t Meter_Rotate_Set = 10; // 10sec int32_t Duration_Time = 0; uint32_t GetCounter = 0; const char* ssid = "My_ssid"; const char* password = "My_PassWord"; const char* host = "192.168.x.xx"; static int count_sec = 10; // 60 sec counter void count_down() { // 1秒ごとの処理 count_sec--; digitalWrite(LED_BLINK, count_sec%2 ); GetCounter++; if (GetCounter >= 86400) GetCounter = 0; if(Flow_Count >= 50000000 ) Flow_Count = Flow_Count - 50000000; // 水道メーターのローター回転状態に応じた処理 Duration_Time = GetCounter - Previous_Time; if( Duration_Time= Meter_Rotate_Set){ Flow_Status = false; if(Alarm_FlowTimeExess){ Alarm_FlowTimeExess = false; AlarmDisplay = 9; } AlarmDisplay = AlarmDisplay | 0x10;// FlowStatusOFF (000100000) Q_FlowTime = Q_FlowTime + Duration_Time; if(Q_FlowTime >= 86400) Q_FlowTime = 0; Duration_Time = 0; Previous_Time = GetCounter; /* Enable time update */ } TimeDisplay--; }else{ // continue FLOW or start flowing again within Meter_Rotate_Set. Meter_Rotate = 0; if((!Alarm_FlowTimeExess) && (Duration_Time >= Alarm_Set_Time_Flow) ){ Alarm_FlowTimeExess = true; /* Enable time update */ AlarmDisplay = 0xb; } TimeDisplay--; } }else{ // Flow_Status = false. (isn't flowing. It's in STOP STATUS.) if(Previous_Flow_Count != Flow_Count){ Flow_Status = true; Meter_Rotate = 0; Duration_Time = 0; Previous_Time = GetCounter; if(Alarm_StopTimeExess){ Alarm_StopTimeExess = false; AlarmDisplay = 5; } AlarmDisplay = AlarmDisplay | 0x30;// FlowStatusOFF (00110000) /* Enable time update */ TimeDisplay--; }else{ if((!Alarm_StopTimeExess) && (Duration_Time >= Alarm_Set_Time_Stop) ){ Alarm_StopTimeExess = true; /* Enable time update */ AlarmDisplay = 7; } TimeDisplay--; } } Previous_Flow_Count = Flow_Count; } void roter_count() { //割込み処理 static int pin_state = 0; // Flow_Count increment if( pin_state != digitalRead(InteruptInputPin)){ Flow_Count++; pin_state = digitalRead(InteruptInputPin); } } void setup() { Serial.begin(115200); delay(10); ticker1.attach_ms(1000, count_down); pinMode(LED_BLINK, OUTPUT); Serial.println("DHT11 init"); dht.begin(); // We start by connecting to a WiFi network Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); // WiFi.PrintDiag(Serial); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); pinMode(InteruptInputPin, INPUT); ticker2.attach_ms(10, roter_count); // check input pulse level every 10msec } // int value = 0; void loop() { static char url[50]; static char str[150]; static char line[100]; static char str_time[5]; char *p; /* If 1sec has passed */ if ( (TimeDisplay <= 0) || (AlarmDisplay & 0x11) ){ /* Display current time cprintf("<Time:"); Time_Display(RTC_GetCounter()); cprintf(" RTC counter is %urn", RTC_GetCounter());*/ int THH, TMM, TSS; /* Compute hours */ THH = Duration_Time / 3600; /* Compute minutes */ TMM = (Duration_Time % 3600) / 60; /* Compute seconds */ TSS = (Duration_Time % 3600) % 60; memset(str, 0, 150); if(Flow_Status) sprintf(&str[0], "count=%d&status=%s&duration=%dh%dm%ds&qflowtime=%d", Flow_Count, Flow_Status ? "Flow":"Stop", THH , TMM, TSS, Q_FlowTime + Duration_Time); else sprintf(&str[0], "count=%d&status=%s&duration=%dh%dm%ds&qflowtime=%d", Flow_Count, Flow_Status ? "Flow":"Stop", THH , TMM, TSS, Q_FlowTime); if(AlarmDisplay & 0x11){ switch(AlarmDisplay & 0xf){ //下4桁がアラーム case 0xb: // Flow Excess On strcat(str, "&alarm=FlowTimeExcess&on_off=ON"); break; case 0x9: // Flow Excess Off strcat(str, "&alarm=FlowTimeExcess&on_off=OFF"); break; case 0x7: // Stop Excess On strcat(str,"&alarm=StopTimeExcess&on_off=ON"); break; case 0x5: // Stop Excess Off strcat(str, "&alarm=StopTimeExcess&on_off=OFF"); break; } switch(AlarmDisplay & 0x30){ // 上4桁がイベント case 0x30: // Flow Status On strcat(str,"&event=FlowStart"); break; case 0x10: // Flow Status Off strcat(str,"&event=FlowStop"); break; } AlarmDisplay = 0; } Serial.println(str); // 水道メーター情報をサーバーへ送る Serial.print("connecting to "); Serial.println(host); // Use WiFiClient class to create TCP connections WiFiClient client; const int httpPort = 80; if (!client.connect(host, httpPort)) { Serial.println("connection failed"); return; } // We now create a URI for the request memset(url, 0, 50); strcat(url, "/watermeter/receive_data.php"); Serial.print("Requesting URL: "); Serial.println(url); client.print(String("POST ") + url + " HTTP/1.1rn" + "Host: " + host + ":80rn" + "Connection: closern" + "Content-Type: application/x-www-form-urlencodedrn" + "Content-Length: " + strlen(str) + "rn" + "rn" + str + "rn"); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println(">>> Client Timeout !"); client.stop(); break; } delay(10); // added yield(); // added } while(client.available()){ delay(10); // added yield(); // added memset(line, 0, 100); sprintf(&line[0], "%s", client.readStringUntil('r').c_str()); p = strstr(&line[0], ":Data Inserted"); Serial.print(line); if(p){ memset(str_time, 0, 5); strncpy(&str_time[0], p-2, 2); TimeDisplay = 65 - atoi(str_time); Serial.print("timer set:"); Serial.println( TimeDisplay); } } if(TimeDisplay <= 0) TimeDisplay=60; Serial.print("Info:GetCounter="); Serial.println(GetCounter); Serial.println("closing connection"); } }</pre>
Raspberry Pi3 でサーバー構築
Raspberry Pi3は安価なマイクロコントローラながら、LAN、WiFiインタフェース、HDMIインタフェース、4USBポートを備えており、64ビットCPUに1GBRAMのメモリ容量があることより、性能面、仕様面でもパソコン並みである。
キーボード、ディスプレイを繋ぎ、インターネットに接続するとパソコンとして十分使える。しかも値段が4,500円。なんて安いのだろう。
そもそも英国で教育用に開発されたマイクロコントローラであるが、今やホビイスト向けにも大変人気があり、世界的なヒット商品である。
これを自宅のサーバーとして立上げ、24時間運転のもとに、先に開発したマイクロコントローラで収集したデータを受信し、データベースに蓄積する、
また、ブラウザからの要求により、収集データをグラフで表示したり、メールを送信したりすることを開発する。
サーバーの立上げ
Raspberry Pi3の基本的なハードウェア情報は他のソースに譲るとして、ここではソフトウェア面での必要な設定等を記述する。
ラズベリーパイ3を購入してもソフトが入っていないので、動作させるためには、OS等のソフトをインストールする必要がある。
また、基本的な使い方として、ハードディスク運用でなく、SDメモリを使用するので、これもHDD運用に変更する必要がある。
したがって、以下に示す内容にてサーバーとしての準備を行った。
- Linux OS Raspbianのインストール
- HDDのインプリメント
- VPNのインストール
- サーバーとしての稼働
Linux OS Raspbianのインストール
Rasbienのインストールについては、下記サイト(英国のラズベリパイ財団:Raspberry Pi Foundation)が参考になる。
HDDのインプリメント
HDDのインプリメントは非常に重要で必須である。しかしながら、少し注意点が必要なので、下記にコメントをしておきたい。
- ハードディスクを繋ぐ
- パーティションに分ける
- フォーマットを行う
- SDメモリの内容をコピー
- HDDをマウントする
ここで注意するのは、パーティションを作成して拡張パーティションを指定した時、論理パーティションを登録すること。これを忘れるとその部分が認識できず利用できなくなる。
VPNのインストール
VPNのインストールは、下記サイトを参考にする。筑波大学が提供しているVPNサーバーで無料ソフトである。
サーバーとしての稼働
サーバーとしての稼働させるには、Apacheのインストール、phpでのプログラム作成などが必要となる。
マイクロコントローラ回路図
試行錯誤の末、ついに回路図完成。特にセンサー入力回路は、この一年の運用経験によりより安定した回路となっている。 回路図を下記に示す。
センサー回路の電源はDC5VとDC3.3Vで、一次側インフラレッドLEDへはDC5V、二次側フォトインタラプタは3.3Vを使用。
一次側インフラレッドLEDへは、100Ωの制限抵抗によりIf=(5-1.2)/0.1k= 38 mAが流れる。
二次側フォトトランジスタのエミッタ出力の470Ωにかかる電圧は実測0.78V でベース電圧実測0.68を引くと0.1V 、つまり電流値は0.213mAとなる。
ベースのバイアス抵抗3.3k+20kの並列で、合成抵抗が2.83k、ベース電圧が0.68Vになるには、0.206mAの電流となる。
電流差=0.213-0.206=0.00694mAでhFE100として、コレクタ電流は約0.7mAとなり、コレクタ電圧=3.3-0.7=2.6V。(実測:2.6V)