ESP8266 x Server-Sent Events
リアルタイムにWebServerからデータを更新する
通常WEBだとリロードや更新をしないとデータが反映されないのですが、
Server-Sent Events を利用するとリアルタイムにデータが更新されます。
このサンプルをインターネットに繋がるWIFIの設定をして起動すると
PCのブラウザ(ただしIEは不可 chrome推奨)からhttp://172.20.10.2
と接続すると時計がリアルタイムに更新されていることが確認できます。
ソースコード
#include
#include
//#include
//https://github.com/exabugs/sketchLibraryNTP
#include //timeライブラリver1.4以上
//time
//https://github.com/PaulStoffregen/Time
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 char* ntpServerName = "ntp.nict.jp";
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;
long LastTime = 0;
void printDigits(int digits)
{
// utility function for digital clock display: prints preceding colon and leading 0
Serial.print(':');
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
//*****************セットアップ**********************
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");
// Print the IP address
Serial.println(WiFi.localIP());
//NTPサーバーでタイムを取得
udp.begin(localPort);
WiFi.hostByName(ntpServerName, timeServerIP);
setSyncProvider(getNtpTime);
delay(3000);
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(' ');
Serial.print(day());
Serial.print(' ');
Serial.print(month());
Serial.print(' ');
Serial.print(year());
Serial.println();
}
//メインループ***********************************************
void loop() {
HTTP_Responce();
yield();//これは重要!!これがないと動作しない。
}
String ip2Str(IPAddress ip){
String s="";
for (int i=0; i<4; i++) {
s += i ? "." + String(ip[i]) : String(ip[i]);
}
return s;
}
//**********************Server-Sent Events レスポンス関数**************
void HTTP_Responce()
{
client = server.available();//クライアント生成は各関数内でしか実行できないので注意
while(client.status()!=CLOSED){
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();
}
// char *lcdBuffer;
IPAddress ip = WiFi.localIP();
//sprintf(lcdBuffer, "%d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], udpPort);
// sprintf(lcdBuffer, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
String str_ip = ip2Str(ip);
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 += "\r\n";
str += "\r\n";
str += "\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.status()!=CLOSED){
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.status()!=CLOSED){//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リクエストパケット送信****************************
unsigned long 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();
}