ESP8266でホームモニタリングシステム構築
こんにちは、くろべこです。
巷では室内の環境データを取得してモニタリングやクラウドに保存するようなIoTシステムは今どき普通になってきていますよね?しかし、実際に導入しようとするとサーバーとの契約やクライアント(子機)の購入/レンタル。システムの保守・維持管理など結構お金がかかってくると思います。
そこで今回はESP8266を使用した室内環境のモニタリングシステムの製作(サーバー編)をしてみたいと思います。私が提案するシステムのコンセプトは以下の通りです。
システムのコンセプト
- 安価で構築できる
- クライアント(子機)を自由に増やせる
- クラウドにデータ上げたりやデータ保存が出来たりなど拡張性がある
具体的な内容はソケット通信を用いてサーバーとクライアントを作成します。なので、ESP8266は最低2個必要となりますね。
サーバー側はクライアント側からの情報を待ち受けて、データを収集します。クライアント側は一定周期でサーバーにアクセスしてデータをサーバー側に送信する感じです。
【システム概要】
- サーバーはクライアントからのデータ受信を待ち受け
- クライアントは室内データを取得&サーバーへ送信
- サーバーはクライアントから受け取ったデータをOLEDに表示
イメージ的には以下のような感じです。
ちなみに、今回はソケット通信の取り決めとしては【TCP】を使用します。なので、クライアントとサーバーの通信は1対1で行われます。
サーバーのプログラムを構築する
まずは、サーバー側のプログラムを作りましょう。
【サーバーが持つ機能】
- 静的IPの設定
- ソケット通信用ポート設定
- 家庭内WiFiへの接続(STAモード)
- データ受信コード
- 受信データ処理コード
- ACK用データ送信コード
- 受信データ表示用コード
ざっくり機能としてはこんな感じです。では、実際にプログラムを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
/* * ESP8266:TCPサーバー */ #include <Wire.h> #include "SH1106.h"//OLED表示用ヘッダ #include <ESP8266WiFi.h> IPAddress ip(100, 64, 1, 100); //サーバーIP IPAddress gateway(100, 64, 1, 1);//ipconfigで確認 IPAddress subnet(255, 255, 255, 0);//ipconfigで確認 IPAddress DNS(100, 64, 1, 1);//ipconfigで確認 const char* ssid = "65661981-2.4G"; //Enter SSID const char* password = "28544552"; //Enter Password const String shdr = "STX";//ASCII:テキスト開始 const String rhdr = "ACK";//ASCII:ACK const String endCode1 = "OK";//ASCII:OK const String endCode2 = "NG";//ASCII:NG const char dmt = ',';//受信デリミタ const String ClientIP1="100.64.1.10"; const String ClientIP2="100.64.1.20"; const String ClientIP3="100.64.1.30"; static uint32_t t=0;//点滅周期カウンタ static bool st=0;//LED状態 String ary[3] = {"\0"}; // 分割された受信文字列を格納する配列 uint8_t sw=0;//クライアント選択 WiFiServer server(9600); //サーバー通信用ポート SH1106 display(0x3c, 4, 5);//OLEDインスタンス void setup() { Serial.begin(115200); pinMode(14,OUTPUT);//D5 クライアント① pinMode(12,OUTPUT);//D6 クライアント② pinMode(13,OUTPUT);//D7 クライアント③ WiFi.config(ip, gateway, subnet, DNS);//静的IP設定 WiFi.begin(ssid, password);//WiFi接続開始 // WiFi接続確認 for(int i = 0; i < 15 && WiFi.status() != WL_CONNECTED; i++) { Serial.print("."); delay(1000); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP());//サーバーのローカールIP(確定) server.begin();//サーバーのlisten開始 Serial.println("Server started"); //ディスプレイ初期化 display.init(); display.flipScreenVertically();//ディスプレイ反転 display.setContrast(255);//コントラスト設定0~255 display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); display.setFont(ArialMT_Plain_16); display.drawString(64,32,"IP:"+WiFi.localIP().toString()+"\n"+"SERVER"+"\n"+"START"); display.display(); delay(3000); display.clear();//描画バッファクリア } String IPConvert(uint32_t ip){//1oct,2oct,3oct,4oct uint8_t ipaddress[4]; String sipaddress; ipaddress[0]=(ip&0xFF);//1oct ipaddress[1]=(ip&0xFF00)>>8;//2oct ipaddress[2]=(ip&0xFF0000)>>16;//3oct ipaddress[3]=(ip&0xFF000000)>>24;//4oct sipaddress=(String)ipaddress[0]+'.'+(String)ipaddress[1]+'.'+(String)ipaddress[2]+'.'+(String)ipaddress[3];// return sipaddress; } void OledDisp(String ip, String st1, String st2, String st3, String st4){ //1ページ目 display.drawString(64,32,"<<"+ip+">>"+"\n"+st1+"\n"+st2); display.display(); display.clear(); delay(3000); //2ページ目 display.drawString(64,32,"<<"+ip+">>"+"\n"+st3+"\n"+st4); display.display(); display.clear(); delay(3000); } int split(String data, char delimiter, String *dst){ int index = 0; int arraySize = (sizeof(data)/sizeof((data)[0]));//消しても動く int datalength = data.length();//格納されている値のデータサイズ(byte) for (int i = 0; i < datalength; i++) { char tmp = data.charAt(i);// if ( tmp == delimiter ) { index++; if ( index > (arraySize - 1)) return -1;//消しても動く } else dst[index] += tmp; } return (index + 1);//消しても動く } //接続クライアントごとのLEDインジケータ==だと上手くいかないのでIndexOfを使う void SocketLed(String led){ if(led.indexOf(ClientIP1)>=0){ digitalWrite(14,HIGH); digitalWrite(12,LOW); digitalWrite(13,LOW); }else if(led.indexOf(ClientIP2)>=0){ digitalWrite(14,LOW); digitalWrite(12,HIGH); digitalWrite(13,LOW); }else if(led.indexOf(ClientIP3)>=0){ digitalWrite(14,LOW); digitalWrite(12,LOW); digitalWrite(13,HIGH); } } void loop() { String ip; byte buf[10]; WiFiClient client = server.available();//クライアントの接続/データ受信可能確認 if (client.connected()) { String rstr;//受信データ用箱 String clip;//クライアントIP Serial.println("Connected to client"); clip=IPConvert(client.remoteIP());//接続先のクライアントIPを文字列に変換ている Serial.print("ClientIP:");Serial.println(clip);//クライアントIP Serial.print("ClientPort:");Serial.println(client.remotePort());//クライアントPort //クライアント接続LED SocketLed(clip); //コマンド文字列受信(文字列が来なければタイムアウトする) client.setTimeout(10000);//ストリームデータ読み取りタイムアウト rstr = client.readStringUntil('\r');//受信データを終端文字受信orタイムアウトまで読み込み String ack;//ack用 if(rstr.toInt()>0){//格納データが入っているか確認 Serial.print("Recive:");Serial.println(rstr); ack=rhdr+endCode1+"\r";//受信確認(ACK) client.print(ack);//応答送信 }else{//格納データが入っていない場合 Serial.println("Please retry"); ack=rhdr+endCode2+"\r";//受信エラー(ACK) client.print(ack);//応答送信 } //文字列分割 split(rstr, dmt, ary); //OLEDディスプレイ表示 OledDisp(clip, ary[0], ary[1], ary[2], ary[3]); ary[0]="";//配列初期化。これをやらないと文字列が結合し続ける ary[1]="";//配列初期化。これをやらないと文字列が結合し続ける ary[2]="";//配列初期化。これをやらないと文字列が結合し続ける ary[3]="";//配列初期化。これをやらないと文字列が結合し続ける //接続をクローズ client.stop(); Serial.println("Closed"); } } |
プログラム内で使用している関数について紹介します。
<<String IPConvert(uint32_t ip)>>
この関数では、接続先のクライアントIPアドレスをOLEDに表示させるためにIPアドレスを文字列に変換する関数になります。クライアントのIPアドレスはclient.remoteIP()でゲットするのですが、arudinoのIPAddressクラスは1バイト区切りの8進数で定義されています。なので、文字列に直すために1バイトずつ抽出して文字列変換し、文字列を結合させてあげています。
<<OledDisp(String ip, String st1, String st2, String st3, String st4)>>
この関数は文字列をOLEDに表示させるものです。今回使用しているOLEDは1.3インチのSH1106のチップを使用した物を使用しています。引数は4つ設定していまして、st1,st2は1ページ目、st3,st4は2ページ目に表示させるようにしています。
<<split(String data, char delimiter, String *dst)>>
この関数は受信データの分割を行う関数になります。受信データは文字列の区切り(デリミタ)としてカンマ”,”を使用しています。関数内でデリミタを確認しつつ文字列を分割しています。
<<SocketLed(String led)>>
この関数では、接続クライアントのIPアドレスを確認してクライアントに対応するLEDを光らせています。当初は==で書いていたのですが上手くいきませんでした。なので、indexOfを使って、文字列のどこかに該当のIPAddressが入っていたら条件分岐するようにしています。
以上が関数の説明でした。
次にsetup()で行っていることをフローで説明します。
- pinModeの設定
- WiFi接続
- サーバー開始(クライアントからの接続待ちにする)
- OLED初期化
最後に、メインループの説明ですね。
- クライアントの接続確認
- データ受信(タイムアウトも設定)
- 受信に対するACK送信
- OLED表示
- 接続close
このループを回しているだけです。クライアント側は次回解説しますが、現状3つのクライアントを接続していまして、コネクション切断後、再接続時間10秒を設けて1クライアントごとTCP通信を行っています。
ちなみに、TCPソケット通信で同時に複数のクライアントと接続できるんですかね?ポートを分けてあげればイメージ的に出来そうなのですがArduinoでできるのかな??知っている人がいれば教えてください。
長々と書きましたがサーバー側の設定は以上です。参考に、クライアントと接続している実際の動画載せて終わりたいと思います。
コメント