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

記事公開日:2016年3月24日
最終修正日:2017年4月13日

スポンサーリンク

Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。
EasyWebSocketライブラリを Beta ver 1.50 にすると動作します。
それに伴い、スケッチも改良しました。
以下をご覧ください。
(2017/4/13時点)

こんばんは。

なかなか忙しくて、記事アップがだいぶ遅れてしまいました。
プログラムが複雑になるほど記事を書くのが辛くなってくる今日この頃です。

さて、今回は前回の記事から更に新たな機能を2つほど追加しました。いよいよIoTとして一歩踏み出した感じです。

去年の こちらの記事 でほとんど諦めていたWeb記事を自動取得して自作電光掲示板に表示させることが、ついにできました!!!

これができるようになったのは独自にUTF8文字コードをShift_JISに変換するテーブルファイルを作成できたことが大きいですね。(こちらの記事を参照)
諦めないで続けていて良かった・・・。

Yahoo Japan の RSS はHTMLタグの解析がし易く、電子工作で表示させるには最適ですね。そして、記事内容が豊富で選択肢がたくさんあります。
ただ、これを無料で利用するには個人用途限定だそうです。 商用で使用するにはご注意ください。

動画はこんな感じです。
(これは ESP-WROOM-02 ( ESP8266 )と Adafruit I2C 8×8 mini LED matrix だけを使ってスマホのブラウザからWebSocket通信で電光掲示板をコントロールしています)
因みに、この動画を掲載するにあたって、Yahooヘルプセンターに確認して、個人利用ブログなら問題ないという回答をいただいております。

いかがでしょうか。
例えばデスクにこれを置いておいて、トップニュースタイトルだけでも常時流しておけば、パソコンやスマホを開かなくてもニュースを把握できてイイですね。

美咲フォントですから複雑な漢字はさすがに読みにくいです。
でも、遠くから見ると結構把握できますよ。8X8ドットなので贅沢は言えません。
もし、確実に漢字を読めるようにするには16×16ドットは必要ですね。
個人的には、ある程度把握できれば良いのでこれで十分です。

天気予報も取得できていますね。文字だから分かりにくいのですが、今後はこの文字列を基に独自にドットを作成してお天気マークを表示させることもできそうです。
そして、それで別途LEDやモーターを制御することもできそうですね。
因みに、WebSocket通信を切断しても、ニュース記事や天気予報記事は5分毎に自動で記事を更新します。スマホの電源を切っても大丈夫です。

それと、半角数字は全角と混ざるとバランスが悪かったので、4X8.FNTファイルの数字部分を独自に改変させていただきました。

また、NTP時計機能も追加しました。
インターネットのNTPサーバから時刻を自動取得して定時補正しています。
曜日を漢字表示できる機能も追加しました。
NTP時計は5分毎に補正してます。Yahoo記事取得時では4分毎に補正します。

スマホ側でスイッチを沢山作ることが出来るので、ハード側は一切スイッチ無しっていうのがイイですね。

では、これの作り方について説明していきます。

1.準備するもの

●ESP-WROOM-02開発ボード ( スイッチサイエンス製 )
Amazonさんではこんな感じで販売してます。

最近、スイッチサイエンスさんではこんなものも販売されてますね。ピンアサインを同じように使用すればこれも使えると思います。

●I2C 8×8 mini LED matrix (Adafruit製)
色はどれでも良いです。ただ、青や白は高いです。
これを4つ揃える必要があります。
間違えないでいただきたいのは1.2インチではなく、ミニLEDマトリックスの方ですのでご注意ください。
Amazonさんでは私はマルツさん販売のものを購入しました。

2.Arduino IDE やESP-WROOM-02を設定する

※Arduino IDE 1.6.11以降で動作します。こちらの記事を参照して、Arduino15フォルダを削除してから再インストールすれば動作します。(2016/8/29時点)

ESPr Developer ( ESP-WROOM-02 開発ボード )の組み立てや使い方はこちらの記事を参照してください。

そして、前回の記事の 2~5までの設定を予め済ませておいてください。

ただ、今回からバージョンアップしたライブラリがありますので、以下のライブラリを新たにダウンロードし直してインストールしておいてください。

EasyWebSocket Beta 1.50
このバージョンでは https ( SSL )Webページを取得する関数を追加しています。

MisakiFNT_read_for_ESP8266  Beta 1.1
このバージョンでは全角と半角文字混在の場合のレイアウトを修正してます。

I2C_Adafruit_8x8_LEDmatrix_library Beta 1.1
このバージョンではドットスクロールもライブラリ化してます。

初めて当ブログをご覧になる方はあちこちの記事を参照しなければいけないと思いますが、ご容赦ください。

3.Timeライブラリのインストール

Arduino.ccページから最新のTimeライブラリをダウンロードします。
こちらのページ を開くとこのように表示されます。
ws_Yahoo_message05
newer versions のTimeライブラリのリンクをクリックすると下図のように表示されます。
ws_Yahoo_message06

そしたらGitHubをクリックすると下図のように表示されるので、ZIPファイルをダウンロードします。

ws_Yahoo_message07

それを解凍して、Time-masterフォルダの中に同じ名前のTime-masterフォルダがありますので、そのフォルダごとご自分のArduino IDEのlibrariesフォルダにコピペします。
それが終わったらIDEを再起動すればインストール完了です。

4.独自フォントをダウンロードしてSPIFFSファイルシステムでESP-WROOM-02のフラッシュにアップロードしておく

美咲フォントの半角フォント 4X8.FNT では全角に対して数字が小さすぎて「8」という数字が判別しにくいので、数字だけを独自に改変させていただきました。アルファベットや記号、カタカナはそのままです。
フリーのバイナリエディタ、Stirling で修正してみました。
美咲フォントのライセンスを読むと、配布、改変は自由とのことでしたので、当方で改変したファイルはGitHubの こちらのページ にあります。
ダウンロードして解凍しておいてください。

mgotec48.FNT

というファイルを予め保存してある空白サンプルスケッチのdataフォルダにコピーしておきます。
次にSPIFFSファイルシステムを使用して ESP-WROOM-02 のフラッシュメモリにアップロードしておいてください。
SPIFFSファイルシステムについて分からない方は こちらのページ を参照してください。

5.スケッチを入力する

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

Yahoo! RSS ニュースが https ( SSL )化されたことに伴い、EasyWebSocket ライブラリも Beta ver 1.50 に一新しました。
スケッチも大幅変更しました。
解説は以下をご覧ください。
(2017/4/13)
/* 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(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov; //(129, 6, 15, 28)time.nist.gov NTP server
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 = "/pickup/rss.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/pickup/rss.xml にあるとすると、
HOST = news.yahoo.co.jp
target = /pickup/rss.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.コンパイル実行させる。

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

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

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

ws_Yahoo_message03

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

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

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

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

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

ではまた・・・。

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

スポンサーリンク

mgo-tec電子工作 関連コンテンツ ( 広告含む )
Amazon.co.jp 当ブログのおすすめ



投稿者:

mgo-tec

Arduino , ESP32 ( ESP-WROOM-32 ) , ESP8266 ( ESP-WROOM-02 )等を使って、主にスマホと連携した電子工作やプログラミング記事を書いてます。ライブラリも作ったりしてます。趣味、独学でやってますので、動作保証はしません。 電子回路やプログラミングの専門家ではありません。 畑違いの仕事をしてます。 でも、少しだけ電気の知識が必要な仕事なので、電気工事士や工事担任者等の資格は持ってます。

コメントを残す

メールアドレスが公開されることはありません。

*画像の文字を入力してください。(スパム防止の為)