自作LED電光掲示板に Yahooニュースや 天気予報 を表示させ、さらに NTP 時計機能追加しました

ESP8266 ( ESP-WROOM-02 )

5.スケッチを入力する

では、スケッチはこんな感じになります。

Yahoo! RSS ニュースが https ( SSL )化されたことに伴い、EasyWebSocket ライブラリも Beta ver 1.50 に一新しました。
スケッチも大幅変更しました。
解説は以下をご覧ください。
(2017/4/13)

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

/* WebSocketリアルタイムコントロール 8x8LEDドットマトリックス4連 電光掲示板サンプルスケッチ
 * Web記事取得、時計機能追加
 * ESP-WROOM-02(ESP8266) および Adafruit I2C 8x8 mini LED matrix専用
 * The MIT License (MIT)
 * Copyright (c) 2016 MGO-tec 
 * License reference URL --> https://opensource.org/licenses/mit-license.php
 */
#include <EasyWebSocket.h> //Beta ver 1.50 only
#include <Wire.h>
#include <TimeLib.h>
#include <WiFiUdp.h>
#include <UTF8toSJIS.h>
#include <MisakiFNT_read.h>
#include <I2C_Adafruit_8x8_LED_matrix.h>
 
const uint8_t LDaddrs[4] = { 0x70, 0x71, 0x72, 0x73 }; //LEDドライバーHT16K33 アドレス
 
const char* ssid = "xxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxx"; //ご自分のルーターのパスワードに書き換えてください
 
const char* UTF8SJIS_file = "/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* Zen_Font_file = "/MSKG13KU.FNT"; //全角フォントファイル名を定義
const char* Half_Font_file = "/mgotec48.FNT"; //半角フォントファイル名を定義
 
EasyWebSocket ews;
UTF8toSJIS u8ts;
MisakiFNT_read MFR;
I2C_Adafruit_8x8_LED_matrix adaLED;
  
uint16_t PingSendTime = 30000;
 
String ret_str="";
String scl_txt="";
 
uint8_t scl_cnt = 0;
uint8_t fnt_cnt = 0;
bool FntReadOK = true;
 
uint8_t LedDotOut[4][8] = {{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}};
uint8_t Next_buf[4][8] = {{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}};
uint8_t tmp_buf[8] = {0,0,0,0,0,0,0,0};
uint8_t tmp_buf_cnv[8] = {0,0,0,0,0,0,0,0};
uint8_t tmp_buf_cnv2[8] = {0,0,0,0,0,0,0,0};
uint8_t sj_txt[600];
uint16_t sj_cnt = 0;
uint16_t sj_length;
uint32_t SclTime;
uint16_t SclSpeed = 50;
bool Scl_Stop = false;
uint8_t Direction = 0;
uint8_t brightness = 0;
int16_t Angle = 0;
 
boolean sjis_txt_in = false;
boolean BW_reverse = false;
 
//-------NTPサーバー定義----------------
IPAddress timeServer(52, 168, 138, 145); //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
const int timeZone = 9;     // Tokyo
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
time_t prevDisplay = 0; // when the digital clock was displayed
uint32_t LastTime = 0;
 
boolean time_dot_set = false;
String time_str;
uint8_t time_sj_txt[20];
uint16_t time_sj_length;
uint8_t t_week_set = 0;
uint32_t ColonTime = 0;
 
struct TimeStrData
{ String week_jp; String week_en3; String week_en2; };
 
struct TimeStrData TSD[7] =
{
  {"日","Sun","Su"},{"月","Mon","Mo"},{"火","Tue","Tu"},{"水","Wed","We"},{"木","Thr","Th"},{"金","Fri","Fr"},{"土","Sat","Sa"}
};
 
//-----News & Weather-----------------------------------------------
uint8_t web_get = 0;
uint32_t Web_time = 0;
boolean first_get = false;
 
//***********セットアップ***************************************************
void setup() 
{
  Wire.begin(); // initialise the connection
  Wire.setClock(400000L); //HT16K33のクロックはMax 400kHz
  for(uint8_t i=0; i<4; i++) adaLED.LED_Driver_Initialize(LDaddrs[i]);
  delay(300);
   
  ews.AP_Connect(ssid, password);
 
  //NTPサーバーでタイムを取得
  Udp.begin(localPort);
  setSyncProvider(getNtpTime);
  delay(3000);
   
  SclTime = millis();
  LastTime = millis();
}
//**************メインループ*************************************************
void loop() {
  websocket_handshake();
   
  uint8_t i, br;
 
  ret_str = ews.EWS_ESP8266CharReceive(PingSendTime); //ブラウザからのWebSocketデータ受信
 
  if(ret_str != "_close"){
    if(ret_str != "\0"){
      if(ret_str != "Ping"){
        if(ret_str[0] != 't'){
          switch(ret_str[4]){
            case 'S':
              SclSpeed = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
              SclSpeed = 200-SclSpeed;
              Scl_Stop = false;
              break;
            case 'L':
              Direction = 0;
              break;
            case 'R':
              Direction = 1;
              break;
            case 'U':
              Direction = 2;
              break;
            case 'D':
              Direction = 3;
              break;
            case '_':
              Scl_Stop = true;
              break;
            case 'b':
              br = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
              //LEDドライバHT16K33の明るさは0~16
              for(i=0; i<4; i++) adaLED.LED_Driver_Brightness(LDaddrs[i], floor(br/12));
              break;
            case '0':
              Angle = 0;
              break;
            case '9':
              Angle = 90;
              break;
            case '1':
              Angle = 180;
              break;
            case '-':
              Angle = -90;
              break;
            case '#':
              BW_reverse = false;
              break;
            case '~':
              BW_reverse = true;
              break;
            case '(':
              time_dot_set = true;
              web_get = 0;
              break;
            case ')':
              time_dot_set = false;
              break;
            case '[':
              t_week_set = 1;
              break;
            case ']':
              t_week_set = 2;
              break;
            case '@':
              t_week_set = 0;
              break;
            case '/':
              web_get = 1;
              sjis_txt_in = false; time_dot_set = false; first_get = true;
              break;
            case '%':
              web_get = 0;
              break;
            case '<':
              web_get = 2;
              sjis_txt_in = false; time_dot_set = false; first_get = true;
              break;
            case '>':
              web_get = 0;
              break;
          }
        }else if(ret_str[0] == 't'){
          time_dot_set = false;
          scl_txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1);
          scl_txt += String(" ") + String("\0");
           
          u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, scl_txt, sj_txt, &sj_length);
          Serial.println();
          for (int iiii=0; iiii<sj_length; iiii++){
            Serial.write(sj_txt[iiii]);
          }
          Serial.println();
          fnt_cnt = 0; scl_cnt = 0; sj_cnt = 0; web_get = 0;
          FntReadOK = true; sjis_txt_in = true; Scl_Stop = false;
        }
        ret_str = "\0";
      }else{
        ret_str = "\0";
      }
    }
     
    if(sjis_txt_in == true && time_dot_set == false){ //電光掲示板スクロール
      if(millis() - SclTime > SclSpeed){
        if(sj_cnt >= sj_length){
          sj_cnt = 0;
        }
        if(FntReadOK==true){
          uint8_t cp = MFR.Sjis_To_MisakiFNT_DotRead(Zen_Font_file, Half_Font_file, Direction, Angle, sj_txt[sj_cnt], sj_txt[sj_cnt+1], tmp_buf_cnv);
          FntReadOK = false;
          sj_cnt = sj_cnt + cp;
 
          adaLED.LED_Black_White_Reversal(BW_reverse, tmp_buf_cnv, tmp_buf_cnv);
          adaLED.LED_Dot_Rotation(Angle, tmp_buf_cnv, tmp_buf_cnv2);
        }
 
        if(Scl_Stop == false){
          for(i=0; i<4; i++) adaLED.LED_8x8mini_Disp_Out(LDaddrs[i], LedDotOut[i]);
 
          switch(Direction){
            case 1:
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[3], LedDotOut[3], tmp_buf_cnv2);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[2], LedDotOut[2], Next_buf[3]);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[1], LedDotOut[1], Next_buf[2]);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[0], LedDotOut[0], Next_buf[1]);
              break;
            default:
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[0], LedDotOut[0], tmp_buf_cnv2);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[1], LedDotOut[1], Next_buf[0]);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[2], LedDotOut[2], Next_buf[1]);
              adaLED.Scroller_Dot_Replace( Direction, Next_buf[3], LedDotOut[3], Next_buf[2]);
              break;
          }
          scl_cnt++;
        }
         
        if(scl_cnt == 8){
          scl_cnt = 0;
          FntReadOK = true;
        }
        SclTime = millis();
      }
    }
  }else if(ret_str == "_close"){
    ret_str = "\0";
    scl_txt = "";
  }
 
  if(time_dot_set == true){ //時計表示設定
    if(now() != prevDisplay || millis()-ColonTime >= 500L){
      char h_chr[3], m_chr[3], s_chr[3];
      sprintf(h_chr, "%02d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
      sprintf(m_chr, "%02d", minute());
      sprintf(s_chr, "%02d", second());
 
      switch(t_week_set){
        case 0:
          if(Angle == 90 || Angle == -90){
            time_str = String(h_chr) + String(m_chr) + String(s_chr) + String(TSD[weekday()-1].week_jp);
          }else{
            time_str = String(h_chr) + ':' + String(m_chr) + ':' + String(s_chr);
          }
          break;
        case 1:
          if(Angle == 90 || Angle == -90){
            time_str = String(h_chr) + ": " + String(m_chr) + String(TSD[weekday()-1].week_jp);
          }else{
            time_str = String(h_chr) + ':' + String(m_chr) + ' ' + String(TSD[weekday()-1].week_jp);
          }
          break;
        case 2:
          if(Angle == 90 || Angle == -90){
            time_str = String(h_chr) + ": " + String(m_chr) + String(TSD[weekday()-1].week_en2);
          }else{
            time_str = String(h_chr) + ':' + String(m_chr) + String(TSD[weekday()-1].week_en3);
          }
          break;
      }
 
      if(t_week_set > 0){
        if(millis()-ColonTime >= 500L){
          time_str[2] = ' ';
          ColonTime = millis();
        }else{
          prevDisplay = now();
          ColonTime = millis();
        }
      }
       
      u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, time_str, time_sj_txt, &time_sj_length);
      uint8_t time_dot[4][8];
      if(Direction != 0) Direction = 0;
      for(i=0; i<4; i++) MFR.Sjis_To_MisakiFNT_DotRead(Zen_Font_file, Half_Font_file, Direction, 0, time_sj_txt[i*2], time_sj_txt[i*2+1], time_dot[i]);
      uint8_t dummy_time_dot[4][8];
      for(i=0; i<4; i++) adaLED.LED_Black_White_Reversal(BW_reverse, time_dot[i], time_dot[i]);
      for(i=0; i<4; i++) adaLED.LED_Dot_Rotation(Angle, time_dot[i], dummy_time_dot[i]);
       
      if(Angle == 0 || Angle == 90){
        for(i=0; i<4; i++) adaLED.LED_8x8mini_Disp_Out(LDaddrs[i], dummy_time_dot[3-i]);
      }else{
        for(i=0; i<4; i++) adaLED.LED_8x8mini_Disp_Out(LDaddrs[i], dummy_time_dot[i]);
      }
    }
  }
 
  if(web_get == 0){
    if(millis()-LastTime >= 300000L){//300秒毎にNTPサーバーからタイム取得
      setSyncProvider(getNtpTime);
      Serial.println(time_str);
      LastTime = millis();
    }
  }else{
    if(millis()-LastTime >= 240000L){ //Web記事取得時でもNTPサーバからタイムを補正しておく
      setSyncProvider(getNtpTime);
      LastTime = millis();
    }
  }
 
  if(web_get > 0){ //Web記事取得
    if(first_get == true || millis()-Web_time > 300000L){ //Web記事を5分毎に取得
      const char* news_host = "news.yahoo.co.jp";
      String news_target_ip = "/rss/topics/top-picks.xml"; // トップニュースタイトルページ
      const char* weather_host = "rss.weather.yahoo.co.jp";
      String weather_target_ip = "/rss/days/4410.xml"; //東京の天気予報ページ
      String yyy = "";
      char Web_h[3], Web_m[3];
      sprintf(Web_h, "%02d", hour());//ゼロを空白で埋める場合は%2dとする
      sprintf(Web_m, "%02d", minute());
      yyy = String("◆") + String(Web_h) + ":" + String(Web_m) + " "; //半角が奇数個の場合は末尾にスペースを入れる
      switch(web_get){
        case 1:
          yyy += ews.EWS_https_Web_Get(news_host, news_target_ip, '\n', "</rss>", "<title>", "</title>", "◆");
          break;
        case 2:
          yyy += ews.EWS_Web_Get(weather_host, weather_target_ip, '>', "</rss>", "【", " - ", "【");
          break;
      }
      yyy.replace("&amp;","&"); //XMLページのソースコードでは半角&は"&amp"と表示されてしまうので、全角に変換する
      Serial.println();
      u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, yyy, sj_txt, &sj_length);
      Serial.println(yyy); //Arduino IDE 1.8.2以降の場合はシリアルモニターに UTF-8 出力でOK
      yyy ="";
      /*//Arduino IDE 1.8.1以下の場合、windowsのシリアルモニターはShift_JISで出力する
      for(int iy=0; iy<sj_length; iy++){
        Serial.write(sj_txt[iy]); 
      }
      */
      Serial.println();
      fnt_cnt = 0; scl_cnt = 0; sj_cnt = 0;
      sjis_txt_in = true;  FntReadOK = true;  time_dot_set = false;
      Web_time = millis();
      first_get = false;
    }
  }
}
//*************************NTP Time**************************************
time_t getNtpTime()
{
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      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;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}
//*************************NTP Time**************************************
void sendNTPpacket(IPAddress &address)
{// send an NTP request to the time server at the given address
  // 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();
}
//************************* Websocket handshake **************************************
void websocket_handshake(){
  
  if(ews.Get_Http_Req_Status()){ //ブラウザからGETリクエストがあったかどうかの判定
    String html_str1="", html_str2="", html_str3="", html_str4="", html_str5="", html_str6="", html_str7="";
  
    html_str1 = "<body style='background:#ddddff; color:#000;'>\r\n";
    html_str1 += ews.EWS_TextBox_Send("txt3", "日本語漢字、半角カタカナもOK!","送信");
    html_str1 += "<br>\r\n";
    html_str1 += ews.EWS_On_Momentary_Button("Left", "←", 60,25,15,"#FFFFFF","#0055ff");
    html_str1 += ews.EWS_On_Momentary_Button("Right", "→", 60,25,15,"#FFFFFF","#0055ff");
    html_str1 += ews.EWS_On_Momentary_Button("Up", "↑", 60,25,15,"#FFFFFF","#0055ff");
    html_str1 += ews.EWS_On_Momentary_Button("Down", "↓", 60,25,15,"#FFFFFF","#0055ff");
    html_str2 = "<br><br>Brightness\r\n";
    html_str2 += ews.EWS_Canvas_Slider_T("bright",200,40,"#777777","#55ff55");
    html_str2 += "<br><br>Scrolle Speed\r\n";
    html_str2 += ews.EWS_Canvas_Slider_T("Speed",200,40,"#777777","#ffaaff");
    html_str2 += "<br><br>\r\n";
    html_str3 += ews.EWS_On_Momentary_Button("_SclStop", "Scrole Stop", 100,25,15,"#ffffff","#777777");
    html_str3 += "<br><br>\r\n";
    html_str3 += ews.EWS_On_Momentary_Button("0", "0°", 60,25,15,"#ffffff","#7777ff");
    html_str3 += "<span>  </span>\r\n";
    html_str3 += ews.EWS_On_Momentary_Button("90", "90°", 60,25,15,"#ffffff","#7777ff");
    html_str3 += "<span>  </span>\r\n";
    html_str3 += ews.EWS_On_Momentary_Button("-90", "-90°", 60,25,15,"#ffffff","#7777ff");
    html_str3 += "<span>  </span>\r\n";
    html_str3 += ews.EWS_On_Momentary_Button("180", "180°", 60,25,15,"#ffffff","#7777ff");
    html_str3 += "<br><br>\r\n";
    html_str4 = ews.EWS_On_Momentary_Button("#rev", "Normal", 70,25,15,"#000000","#ffffff");
    html_str4 += "<span>  </span>\r\n";
    html_str4 += ews.EWS_On_Momentary_Button("~rev", "Reverse", 70,25,15,"#ffffff","#0000ff");
    html_str4 += "<br><br>\r\n";
    html_str4 += "<div style='width:330px; height:70px; text-align:center; padding:10px; background:#ccccff; border:#0000ff solid 1px;'>\r\n";
    html_str4 += ews.EWS_On_Momentary_Button("(time", "Watch ON", 90,25,15,"#000000","#ffffff");
    html_str4 += "<span>  </span>\r\n";
    html_str4 += ews.EWS_On_Momentary_Button(")timeOFF", "Watch OFF", 90,25,15,"#ffffff","#0000ff");
    html_str4 += "<br><br>\r\n";
    html_str5 = ews.EWS_On_Momentary_Button("[week1", "曜日(漢字)", 110,25,15,"#000000","#ffffff");
    html_str5 += "<span>  </span>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button("]week2", "Week", 100,25,15,"#000000","#ffffff");
    html_str5 += "<span>  </span>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button("@week0", "NonWeek", 100,25,15,"#ffffff","#0000ff");
    html_str5 += "</div><br><br>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button("/News", "News ON", 100,25,15,"#000000","#ffffff");
    html_str5 += "<span>  </span>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button("%News", "News OFF", 100,25,15,"#ffffff","#0000ff");
    html_str5 += "<br><br>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button("<News", "Weather ON", 110,25,15,"#000000","#ffffff");
    html_str5 += "<span>  </span>\r\n";
    html_str5 += ews.EWS_On_Momentary_Button(">News", "Weather OFF", 110,25,15,"#ffffff","#0000ff");
    html_str5 += "<br><br>\r\n";  
    html_str6 = ews.EWS_Status_Text(20,"RED");
    html_str6 += "<br>\r\n";
    html_str6 += "<br><br>\r\n";
    html_str6 += ews.EWS_WebSocket_Reconnection_Button("WS-Reconnect", 200, 40, 17);
    html_str6 += "<br><br>\r\n";  
    html_str6 += ews.EWS_Close_Button("WS CLOSE",150,40,17);
    html_str6 += ews.EWS_Window_ReLoad_Button("ReLoad",150,40,17);
    html_str6 += "</body></html>\r\n";
    html_str7 = "";

    //WebSocket ハンドシェイク関数
    ews.EWS_HandShake_main(0, "/spiffs_01.txt", "", "", "", IPAddress(0,0,0,0), html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7);
  }

}

これはMITライセンスです。
ネット上ではライセンス表記しないと配布できないそうなのでそうしてます。
配布、改変、商用利用は自由。だたし、無保証で、ライセンス表記はしておいてくださいというものです。

ご覧のように、グローバル変数を沢山使っています。あまり良くないのですが、再接続したりすることが多々あるのでこうしてます。
でも、ESP-WROOM-02 はSRAMメモリが膨大なので全く問題有りません。

では、順番にザッと解説していきます。
前回の記事で説明したものは省略させていただきます。

Yahoo! RSSサイトが https ( SSL )化されたことに伴い、スケッチを大幅変更しました。
EasyWebSocket beta 1.50 に変更したことにより、主に以下を変更しています。

●44行:
SRAMメモリ削減のため、600バイトとしました。
これは、天気予報のバイト数までとしました。

●412-476行:
EasyWebSocket Beta 1.50 にしたことにより、メモリを節約しなければなりません。
そのため、html_str 文字列をグローバル変数領域を使わず、ローカル変数内で定義しました。
そうすれば、ローカル関数内を抜けるとメモリを解放してくれます。
そして、ハンドシェイク関数が新しくなりました。

●341行:
EasyWebSocket Beta 1.50 で登場した、https ( SSL )サイトの記事取得関数です。
これはメモリを多く消費しますので、使う場合は要注意です。
使い方は EWS_Web_Get と同じです。
天気予報はまだ http のままです。

●350-356行:
Arduino IDE 1.8.2 からシリアルモニターにUTF-8コード日本語文字が表示できるようになりました。
それ以前のバージョンを使う場合はコメントを解除して、従来通り Shift_JIS 出力にしてください。

●18-19行:
ご自分のルーターのSSIDとパスワードに書き換えてください。
因みに、スケッチフォルダ内のdataフォルダにあるspiffs_01.txtファイルの ws://192.168.0.14 というところもご自分のESP-WROOM-02のローカルIPアドレスに書き換えておくのを忘れずに。

●23行:
美咲フォントの半角の数字だけを独自に改変したファイルをここで定義してます。
4X8.FNT にしたければ変更してください。

●58-65行:
NTPサーバー時刻取得関係の定義です。
これは、ESP8266 for Arduino のスケッチの例のNTPClient というスケッチをそのまま流用してます。

●74-80行:
電光掲示板の時計表示で曜日の文字を構造体で定義してます。

●98-99行;
ここでNTPサーバーから時刻を取得します。

●167行:
412-476行の関数を呼び出して、WebSocket ハンドシェイク(コネクション確立)してます。ループ内に置けば切断されてもすぐに再接続できるようになってます。

●111行:
スマホのブラウザから送信されてくる文字列を取得

●117-191行:
スマホのブラウザのボタンから送信されてくる文字列の最初の文字だけで電光掲示板の動作を選別しています。
161-176行で時間表示する方法を選別。
177-190行でYahoo ニュースや天気予報を取得する選別。

●192-205行:
ブラウザのテキストボックスで送信されてきたデータを電光掲示板に表示する選別

●212-252行:
得られたテキストデータを美咲フォントドットを抽出してスクロールしていきます。
231行からの Scroller_Dot_Replace は I2C_Adafruit_8x8_LED_matrix ライブラリのバージョンが1.1 から追加されたものです。
実際に出力されるビットは LedDotOut で、それは227行で出力されます。

●258-313行:
時計表示で、ESP-WROOM-02の内部時計から文字列を組んでいます。Timeライブラリを使用してます。
361-363行ではTimeライブラリ関数では一桁表示の場合もあるので、2桁表示に固定して、ゼロで穴埋めしています。
365-387行では文字の回転によって時刻表示の文字列を変えています。
389-397行ではコロン「:」を0.5秒ごとに点滅させてます。
399-311行で文字列から美咲フォントを抽出してLEDに出力してます。

●315-326行:
NTPサーバーから時刻を取得するタイミングを設定してます。

●328-363行:
ここではYahoo記事を取得してます。
329行はとても重要です。ここでは300秒(5分)毎に記事を取得してますが、これを間違えて、ものすごく高速で記事取得しに行くと、相手サーバーからDOS攻撃と判断されてブラックリストに載ってしまうか、サーバーをダウンさせてしまいかねませんので十分注意してください。当方ではいかなる事故でも責任を負えませんので、皆さま個人の責任において使用してください。ここは個人レベルの電子工作では十分注意しなければいけないところですね。

330-333行で取得する記事のHOSTサーバーアドレス、ターゲット記事のアドレスを定義してます。
335-338行で、記事を取得する時間を表示させる文字列を構成してます。
441行でEasyWebSocketのバージョンBeta1.50 ライブラリです。
https ( SSL )対応にしました。
この関数はメモリを多量に消費するため、使う場合は要注意です。

ews.EWS_https_Web_Get(”HOSTアドレス文字列”, ”ターゲットアドレス文字列”, ‘区切り文字’, “終端文字列”, “抽出開始文字列”, “抽出終了文字列”, “段落区切り文字”)

HOSTアドレスはchar型ポインタで、ターゲットアドレスはString型です。
実際に欲しい情報が https://news.yahoo.co.jp/rss/topics/top-picks.xml にあるとすると、
HOST = news.yahoo.co.jp
target = /rss/topics/top-picks.xml
となります。

ここで、Yahoo RSS のニュース記事をブラウザでソースコード表示させてみると、改行でそれぞれの文字列が区切られていることが分かります。ですからニュースは \n で区切れば良いのです。
しかし、天気予報はブラウザで見るとxml表示になっていて、ソースコード表示させると1行の間に改行無しで、もの凄く長い文字列となっていることが分かると思います。これでは改行で区切るとオーバーフローしてしまいます。
ですから、天気予報では’>’ という文字で区切って細切れにして、その中から天気予報文字列を挟んでいる共通の文字列を探します。すると、’【’と ‘ ‐ ‘ という文字で挟まれている間の文字列を抽出すれば良いことが分かります。そうするとLED表示では ‘【’ という文字がカットされてしまうため、段落区切り文字を ‘【’ として追加しているというわけです。

段落区切り文字は段落の区別をするために追加したい文字を何でもよいので追加してください。スペースでもOKです。

因みに、この関数は本当はこのライブラリに入れたくなかったのですが、WebSocket通信中に制御するとなるとclientの定義で不都合が出てくるので止む無くEasyWebSocketライブラリ内に設置した次第です。

●366-410行:
NTPサーバー関係の関数です。
これはIDEのスケッチの例のNTPClient スケッチそのままの流用です。

●414-470行:
EasyWebSocket beta 1.50 から追加した関数で、ブラウザから GETリクエストがあった場合のみ true を返します。
そうしたら html文字列を読み込むようにすれば、ESP8266 のメモリを節約できます。
ローカル関数内で文字列を読み込むことによって、それを抜けるとメモリを解放するので、この方法が良いと思います。
417-470行で、コネクション確立時にブラウザに送信する HTMLタグ文字列を String変数に格納しています。
これは独自のEasyWebSocketライブラリを使用してます。
このライブラリの使用方法は こちらのページ を参照してください。
ただ、今回はさらにバージョンアップした 1.50 を使います。

●465行:
EasyWebSocket Beta1.39以降で追加されたライブラリです。
これが排出するHTMLタグでは、ボタンを押すとHTMLヘッダファイルのspiffs_01.txtファイルにある、JavaScriptの init() という関数を呼び出し、WebSocket通信だけをスムースに再接続します。Reloadボタンよりもこちらの方がスムースに再接続できます。

ソースコードの説明は以上です。

6.コンパイル実行させる。

コンパイル実行させると、シリアルモニターにはこのように表示されます。

NTPサーバーから最初に時刻を取得しているのがわかると思います。
次にスマホのブラウザのURL入力欄にESP-WROOM-02のローカルIPアドレスを入力してコネクションします。数十秒時間がかかりますので、しばらくお待ちください。
シリアルモニターでWebSocket Response Complete! と表示されたら、スマホ側で操作可能となります。

ボタンについてはこんな感じです。

「News ON」 ボタンを押すと Yahoo RSS ニュースを取得します。
取得には10秒ほどかかります。ヘッドラインだけです。
この間、スクロールや時計表示は停止します。
これを停止させないようにするには、別途Arduino Pro mini などを使って電光掲示板表示専用のCPUを使うしかないかと思います。

ちなみにシリアルモニターにはこんな感じで表示されます。

天気予報も同じように表示されると思います。
これを表示させると、WebSocket通信は強制的に切断されますのでご注意ください。
それはそうですよね。WebSocket通信しながら、別のHTTPリクエストを送るわけですからWebSocketエラーになって切断されてしまいます。
ですから、その後にブラウザからコントロールしたければ再接続しなければなりません。そこだけ頭に入れておいてください。

これで面白いことに、WebSocketで通信していようが、切断されていようが、UDP通信ではNTPサーバーと通信できているんですね。
ポート番号が異なることで競合が起きないんでしょうか・・・。不思議です。

というわけで、今回は以上です。
これで、念願のスマホ連携で日本語Web記事取得に成功しました。
これからかなり色んなことができそうですよ。

ではまた・・・。

最新記事では超小型の有機EL(OLED)SSD1306 を使ったNTP時計付き電光掲示板も作りました。
こちらをご覧ください
→https://www.mgo-tec.com/blog-entry-wroom-oled-news-ntp01.html

コメント

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