WROOM 単体に Arduino スケッチで Wi-Fi ストリーミング

ESP8266 ( ESP-WROOM-02 )

8.スケッチを入力

スケッチを以下を参考に入力していきます。

ソースコードを修正しました。
スマホで正しく表示されない不具合は、IPAdress型をString型に変換できていなかった問題を修正しました。
(2019/10/21)

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h> //timeライブラリver1.4以上

const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

boolean SSE_on = false;//Server-Sent Events設定が済んだかどうかのフラグ

WiFiServer server(80);

WiFiClient client;

//-------NTPサーバー定義----------------
unsigned int localPort = 2390;      // local port to listen for UDP packets
//IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP;
const char* ntpServerName = "time.windows.com";
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp;
IPAddress myIP;
long LastTime = 0;

//*****************セットアップ**********************
void setup() {
  Serial.begin(115200);//このシリアル通信はモニター用
  delay(10);

  // Connect to WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Start the server
  server.begin();
  Serial.println("Server started");

  myIP = WiFi.localIP();
  Serial.println(myIP);
  delay(2000);
  //NTPサーバーでタイムを取得
  udp.begin(localPort);
  WiFi.hostByName(ntpServerName, timeServerIP); 
  setSyncProvider(getNtpTime);
  delay(1000);
}

//メインループ***********************************************
void loop() {
  HTTP_Responce();
  yield();//これは重要!!これがないと動作しない。
}

//**********************Server-Sent Events レスポンス関数**************
void HTTP_Responce()
{
  client = server.available();//クライアント生成は各関数内でしか実行できないので注意
  while(client.connected()){
    String req = client.readStringUntil('\r');
    if (req.indexOf("GET / HTTP") != -1){//ブラウザからリクエストを受信したらこの文字列を検知する
      Serial.print(req);
      //ブラウザからのリクエストテキストを残らず読み込む
      while( client.available() ){
        req = client.readStringUntil('\r');
        Serial.print(req);
        yield();
      }
      String str;
      //-------ここからHTTPレスポンスのHTMLとJavaScriptコード
      str = "HTTP/1.1 200 OK\r\n";
      str += "Content-Type:text/html\r\n";
      str += "Connection:close\r\n\r\n";//1行空行が必要
      str += "<!DOCTYPE html><html><head>\r\n";
      str += "<meta name=\"viewport\" content=\"initial-scale=1.5\">\r\n";
      str += "<script type=\"text/javascript\">\r\n";
      str += "var source=new EventSource(\"";
      str += String(myIP[0])+'.'+String(myIP[1])+'.'+String(myIP[2])+'.'+String(myIP[3]); //ルーターのローカルIPアドレスを自動取得
      str += "\");\r\n";
      str += "var obj1=document.getElementById(\"SSE_stop\");\r\n";
      str += "source.addEventListener('msg_1',function(event){\r\n";
      str += "var ms1 = document.getElementById('msgs1');\r\n";
      str += "ms1.innerHTML = event.data;});\r\n";
      str += "source.addEventListener('msg_2',function(event){\r\n";
      str += "var ms2 = document.getElementById('msgs2');\r\n";
      str += "ms2.innerHTML = event.data;});\r\n";
      str += "source.addEventListener('msg_3',function(event){\r\n";
      str += "var ms3 = document.getElementById('msgs3');\r\n";
      str += "ms3.innerHTML = event.data;});\r\n";
      str += "function fnc1(){source.close();}\r\n";//STOPボタンを押したら切断する関数
      str += "</script></head><body><form><FONT size=\"1\">\r\n";
      str += "NTP Server Sync</FONT><br>\r\n";
      str += "<FONT size=\"3\"><b>ESP-WROOM-02(ESP8266)<br>Arduino Watch</b><br>\r\n";
      str += "<FONT size=\"6\" color=\"#7777FF\"><b>\r\n";
      str += "<div id=\"msgs1\">Event wait 1</div>\r\n";
      str += "<div id=\"msgs2\">Event wait 2</div>\r\n";
      str += "</b></FONT>\r\n";
      str += "Get Sync NTP server Time";
      str += "<div id=\"msgs3\">Event wait 3</div><br>\r\n";
      str += "<input type='button' id='SSE_stop' value='SSE STOP' onclick='fnc1()'>";
      str += "</form></body></html>\r\n";
      delay(1000);//1秒待ってレスポンスをブラウザに送信
      client.print(str);
      delay(1);//これが重要!これが無いと切断できないかもしれない。
      str = "";
      SSE_on = true;//Server-Sent Event 設定終了フラグ
      client.stop();
      Serial.println("\nGET HTTP client stop--------------------");
      req ="";
      SSE_Responce();
    }else if(req != ""){
      delay(1);
      client.stop();
      delay(1);
      client.flush();
    }
    req ="";
    yield();
  }
}
//**************Server-Sent Events データ送信関数****************************
void SSE_Responce(){
  //HTTPレスポンス1度目を送信したら、すぐにブラウザから2回目のGETリクエストが来る
  while(SSE_on == true){//無限ループ
    client = server.available();
    while(client.connected()){
      String req = client.readStringUntil('\r');
      if(req.indexOf("GET") != -1){//2回目のGETを検知したらServer-Sent Eventsレスポンス送信
        Serial.println("GET in--------------------");
        Serial.print(req);
        while(req.indexOf("Accept-Language") == -1){
          req = client.readStringUntil('\r');
          Serial.print(req);
        }
        if(SSE_on == true){
          Serial.println("\nsse responce send--------------------");
          String sse_resp;
          //ストリーム配信をブラウザが認識するためのレスポンス
          sse_resp = "HTTP/1.1 200 OK\r\n";
          sse_resp += "Content-Type:text/event-stream\r\n";//SSE使用時に必ずサーバー側からブラウザへこれを返す
          sse_resp += "Cache-Control:no-cache\r\n";
          sse_resp += "\r\n";//必ずこの空行が必要

          client.print(sse_resp);
          delay(3000);//ここの秒数はもう少し少なくても問題ない
          Serial.println(sse_resp);

          String sse_data;

          Serial.println("sse data send--------------------");
          String str_h;
          String str_m;
          String str_s;
          String sync_h="?";
          String sync_m="?";
          String sync_s="?";
          LastTime = millis();   
          while(client.connected()){//Event Sourceデータの無限ループストリーム送信
            if(hour()<10){//一桁の数値を二桁にする
              str_h = "0" + String(hour()) ;
            }else{
              str_h = String(hour());
            }
            if(minute()<10){
              str_m = "0" + String(minute()) ;
            }else{
              str_m = String(minute());
            }
            if(second()<10){
              str_s = "0" + String(second()) ;
            }else{
              str_s = String(second());
            }
            //30秒毎にNTPサーバーから時刻をゲットしてArduinoタイムを修正
            if(millis()-LastTime > 30000){
              WiFi.hostByName(ntpServerName, timeServerIP); 
              setSyncProvider(getNtpTime);
              LastTime = millis();
              sync_h = str_h;
              sync_m = str_m;
              sync_s = str_s;
            }
            sse_data = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける
            sse_data += "data:";
            sse_data += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける
            sse_data += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける
            sse_data += "event:msg_2\n";
            sse_data += "data:";
            sse_data += str_h+":"+str_m+":"+str_s;
            sse_data += "\n\n";
            sse_data += "event:msg_3\n";
            sse_data += "data:";
            sse_data += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示
            sse_data += "\n\n";
            client.print(sse_data);
            sse_data = "";
            yield();
          }
          delay(1);
          client.stop();
          delay(1);
          client.flush();
          Serial.println("Client.Stop-----------------");
          Serial.println();
          SSE_on = false;
          break;
        }
      }
      req = "";
      yield();
    }
    yield();
  }
}
//************NTPサーバータイム取得関数*****************************
const int timeZone = 9;     // 日本時間
//const int timeZone = 1;     // Central European Time
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)

time_t getNtpTime()
{
  while (udp.parsePacket() > 0) ; // discard any previously received packets
  sendNTPpacket(timeServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    delay(1);//これを入れないと更新できない場合がある。
    int size = udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  return 0; // return 0 if unable to get the time
}
//******************NTPリクエストパケット送信****************************
void sendNTPpacket(IPAddress& address)
{
//  Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

このプログラムをコンパイルする前に、プログラムの最初の方にあるご自分のルーターで設定したSSIDやパスワードの部分を書き換えるのを忘れずに。
const char* ssid = “xxxx”;←ここのxxxxの部分をご自分のルーターのSSIDに書き換える
const char* password = “xxxx”;←ここのxxxx部分をご自分のルーターのパスワードに書き換える
このプログラムの詳細な説明は後日アップする予定ですが、過去の記事を参考にしていただければある程度は理解できるかと思います。
NPTサーバー通信はサンプルコードを改良しました。

9.スケッチをコンパイルおよびWROOMに書き込み

ではいよいよスケッチをコンパイルしてWROOMのフラッシュに書き込みします。
Arduino IDEの書き込みボタンを押して、コンパイル&書き込みを行います。
以下のように赤いテキストでピリオドが連続して出ればOKです。

ただ、もし、うまくいかない場合は、USBを抜き、WROOMの電源を一旦切り、もう一度USBをパソコンと接続して立ち上げなおしてください。
USBを接続しなおした場合に1回目はコンパイルがうまくできません。直ぐもう一度書き込みボタンを押してください。そうすれば書き込みが始まると思います。

10.実行させる

ではいよいよ実行させていきます。
まず、WROOM(ESP8266)のジャンパーピンを下図のようにセットして、フラッシュブートモード(読み込み専用)にします。

そして、USBケーブルを抜き、WROOM(ESP8266)の電源を一旦落とします。
次に、ご自分のルーター(アクセスポイント)をあらかじめ立ち上げておき、ルーターの設定やファイアウォール、DHCP、アクセス制限などを確認し、WROOMがコネクトできるようにしておきます。
次に再度USBケーブルを接続してWROOMの電源を立ち上げ、直ぐにArduino IDEのシリアルモニターを起動しておきます。
シリアルモニターの右下の通信速度設定は115200bpsにします。
そして、起動後に再度WROOMのリセットボタンを押します。
すると、下図のように表示されます。

次にスマホのWi-Fiを起動し、Wi-FiのSSID、パスワードを設定し、最新ブラウザ(Android4.2.2以上 最新版Google Chrome、または iOS8.4以上 Safari)を起動し、下図のようにURL入力欄にご自分のルーターで割り当てられたローカルIPアドレスを入力します。

すると、以下のようにシリアルモニターにブラウザからのGETリクエストが表示され、WROOMからEvent-Streamレスポンスを返し、再度ブラウザからStreamのGETリクエストがあり、WROOMからServer-Sent Eventsのデータを無限連続ストリーミング送信する様子が表示されます。

動画はこのページの最初の方のYouTube動画をご覧ください。
このまま放っておくと、永遠にストリーミング送受信しますので、ブラウザの「SSE STOP」ボタンを押すとストリーミング停止します。
そうすると、シリアルモニターには以下のように表示されます。
停止後、数分すると再度自動的にルーターとコネクションし始めます。
この動作はなぜそうなるのかわかりません。
これはどうやら例外エラーのようです。いつかこれは解消したいと思います。
ちなみに、WROOM(ESP8266)がフラッシュダウンロードモード(書き込みモード)になっていると、再コネクションしませんので注意してください。

WDT例外エラーはなくなりました。
プログラムを改変しました。
Ikeuchi Toruさんの記事(現在リンク切れ)を参考に改変しました。
Ikeuchiさん、ありがとうございました。m(_ _)m

さて、皆さま、これでちゃんと動作しましたでしょうか?
ここで、
ちょっとしたトラブルシューティングをお知らせします。

●再コンパイルおよび書き込みができない場合
一度、プログラムを実行してストリーミング通信をしてしまった場合、Arduino IDEで再コンパイルおよび書き込みしようとしても、書き込みを完了できません。
その場合次の手順をやってみてください。

  • 一旦USBケーブルを抜いてWROOM(ESP8266)の電源を落とす。
  • GPIO 0番 をLOWに接続し、FLASHダウンロードモード(書き込みモード)にする。(忘れがち!!)
  • USBケーブルとパソコンを接続する。
  • 一呼吸置いて、Arduino IDEのコンパイル&書き込みボタンを押します。しかし、一回目はエラーになる。
  • もう一度コンパイル&書き込みボタンを押す。(エラーになったら、再度試してみる)

●実行しても、シリアルモニターに何も表示されない
次の手順を試してみてください。

  • 書き込みが完了したら、一旦USBを抜いてWROOM(ESP8266)の電源を落としてください。
  • その後、GPIO 0番 をHIGHに接続して、FLASHブートモード(読み込み専用モード)にする(忘れがち!!)
  • USBとパソコンを接続し、Arduino IDEのシリアルモニターを再起動する。
  • WROOMの電源を入れてからシリアルモニターを起動する時間が十分短ければ起動状況が表示されますが、シリアルモニターの起動が遅ければ既にWi-Fiセットアップ完了してしまっているので、シリアルモニターには表示されません。
    その場合は、WROOMのリセットボタン(無ければRSTピンとGNDをリード線でショートする)を押してリセットすると起動状態がモニターされます。

以上です。 みなさまもしっかり動作しましたでしょうか・・・?。
もし、これでうまく動かないなどありましたら、コメントやお問い合わせフォームでお問い合わせください。
ここまでお読み頂きありがとうございます。
では、また・・・

(追記)ESP32(ESP32-WROOM-32)やM5StackでServer-Sent Eventsを試すスケッチ例

(2019/10/21追記)
以上で紹介したものはESP8266用のスケッチでしたが、ESP8266の上位チップである、ESP32(ESP32-WROOM-32)を使った場合のスケッチ例を以下に記載しておきます。
M5StackもESP32を使用しています。
最近はメモリの少ないESP8266を使うのを止めて、ESP32やM5Stackに専念しています。
こちらの方が遙かに使いやすく、やりたいことは殆どできると思います。
因みに以下のスケッチは前節で紹介したESP8266用のスケッチをESP32用に替えただけです。
コーディングスタイルは未熟な当時のままですのでご容赦ください。
これには予めArduino core for the ESP32をインストールしておく必要があり、ボード設定をESP32にする必要がありますのでご注意ください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h> //timeライブラリver1.4以上

const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

boolean SSE_on = false;//Server-Sent Events設定が済んだかどうかのフラグ

WiFiServer server(80);

WiFiClient client;

//-------NTPサーバー定義----------------
unsigned int localPort = 2390;      // local port to listen for UDP packets
//IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP;
const char* ntpServerName = "time.windows.com";
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp;
IPAddress myIP;
long LastTime = 0;

//*****************セットアップ**********************
void setup() {
  Serial.begin(115200);//このシリアル通信はモニター用
  delay(10);

  // Connect to WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Start the server
  server.begin();
  Serial.println("Server started");

  myIP = WiFi.localIP();
  Serial.println(myIP);
  delay(2000);
  //NTPサーバーでタイムを取得
  udp.begin(localPort);
  WiFi.hostByName(ntpServerName, timeServerIP); 
  setSyncProvider(getNtpTime);
  delay(1000);
}

//メインループ***********************************************
void loop() {
  HTTP_Responce();
  yield();//これは重要!!これがないと動作しない。
}

//**********************Server-Sent Events レスポンス関数**************
void HTTP_Responce()
{
  client = server.available();//クライアント生成は各関数内でしか実行できないので注意
  while(client.connected()){
    String req = client.readStringUntil('\r');
    if (req.indexOf("GET / HTTP") != -1){//ブラウザからリクエストを受信したらこの文字列を検知する
      Serial.print(req);
      //ブラウザからのリクエストテキストを残らず読み込む
      while( client.available() ){
        req = client.readStringUntil('\r');
        Serial.print(req);
        yield();
      }
      String str;
      //-------ここからHTTPレスポンスのHTMLとJavaScriptコード
      str = "HTTP/1.1 200 OK\r\n";
      str += "Content-Type:text/html\r\n";
      str += "Connection:close\r\n\r\n";//1行空行が必要
      str += "<!DOCTYPE html><html><head>\r\n";
      str += "<meta name=\"viewport\" content=\"initial-scale=1.5\">\r\n";
      str += "<script type=\"text/javascript\">\r\n";
      str += "var source=new EventSource(\"";
      str += String(WiFi.localIP());//ルーターのローカルIPアドレスを自動取得
      str += "\");\r\n";
      str += "var obj1=document.getElementById(\"SSE_stop\");\r\n";
      str += "source.addEventListener('msg_1',function(event){\r\n";
      str += "var ms1 = document.getElementById('msgs1');\r\n";
      str += "ms1.innerHTML = event.data;});\r\n";
      str += "source.addEventListener('msg_2',function(event){\r\n";
      str += "var ms2 = document.getElementById('msgs2');\r\n";
      str += "ms2.innerHTML = event.data;});\r\n";
      str += "source.addEventListener('msg_3',function(event){\r\n";
      str += "var ms3 = document.getElementById('msgs3');\r\n";
      str += "ms3.innerHTML = event.data;});\r\n";
      str += "function fnc1(){source.close();}\r\n";//STOPボタンを押したら切断する関数
      str += "</script></head><body><form><FONT size=\"1\">\r\n";
      str += "NTP Server Sync</FONT><br>\r\n";
      str += "<FONT size=\"3\"><b>ESP-WROOM-02(ESP8266)<br>Arduino Watch</b><br>\r\n";
      str += "<FONT size=\"6\" color=\"#7777FF\"><b>\r\n";
      str += "<div id=\"msgs1\">Event wait 1</div>\r\n";
      str += "<div id=\"msgs2\">Event wait 2</div>\r\n";
      str += "</b></FONT>\r\n";
      str += "Get Sync NTP server Time";
      str += "<div id=\"msgs3\">Event wait 3</div><br>\r\n";
      str += "<input type='button' id='SSE_stop' value='SSE STOP' onclick='fnc1()'>";
      str += "</form></body></html>\r\n";
      delay(1000);//1秒待ってレスポンスをブラウザに送信
      client.print(str);
      delay(1);//これが重要!これが無いと切断できないかもしれない。
      str = "";
      SSE_on = true;//Server-Sent Event 設定終了フラグ
      client.stop();
      Serial.println("\nGET HTTP client stop--------------------");
      req ="";
      SSE_Responce();
    }else if(req != ""){
      delay(1);
      client.stop();
      delay(1);
      client.flush();
    }
    req ="";
    yield();
  }
}
//**************Server-Sent Events データ送信関数****************************
void SSE_Responce(){
  //HTTPレスポンス1度目を送信したら、すぐにブラウザから2回目のGETリクエストが来る
  while(SSE_on == true){//無限ループ
    client = server.available();
    while(client.connected()){
      String req = client.readStringUntil('\r');
      if(req.indexOf("GET") != -1){//2回目のGETを検知したらServer-Sent Eventsレスポンス送信
        Serial.println("GET in--------------------");
        Serial.print(req);
        while(req.indexOf("Accept-Language") == -1){
          req = client.readStringUntil('\r');
          Serial.print(req);
        }
        if(SSE_on == true){
          Serial.println("\nsse responce send--------------------");
          String sse_resp;
          //ストリーム配信をブラウザが認識するためのレスポンス
          sse_resp = "HTTP/1.1 200 OK\r\n";
          sse_resp += "Content-Type:text/event-stream\r\n";//SSE使用時に必ずサーバー側からブラウザへこれを返す
          sse_resp += "Cache-Control:no-cache\r\n";
          sse_resp += "\r\n";//必ずこの空行が必要

          client.print(sse_resp);
          delay(3000);//ここの秒数はもう少し少なくても問題ない
          Serial.println(sse_resp);

          String sse_data;

          Serial.println("sse data send--------------------");
          String str_h;
          String str_m;
          String str_s;
          String sync_h="?";
          String sync_m="?";
          String sync_s="?";
          LastTime = millis();   
          while(client.connected()){//Event Sourceデータの無限ループストリーム送信
            if(hour()<10){//一桁の数値を二桁にする
              str_h = "0" + String(hour()) ;
            }else{
              str_h = String(hour());
            }
            if(minute()<10){
              str_m = "0" + String(minute()) ;
            }else{
              str_m = String(minute());
            }
            if(second()<10){
              str_s = "0" + String(second()) ;
            }else{
              str_s = String(second());
            }
            //30秒毎にNTPサーバーから時刻をゲットしてArduinoタイムを修正
            if(millis()-LastTime > 30000){
              WiFi.hostByName(ntpServerName, timeServerIP); 
              setSyncProvider(getNtpTime);
              LastTime = millis();
              sync_h = str_h;
              sync_m = str_m;
              sync_s = str_s;
            }
            sse_data = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける
            sse_data += "data:";
            sse_data += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける
            sse_data += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける
            sse_data += "event:msg_2\n";
            sse_data += "data:";
            sse_data += str_h+":"+str_m+":"+str_s;
            sse_data += "\n\n";
            sse_data += "event:msg_3\n";
            sse_data += "data:";
            sse_data += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示
            sse_data += "\n\n";
            client.print(sse_data);
            sse_data = "";
            yield();
          }
          delay(1);
          client.stop();
          delay(1);
          client.flush();
          Serial.println("Client.Stop-----------------");
          Serial.println();
          SSE_on = false;
          break;
        }
      }
      req = "";
      yield();
    }
    yield();
  }
}
//************NTPサーバータイム取得関数*****************************
const int timeZone = 9;     // 日本時間
//const int timeZone = 1;     // Central European Time
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)

time_t getNtpTime()
{
  while (udp.parsePacket() > 0) ; // discard any previously received packets
  sendNTPpacket(timeServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    delay(1);//これを入れないと更新できない場合がある。
    int size = udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  return 0; // return 0 if unable to get the time
}
//******************NTPリクエストパケット送信****************************
void sendNTPpacket(IPAddress& address)
{
//  Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

以上、ESP32用のスケッチ例でした。
ではまた・・・。

最近は ESP-WROOM-02 ( ESP8266 )をさらに使い易くした ESPr Developer を使っています。
USB-シリアル変換、余裕のある容量の電源レギュレーター、ロジックレベル変換をパッケージにした ESP-WROOM-02 開発ボードです。
Wi-Fi通信が安定して実現できるので、超お勧めです。
こちらの記事も合わせてご覧ください。
https://www.mgo-tec.com/blog-entry-ss-wroom-howto01.html

その他、以下の記事では Wi-Fi 双方向通信したり、画像を転送したりもできてしまいました。
https://www.mgo-tec.com/blog-entry-jr-train-message-board-01.html
https://www.mgo-tec.com/blog-entry-easywebsocket-beta13.html
https://www.mgo-tec.com/blog-entry-wroom-esp8266-bme280-ssd1351-sd.html
https://www.mgo-tec.com/blog-entry-neopixel-paper-illumination01.html

コメント

  1. soarus より:

    参考にさせていただいています。
    一点、モジュールの個体差でしょうか
    我が家の環境では、
    while (millis() – beginWait < 1500) {
    の後に、delay(1);を挿入しないと
    NTPから取得したデータの更新が
    正常に機能せず、time関数の初期値で
    表示されていました。

  2. mgo-tec より:

    soarusさん
    貴重なコメントありがとうございました。
    当方の環境ではdelay(1);を入れなくても、iOS、Androidともに問題なかったので、他の方の環境で動くのかが疑問でした。
    動かなくて悩んでいる方にはとても助かる意見だと思います。
    早速、スケッチを追加修正させていただきました。
    当方の環境でもdelay(1);を追加しても問題なく動作しました。
    また何かありましたら、意見をお寄せください。

  3. A.koni より:

    とても興味深い記事だったので参考にさせていただいております。
    当記事の通りにセッティングを行い、プログラムをそのまま(SSID,PASSWARDは変えました)検証いたしましたところ、182行目の「setSyncProvider(getNtpTime); 」が引っ掛かってしまい、書き込むことができませんでした。
    どのようにしたら改善することができるのでしょうか。
    よろしくお願いします。

    -環境-
    windows7
    Arduino 1.6.7
    ↓エラーコード
    —————————————————————–
    C:\Users\aki\Documents\Arduino\ESP-WROOM-02\ESP-WROOM-02.ino: In function ‘void setup()’:
    ESP-WROOM-02:61: error: ‘getNtpTime’ was not declared in this scope
    setSyncProvider(getNtpTime);
    ^
    ESP-WROOM-02:61: error: ‘setSyncProvider’ was not declared in this scope
    setSyncProvider(getNtpTime);
    ^
    C:\Users\aki\Documents\Arduino\ESP-WROOM-02\ESP-WROOM-02.ino: In function ‘void loop()’:
    ESP-WROOM-02:67: error: ‘HTTP_Responce’ was not declared in this scope
    HTTP_Responce();
    ^
    C:\Users\aki\Documents\Arduino\ESP-WROOM-02\ESP-WROOM-02.ino: In function ‘void HTTP_Responce()’:
    ESP-WROOM-02:126: error: ‘SSE_Responce’ was not declared in this scope
    SSE_Responce();
    ^
    C:\Users\aki\Documents\Arduino\ESP-WROOM-02\ESP-WROOM-02.ino: In function ‘void SSE_Responce()’:
    ESP-WROOM-02:169: error: ‘hour’ was not declared in this scope
    if(hour()<10){
    ^
    ESP-WROOM-02:174: error: 'minute' was not declared in this scope
    if(minute()<10){
    ^
    ESP-WROOM-02:179: error: 'second' was not declared in this scope
    if(second()<10){
    ^
    ESP-WROOM-02:187: error: 'getNtpTime' was not declared in this scope
    setSyncProvider(getNtpTime);
    ^
    ESP-WROOM-02:187: error: 'setSyncProvider' was not declared in this scope
    setSyncProvider(getNtpTime);
    ^
    ESP-WROOM-02:195: error: 'year' was not declared in this scope
    sse_data += String(year())+"/"+String(month())+"/"+String(day());
    ^
    ESP-WROOM-02:195: error: 'month' was not declared in this scope
    sse_data += String(year())+"/"+String(month())+"/"+String(day());
    ^
    ESP-WROOM-02:195: error: 'day' was not declared in this scope
    sse_data += String(year())+"/"+String(month())+"/"+String(day());
    ^
    C:\Users\aki\Documents\Arduino\ESP-WROOM-02\ESP-WROOM-02.ino: In function 'time_t getNtpTime()':
    ESP-WROOM-02:229: error: 'sendNTPpacket' was not declared in this scope
    sendNTPpacket(timeServerIP);
    ^
    ESP-WROOM-02:242: error: 'SECS_PER_HOUR' was not declared in this scope
    return secsSince1900 – 2208988800UL + timeZone * SECS_PER_HOUR;
    ^
    exit status 1
    'getNtpTime' was not declared in this scope
    ——————————————————————————–

    • mgo-tec mgo-tec より:

      A.koniさん

      コメントありがとうございます。
      おそらく、Arduino IDE が1.6.7であることが原因だと思います。
      Arduino core for ESP8266 WiFi chip は推奨Arduino IDE は1.6.5 です。
      1.6.7ではまず正常に動作しないようです。

      当方のブログでも、最新版IDEのページへリンクを貼っていましたので、修正しました。
      私も1.6.7の動作確認は最近までやっておらず、申し訳ございませんでした。
      その他のページでも1.6.5をインストールするように修正しています。
      Arduino core for ESP8266 WiFi chipも1.6.5限定でなく、他のバージョンでも動作するようにしてほしいですね。
      ご指摘ありがとうございました。

      • kuma より:

        当方、1.6.9にて同じコンパイルエラーになっていました。
        #include を
        #include に変更するとコンパイル、動作ともにできました。
        最近Arduinoを始めたど素人ですが参考までに。

        • mgo-tec mgo-tec より:

          kumaさん

          コメントありがとうございます。
          Sever-Sent Events はしばらく使ってなかったもので、不具合が分かりませんでした。
          Arduino Timeライブラリも知らぬ間に結構変更されていて、ライブライ使用方法も変わっていることに気がづきませんでした。
          kumaさんのコメント欄では<>の中の文字が消えてしまってますが、現在のTimeライブラリでは
          #include <Time.h> → #include <TimeLib.h>
          と変更しなければならないということでした。
          kumaさん、こういうことでよろしいでしょうか?
          当方でもArduno1.6.9でこのように変更したら動作しました。
          大変失礼いたしました。
          記事を修正したいと思います。

  4. かわ より:

    有用な記事ありがとうございます。
    ビギナー過ぎて良く分からず、基本的なことかと思いますが質問させてください!

    2.Timeライブラリをダウンロードしてインストール
    のところで”Time”をクリックしてダウンロードとありますが、クリックすると右記のページが開きます。”http://www.pjrc.com/teensy/td_libs_Time.html”
    このページにある” Teensyduino Installer”をクリックして、”http://www.pjrc.com/teensy/td_download.html”を開き、
    以下の環境の中から使っている環境のインストーラをダウンロードしてArduinoフォルダの中の”libraries”フォルダにコピーすれば良いという認識で合っていますでしょうか

    Macintosh OS-X Installer
    Linux Installer (32 bit)
    Linux Installer (64 bit)
    Windows XP / 7 / 8 / 10 Installer

    宜しくお願いいたします。

    • mgo-tec mgo-tec より:

      かわさん

      記事をご覧頂きありがとうございます。
      ライブラリのページが知らぬ間に変わっている事が多く、私も困っております。

      今、多忙で記事訂正が出来ないのですが、Windowsであればこちらの記事のTimeライブラリインストールの節を参照してみて下さい
      (MACは持ち合わせておりませんので分かりません)
      https://www.mgo-tec.com/blog-entry-1616shinonome-ws-oled-news.html

      • かわ より:

        mgo-tecさん
        お忙しいところ迅速な返答ありがとうございます!
        リンク先確認いたします

        • mgo-tec mgo-tec より:

          かわさん

          先のコメントのkumaさんの件で気付いたのですが、Timeライブラリの仕様が変更されておりました。
          #include <Time.h> → #include <TimeLib.h>
          と変更しないと動きません。
          記事も修正しました。
          これで試してみてください。

  5. macsbug より:

    Arduino IDE 1.6.7 にて setSyncProvider(getNtpTime); でコンパイルエラーが出る件。
    void setup()の前に、以下を記述するとコンパイルが可能です。

    void HTTP_Responce();
    void SSE_Responce();
    time_t getNtpTime();
    unsigned long sendNTPpacket(IPAddress& address);

    私の方では Arduino IDE 1.6.7 でコンパイルできましたが、間違いかもしれませんので、他の方で試されてお返事を頂けると幸いです。

    • mgo-tec mgo-tec より:

      macsbugさん

      コメント投稿ありがとうございます。
      私は1.6.7はいろいろと問題があったので、今は使ってません。
      先のコメント投稿でもあったように、現在は1.6.9で快適に動作しております。
      setSyncProviderの件もTimeライブラリが知らぬ間にバージョンアップしていたりしてエラーになっていたようです。
      最新版をダウンロードした場合には
      #include<Time.h> → #include<TimeLib.h>に変更したら動作します。

      ただし、注意していただきたいのは、古いIDEをアンインストールする場合、Arduino15フォルダを削除せねばなりません。
      その方法はこちらの記事を参照にしてください。
      https://www.mgo-tec.com/esp8266-board-install01-html

  6. yoshi より:

    いつも大変お世話になっております。
    無事にEvent 1,2,3 表示しました。
    win10,arduino 1.8.0
    以前から取り組んでます、sensorValueが表示が出ません。

          }
                int V=sensorValue;
                sse_data = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける
                sse_data += "data:";
                sse_data += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける
                sse_data += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける
                sse_data += "event:msg_2\n";
                sse_data += "data:";
                sse_data += str_h+":"+str_m+":"+str_s;
                sse_data += "\n\n";
                sse_data += "event:msg_3\n";
                sse_data += "data:";
                sse_data += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示
                sse_data += "\n\n";
                client.print(sse_data);
                client.print(V);
                client.print("\n\n");
                sse_data = "";
                yield();
              }
              delay(1);
    

    このデータ送信関数部分では、2か所追加しましたが、画面表示に変化は見られません(本来の表示のみ)、又、試行錯誤中に文字列のみ表示時、sensorValue;値表示は、確認しました。
    Server-Sent-Eventが理解できてません。
    ヒント、勉強方法をご教授いただけませんか?

    • mgo-tec mgo-tec より:

      yoshiさん

      お返事遅くなり、すみません。
      Server-Sent Events は2年くらい使っていなかったので、思い出すまでに時間がかかってしまいました。

      こちらのページにコメントされたということは、Arduino との ATコマンド通信はやめて、ESP-WROOM-02 へ直接スケッチを書きこんでいるものと解釈させていただきます。
      そちらの方が私自身もお勧めしています。

      まず、過去の以下の記事をご参照ください。
      https://www.mgo-tec.com/blog-entry-36.html

      Server-Sent Events はコマンドをブラウザが認識しなければいけないので、V=sensorValue を “data:” の後に続けてテキスト形式で送らなければなりません。
      ですから、

      sse_data += "data:";

      の後に、

      sse_data += String( V );
      とします。
      時計を表示させないのであれば、
      
      sse_data += String(year())+"/"+String(month())+"/"+String(day());
      

      のところを書き替えても良いですね。

      まず、これで試してみて下さい。

  7. yoshi より:

    いつも大変お世話になっております。
    Event 1,2,3すべて表示できています。
    arduino 1.8.0 win10 仕様
    そこで、以前からお聞きしてます、senValue値を取り込む作業をしてますが、今だ、解決してません。
    この部分では、2か所追加しますたが、全く変化ありません。
    試行錯誤するなかで、文字列ばかりになり、その時、senValue値が確認できましたが、正常画面じゃありません 。
    どのあたりを修正すれば、良いか、ご教授いただけませんか?
    又、どんなURLを参考にすれば、良いか教えていただければ
    嬉しいです。
    よろしくお願いします。

              sync_m = str_m;
                  sync_s = str_s;
                  
                }
                int V=sensorValue;  追加
                sse_data = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける
                sse_data += "data:";
                sse_data += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける
                sse_data += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける
                sse_data += "event:msg_2\n";
                sse_data += "data:";
                sse_data += str_h+":"+str_m+":"+str_s;
                sse_data += "\n\n";
                sse_data += "event:msg_3\n";
                sse_data += "data:";
                sse_data += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示
                sse_data += "\n\n";
                client.print(sse_data);
                 sse_data = "";
                client.print(V);   追加
                client.print("\n\n");
                sse_data = "";
                yield();
    
    • mgo-tec mgo-tec より:

      yoshiさん

      つい先ほど修正点をお返事したとおり、

      client.print(V);
      

      が誤りです。
      例えば、

      sse_data += sync_h + ":" + sync_m + ":" + sync_s;
      

      のところをを書き替えて、

      sse_data += String( V );
      

      としてみてください。

      因みに、ESP-WROOM-02 ( ESP8266 ) のAnalog 入力は 0~1V の範囲しか計測できませんので、測定範囲が限定されてしまいます。
      ちょっと高度なプログラミングが必要ですが、別途、ADコンバーターを I2C や SPI接続で追加する方が、精度の高い電圧値取得ができます。

  8. yoshi より:

    いつも大変お世話になっております。
    Get Sync NTP server Timeの下にString( V )のanalog値を表示できました。
    有難うございます。
    ボードリセット+ブラウザ更新でanalog値の変化は確認できました。
    何もせずに秒数のようにanalog値を出すことは可能ですか、可能であれば
    どの辺りの修正が必要ですか?
    宜しくお願い致します。

    • mgo-tec mgo-tec より:

      とりあえず、表示出来て良かったですね。

      あまり詳しく書いていませんが、今一度以下の記事をお読みください。
      https://www.mgo-tec.com/blog-entry-36.html

      コネクション確立後は、closeされていなければ、続けてスマホにテキストデータを送れば良いです。
      msg_1 のところにデータを表示させたい場合、センサーデータを取得した値をV1とすると、ESP-WROOM-02 から以下のテキストを送信すれば良いです。

      event:msg_1\n
      data:V1\n
      \n
      

      テキストで送る方法は、

      client.print("event:msg_1\n");
      client.print("data:");
      client.print(String(V1));
      client.print("\n\n");
      

      最後に改行コードを2回送ることがポイントです。
      ただし、データを送る頻度はdelay(100);~delay(1000);以上にしておいた方が良いかも知れません。
      頻度が高すぎると、通信トラフィックを圧迫します。

      あと、ブラウザのテキストレイアウトを変えるには HTML と Javascript の知識が必要です。
      ここでは説明できませんので、ネットで検索してみてくださいませ。

  9. yoshi より:

    いつも大変お世話になっております。
    evnt 1の場所に常時、変化するアナログ値が表示出来ました。
    有難うございます。
    次は、csvでデータを取り込み、exsel表示できるよう頑張ってみます。
    又、お聞きすると思いますが、その時はよろしくお願いいたします。

               // int V1=sensorValue;
                sse_data = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける
                sse_data += String(system_adc_read()); 
                //sse_data += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける
                sse_data += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける
                sse_data += "event:msg_2\n";
                sse_data += "data:";
                sse_data += str_h+":"+str_m+":"+str_s;
                sse_data += "\n\n";
                sse_data += "event:msg_3\n";
                sse_data += "data:";
                sse_data += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示
                sse_data += "\n\n";
                client.print("event:msg_1\n");
                client.print("data:");client.println(system_adc_read());
                delay(300);
               // client.print(String(V1));
                client.print("\n\n");
                client.print(sse_data);
                sse_data += ""; 
                yield();
    
    • mgo-tec mgo-tec より:

      表示できてよかったですね。
      因みに、String変数にあまり多量の文字は収納できないので、長い文字列は分割して送った方が良いですヨ(^^)

  10. yoshi より:

    いつも大変お世話になっております。

    esp8266のserial表示をWIFi化出来ないと考え、esp8266をサーバー側
    PCをクライアント側でesp8266のanalogデータをPCで受信し、csvdataを溜めてexsel表示出来ないかと色々検索しましたが、もう一歩のところでダメでした。
    やはり、SSEを利用したWi-Fi ストリーミングしか、ないのかな思い戻ってきました。
    もし、Wi-Fi ストリーミングでデータを取り込み、csvデータとして保存可能ならば、是非、ご教授願います。
    ズバリ無いようであれば、参考URL等、教えてください。
    宜しくお願い致します。

    • mgo-tec mgo-tec より:

      yoshiさん

      いつもブログをご覧いただき、ありがとうございます。

      Server-Sent Events データをパソコンに取り込むには、HTML と Javascript の知識があれば可能かと思われます。
      私の場合は、一方向送信しかできない Sever-Sent Events はもう使っていません。
      今は双方向同時送受信が可能な WebSocket を使っています。
      このブログでは何度も取り上げています。
      ただ、クライアントのブラウザ経由でパソコンにログを記録することはやったことがありません。
      まぁ、これも HTML と Javascript の知識があれば可能です。

      その他、スマホの Blynkというアプリを使ってログがスマホに記録できたような気がします。
      どこかのサイトに方法があったと思われますが、忘れました。
      これはローカル環境だけでなく、クラウドサービスなので、世界中のどこからでもアクセスできます。
      意外とリアルタイム追従性は良いと思います。

      また、私は使ったことが無いのですが、最近流行っているクラウドサービスの Ambient を使う方法があります。
      https://ambidata.io/docs/esp8266/
      リアルタイムのレスポンス性は分かりません。

      その他別件ですが、パソコンではなく、ESP8266 に接続された micro SD カードにログを保存して、それから CSV で Excel で表示させたことがあります。
      以下の記事を参照してみてください。
      https://www.mgo-tec.com/blog-entry-bme280-excel.html

  11. yoshi より:

    いつもご丁寧にご返事いただき感謝しております。
    私なりに頑張ってやってみます。
    もし、有益な情報が出ましたらご連絡もらえれば
    嬉しいです。

  12. HitoriUser より:

    センサーの値をスマホにグラフで表示するプログラムを作りたいと思っていました。
    大変参考になりました。ありがとうございました。
    環境はmacOS Big Sur11.6 Arduino1.8.13 BME280の温度湿度圧力センサーを使いました。
    Timer.hTimerlin.hにかえ、IPADress myIP; myIP=WiFi.localIP()の宣言をし、
    str1 += String(WiFi.localIP());を下のように変えて行いました。
    str1 += String(myIP[0])+'.'+String(myIP[1])+'.'+String(myIP[2])+'.'+String(myIP[3]);

    • mgo-tec mgo-tec より:

      HitoriUserさん

      記事をご覧いただき、ありがとうございます。

      この記事はかなり古くて、今Arduino core ESP8266の環境も大幅に変わっていると思いますが、動いたんですね~。それはよかったです。

      ところで、ご存知かもしれませんが、スマホと連携してグラフを表示させるなら、ESP32を使ってAmbientというWebサイトを使えば超簡単にできますよ~!
      (^^)

タイトルとURLをコピーしました