ESP-WROOM-02 と 極小OLED (有機EL) で Yahooニュース電光掲示板とNTP時計を表示させてみた。もうIoTですね

ESP8266 ( ESP-WROOM-02 )

6.スケッチを入力する

いよいよスケッチです。こんな感じになります。

2017/4月以降、Yahoo! RSSニュースが https ( SSL )化されました。
そこで、EasyWebSocketライブラリ内で WiFiClientSecureライブラリを使うことになったために、ESP8266 のSRAMメモリが足りなくなりました。
それに伴いスケッチ内のいくつかをコメントアウトしています。
以下の解説も参照してください。
なお、このコードは無保証です。
(2017/12/19)

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

/* WebSocketリアルタイムコントロール 有機EL(OLED) 電光掲示板サンプルスケッチ
 * Web記事取得、時計機能追加
 * ESP-WROOM-02(ESP8266) および 有機EL SSD1306専用
 * Dual licensed under The MIT License or LGPL v2.1
 * Copyright (c) 2016 MGO-tec 
 * License reference URL --> https://opensource.org/licenses/mit-license.php
 */
#include <EasyWebSocket.h> //Beta ver 1.50
#include <Wire.h>
#include <TimeLib.h> //timeライブラリver1.4の場合
#include <WiFiUdp.h>
#include <UTF8toSJIS.h>
#include <MisakiFNT_read.h>
#include <I2C_Adafruit_8x8_LED_matrix.h>
#include <OLED_SSD1306.h>
 
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"; //半角フォントファイル名を定義
 
const uint8_t OLED_Adress = 0x3C; //OLED address 製品に記載の数値は7bitなので、8bitに変換して1bit右へずらした値(78>>3c)
 
EasyWebSocket ews;
UTF8toSJIS u8ts;
MisakiFNT_read MFR;
I2C_Adafruit_8x8_LED_matrix adaLED;
OLED_SSD1306 oled;
 
String html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7;
 
uint16_t PingSendTime = 30000;
 
String ret_str="";
String scl_txt="";
 
uint8_t scl_cnt = 0, scl_cnt2 = 0;
uint8_t fnt_cnt = 0, fnt_cnt2 = 0;
bool FntReadOK = true, FntReadOK2 = true;
 
uint8_t LedDotOut[8][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},{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[8][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},{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_cnv_2[8] = {0,0,0,0,0,0,0,0};
 
uint8_t LedDotOut2[8][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},{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_buf2[8][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},{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_buf2[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 tmp_buf_cnv2_2[8] = {0,0,0,0,0,0,0,0};
 
uint8_t sj_txt[400], sj_txt2[600];
uint16_t sj_cnt = 0, sj_cnt2 = 0;
uint16_t sj_length, sj_length2;
uint32_t SclTime, SclTime2;
uint16_t SclSpeed = 50, SclSpeed2 = 30;
boolean Scl_Stop = false;
uint8_t Direction = 0;
uint8_t brightness = 0;
int16_t Angle = 0;
 
boolean sjis_txt_in = false, sjis_txt_in2 = false;
boolean BW_reverse = false, BW_reverse2 = 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;
 
String time_str;
String ymd_str;
String dummy_ymd_str;
uint8_t time_sj_txt[25], ymd_sj_txt[25];
uint16_t time_sj_length, ymd_sj_length;
 
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, web_get2 = 0;
uint32_t Web_time = 0;
boolean first_get = false;
 
//-----OLED-----------------------------------------------
uint8_t OLED_tmp_buf_cnv2[8] = {0,0,0,0,0,0,0,0};
uint8_t OLED_DotOut[8][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},{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 OLED_DotOut2[8][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},{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 OLED_DotOut3[8][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},{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 OLED_Next_buf[8][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},{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_OLED_buf_cnv2[8] = {0,0,0,0,0,0,0,0};
uint8_t tmp_OLED_buf_cnv3[8] = {0,0,0,0,0,0,0,0};
 
//***********セットアップ***************************************************
void setup() 
{
  Wire.begin(); // I2C initialise the connection
  Wire.setClock(400000L); //クロックはMax 400kHz
 
  oled.setup_OLED_SSD1306(OLED_Adress);
   
  delay(300);
  //セットアップで全角を一旦表示させることが重要。半角だとなぜかSPIFFSファイル読み込みエラーになってしまうため。
  u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, "WAIT・・・・", sj_txt, &sj_length);
  for(uint8_t i=0; i<8; i++) MFR.Sjis_To_MisakiFNT_DotRead(Zen_Font_file, Half_Font_file, 0, 0, sj_txt[i*2], sj_txt[i*2+1], LedDotOut[i]);
  for(uint8_t i=0; i<8; i++) adaLED.LED_Dot_Rotation(90, LedDotOut[i], OLED_DotOut2[i]);
  oled.OLED_2X2_Display_Out_16x127(OLED_Adress, 0, 2, OLED_DotOut2[7], OLED_DotOut2[6], OLED_DotOut2[5], OLED_DotOut2[4], OLED_DotOut2[3], OLED_DotOut2[2], OLED_DotOut2[1], OLED_DotOut2[0]);
  for(uint8_t i=0; i<8; i++) {
    for(uint8_t j=0; j<8; j++){
      OLED_DotOut2[i][j] = 0; LedDotOut[i][j] = 0; //一旦初期化しておく
    }
  }
   
  html_str1 = "<body style='background:#ddddff; color:#000;'>\r\n";
  html_str1 += ews.EWS_TextBox_Send("txt1", "1行目任意テキスト送信、半角カタカナもOK!","送信");
  html_str1 += "<br>\r\n";
  html_str1 += ews.EWS_TextBox_Send("Txt2", "2行目任意text送信、半角カタカナもOK!","送信");
  html_str1 += "<br><br>\r\n";
  //html_str2 = "Scrolle Speed 1\r\n";
  //html_str2 += ews.EWS_Canvas_Slider_T("Speed",200,40,"#777777","#77aaff");
  //html_str2 += "<br><br>\r\n";
  //html_str2 += "Scrolle Speed 2\r\n";
  //html_str2 += ews.EWS_Canvas_Slider_T("speed2",200,40,"#777777","#77aaff");
  html_str3 = "<br><br>\r\n";
  html_str3 += ews.EWS_On_Momentary_Button("_SclStop", "Scrole Stop", 100,25,15,"#ffffff","#777777");
  html_str3 += "<br><br>1行目白黒反転 \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>2行目白黒反転 \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_str5 = "<div style='width:330px; height:140px; text-align:center; padding:10px; background:#ccccff; border:#0000ff solid 1px;'>\r\n";
  html_str5 += ews.EWS_On_Momentary_Button("webON", "Web GET ON", 110,25,15,"#000000","#ff77ff");
  html_str5 += "※ここボタンを押すとWebSocketは切断されます。再操作する場合はWS-Reconnectボタンを押してください<br>1行目 \r\n";
  html_str5 += ews.EWS_On_Momentary_Button("News", "Top NEWS", 100,25,15,"#000000","#ffffff");
  html_str5 += "<span>  </span>\r\n";
  html_str5 += ews.EWS_On_Momentary_Button("Cnews", "PC NEWS", 110,25,15,"#000000","#ffffff");
  html_str5 += "<br><br>2行目 \r\n";
  html_str5 += ews.EWS_On_Momentary_Button("ENews", "芸能News", 110,25,15,"#000000","#ffffff");
  html_str5 += "<span>  </span>\r\n";
  html_str5 += ews.EWS_On_Momentary_Button("Weather", "Weather", 110,25,15,"#000000","#ffffff");
  html_str5 += "</div><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 = "";
   
  ews.AP_Connect(ssid, password);
 
  //NTPサーバーでタイムを取得
  Udp.begin(localPort);
  setSyncProvider(getNtpTime);
  delay(1000);
   
  SclTime = millis();
  LastTime = millis();
  prevDisplay = now();
}
//**************メインループ*************************************************
void loop() {
  //WebSocket ハンドシェイク関数
  ews.EWS_HandShake(html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7);
   
  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' && 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 's':
              SclSpeed2 = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
              SclSpeed2 = 200-SclSpeed2;
              Scl_Stop = false;
              break;
            case '_':
              Scl_Stop = true;
              break;
            case '(':
              BW_reverse = false;
              break;
            case ')':
              BW_reverse = true;
              break;
            case '[':
              BW_reverse2 = false;
              break;
            case ']':
              BW_reverse2 = true;
              break;
            case 'w':
              web_get = 1; web_get2 = 1; first_get = true;
              break;
            case 'N':
              web_get = 1;
              sjis_txt_in = false; first_get = true;
              break;
            case 'C':
              web_get = 2;
              sjis_txt_in = false; first_get = true;
              break;
            case 'E':
              web_get2 = 1;
              sjis_txt_in2 = false; first_get = true;
              break;
            case 'W':
              web_get2 = 2;
              sjis_txt_in2 = false; first_get = true;
              break;
          }
        }else if(ret_str[0] == 't' || ret_str[0] == 'T' ){
          scl_txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1);
          scl_txt += String(" ") + String("\0");
          switch(ret_str[0]){
            case 't':
              u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, scl_txt, sj_txt, &sj_length);
              fnt_cnt = 0; scl_cnt = 0; sj_cnt = 0; web_get = 0;
              FntReadOK = true; sjis_txt_in = true; Scl_Stop == false;
              FntReadOK2 = false; sjis_txt_in2 = false;
              break;
            case 'T':
              u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, scl_txt, sj_txt2, &sj_length2);
              fnt_cnt2 = 0; scl_cnt2 = 0; sj_cnt2 = 0; web_get = 0;
              FntReadOK2 = true; sjis_txt_in2 = true; Scl_Stop == false;
              FntReadOK = false; sjis_txt_in = false;
              break;
          } 
          Serial.println();
          Serial.print(scl_txt); //Arduino IDE 1.8.2以上の場合
          //for (int iiii=0; iiii<sj_length; iiii++) Serial.write(sj_txt[iiii]); //Arduino IDE 1.8.1以下の場合
          Serial.println();
        }
        ret_str = "";
      }else{
        ret_str = "";
      }
    }
     
    if(sjis_txt_in == true || sjis_txt_in2 == true){ //OLED1行目電光掲示板スクロール
      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_cnv_2);
        }
 
        if(Scl_Stop == false){
          for(i=0; i<8; i++) adaLED.LED_Dot_Rotation(90, LedDotOut[i], OLED_DotOut2[i]);
          oled.OLED_2X2_Display_Out_16x127(OLED_Adress, 0, 6, OLED_DotOut2[0], OLED_DotOut2[1], OLED_DotOut2[2], OLED_DotOut2[3], OLED_DotOut2[4], OLED_DotOut2[5], OLED_DotOut2[6], OLED_DotOut2[7]);
          adaLED.Scroller_Dot_Replace( Direction, Next_buf[0], LedDotOut[0], tmp_buf_cnv_2);
          for(i=0; i<7; i++) adaLED.Scroller_Dot_Replace( Direction, Next_buf[i+1], LedDotOut[i+1], Next_buf[i]);
          scl_cnt++;
        }
         
        if(scl_cnt == 8){
          scl_cnt = 0;
          FntReadOK = true;
        }
        SclTime = millis();
      }
 
      if(millis() - SclTime2 > SclSpeed2){ //OLED2行目電光掲示板スクロール
        if(sj_cnt2 >= sj_length2){
          sj_cnt2 = 0;
        }
        if(FntReadOK2==true){
          uint8_t cp2 = MFR.Sjis_To_MisakiFNT_DotRead(Zen_Font_file, Half_Font_file, Direction, Angle, sj_txt2[sj_cnt2], sj_txt2[sj_cnt2+1], tmp_buf_cnv2);
          FntReadOK2 = false;
          sj_cnt2 = sj_cnt2 + cp2;
          adaLED.LED_Black_White_Reversal(BW_reverse2, tmp_buf_cnv2, tmp_buf_cnv2);
        }
 
        if(Scl_Stop == false){
          for(i=0; i<8; i++) adaLED.LED_Dot_Rotation(90, LedDotOut2[i], OLED_DotOut3[i]);
          oled.OLED_2X2_Display_Out_16x127(OLED_Adress, 0, 4, OLED_DotOut3[0], OLED_DotOut3[1], OLED_DotOut3[2], OLED_DotOut3[3], OLED_DotOut3[4], OLED_DotOut3[5], OLED_DotOut3[6], OLED_DotOut3[7]);
          adaLED.Scroller_Dot_Replace( Direction, Next_buf2[0], LedDotOut2[0], tmp_buf_cnv2);
          for(i=0; i<7; i++) adaLED.Scroller_Dot_Replace( Direction, Next_buf2[i+1], LedDotOut2[i+1], Next_buf2[i]);
          scl_cnt2++;
        }
         
        if(scl_cnt2 == 8){
          scl_cnt2 = 0;
          FntReadOK2 = true;
        }
        SclTime2 = millis();
      }
    }
  }else if(ret_str == "_close"){
    ret_str = "";
    scl_txt = "";
  }
 
  if(now() != prevDisplay){ //ここから時刻表示設定
    char month_chr[3], day_chr[3], h_chr[3], m_chr[3], s_chr[3];
    sprintf(h_chr, "%2d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
    sprintf(m_chr, "%02d", minute());
    sprintf(s_chr, "%02d", second());
    sprintf(month_chr, "%2d", month());
    sprintf(day_chr, "%02d", day());
 
    time_str = String(h_chr) + ':' + String(m_chr) + ':' + String(s_chr);
    ymd_str = String(year()) + '/' + String(month_chr) + '/' + String(day_chr) + "(" + String(TSD[weekday()-1].week_jp) + ")";
     
    uint8_t time_dot[4][8];    
    u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, time_str, time_sj_txt, &time_sj_length);
    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];
    uint8_t OLED_time_dot[4][8];
    for(i=0; i<4; i++) adaLED.LED_Dot_Rotation(Angle, time_dot[i], dummy_time_dot[i]);
    for(i=0; i<4; i++) adaLED.LED_Dot_Rotation(90, time_dot[i], OLED_time_dot[i]);
 
    uint8_t OLED_ymd_dot[8][8];
    if(dummy_ymd_str != ymd_str){
      u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, ymd_str, ymd_sj_txt, &ymd_sj_length);
      uint8_t ymd_dot[8][8];
      if(Direction != 0) Direction = 0;
      for(i=0; i<8; i++) MFR.Sjis_To_MisakiFNT_DotRead(Zen_Font_file, Half_Font_file, Direction, 0, ymd_sj_txt[i*2], ymd_sj_txt[i*2+1], ymd_dot[i]);
      uint8_t dummy_ymd_dot[8][8];
      for(i=0; i<8; i++) adaLED.LED_Dot_Rotation(Angle, ymd_dot[i], dummy_ymd_dot[i]);
      for(i=0; i<8; i++) adaLED.LED_Dot_Rotation(90, ymd_dot[i], OLED_ymd_dot[i]);
    }
     
    if(Angle == 0 || Angle == 90){
      oled.OLED_2X2_Display_Out_16x64(OLED_Adress, 30, 0, OLED_time_dot[3], OLED_time_dot[2], OLED_time_dot[1], OLED_time_dot[0]);
      if(dummy_ymd_str != ymd_str){
        oled.OLED_2X2_Display_Out_16x127(OLED_Adress, 0, 2, OLED_ymd_dot[7], OLED_ymd_dot[6], OLED_ymd_dot[5], OLED_ymd_dot[4], OLED_ymd_dot[3], OLED_ymd_dot[2], OLED_ymd_dot[1], OLED_ymd_dot[0]);
        dummy_ymd_str = ymd_str;
      }
    }else{
      oled.OLED_2X2_Display_Out_16x64(OLED_Adress, 30, 0, OLED_time_dot[0], OLED_time_dot[1], OLED_time_dot[2], OLED_time_dot[3]);
    }
    prevDisplay = now();
  }
 
  if(web_get == 0){
    if(millis()-LastTime >= 300000L){//5分毎に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_get2 > 0){ //Web記事取得
    if(first_get == true || millis()-Web_time > 600000L){ //Web記事を10分毎に取得
      char* news1_1_host = "news.yahoo.co.jp";
      String news1_1_target_ip = "/rss/topics/top-picks.xml"; // トップニュースタイトルページ
      char* news1_2_host = "news.yahoo.co.jp";
      String news1_2_target_ip = "/rss/topics/it.xml"; //コンピューター系ニュースページ
      String news_str = "";
      char Web_h[3], Web_m[3];
      sprintf(Web_h, "%02d", hour());//ゼロを空白で埋める場合は%2dとする
      sprintf(Web_m, "%02d", minute());
      news_str = String("◆") + String(Web_h) + ":" + String(Web_m) + " ";
      switch(web_get){
        case 1:
          news_str += ews.EWS_https_Web_Get(news1_1_host, news1_1_target_ip, '\n', "</rss>", "<title>", "</title>", "◆");
          break;
        case 2:
          news_str += ews.EWS_https_Web_Get(news1_2_host, news1_2_target_ip, '\n', "</rss>", "<title>", "</title>", "◆");
          break;
      }
      news_str.replace("&amp;","&");
      Serial.println();
      u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, news_str, sj_txt, &sj_length);
      
      Serial.println(news_str); //Arduino IDE 1.8.2以上の場合
      /* //Arduino IDE 1.8.1以下の場合
      for(int iy=0; iy<sj_length; iy++){
        Serial.write(sj_txt[iy]);
      }
      Serial.println();
      */
      
      news_str ="";
      fnt_cnt = 0; scl_cnt = 0; sj_cnt = 0;
      sjis_txt_in = true;  FntReadOK = true;
      //2つめの記事取得
      char* news2_1_host = "news.yahoo.co.jp";
      String news2_1_target_ip = "/rss/topics/entertainment.xml"; // 芸能トップニュースタイトルページ
      char* news2_2_host = "rss.weather.yahoo.co.jp";
      String news2_2_target_ip = "/rss/days/4620.xml"; //小田原の天気予報ページ
      String news2_str = "";
 
      news2_str = String("◆") + String(Web_h) + ":" + String(Web_m) + " ";
      switch(web_get2){
        case 1:
          news2_str += ews.EWS_https_Web_Get(news2_1_host, news2_1_target_ip, '\n', "</rss>", "<title>", "</title>", "◆");
          break;
        case 2:
          news2_str += ews.EWS_Web_Get(news2_2_host, news2_2_target_ip, '>', "</rss>", "【", " - ", "【");
          break;
      }
      news2_str.replace("&amp;","&");
      Serial.println();Serial.print("WebGet2=");
      u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, news2_str, sj_txt2, &sj_length2);
      
      Serial.println(news2_str); //Arduino IDE 1.8.2以上の場合
      /* //Arduino IDE 1.8.1以下の場合
      for(int iy=0; iy<sj_length2; iy++){
        Serial.write(sj_txt2[iy]);
      }
      Serial.println();
      */
      
      news2_str ="";
      fnt_cnt2 = 0; scl_cnt2 = 0; sj_cnt2 = 0;
      sjis_txt_in2 = true;  FntReadOK2 = true;
      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();
}

これもMITライセンスとしました。
配布、商用、改変等は自由。ただし無保証。
ライセンス表記はしてくださいというものです。
ネット社会では自由に配布するためにはライセンス表記しないと逆に自由配布できないそうなので、そうしてます。
ただ、つい最近知ったのですが、Arduino core for ESP8266 wifi chip ライブラリを使っていると、それがGPL v2 ライセンスなので、必然とこれもGPL v2 ライセンスになってしまうらしいです。つまり、この場合はデュアルライセンス?になるらしいです。
まぁ、MIT ライセンスとGPLライセンスは親和性があるので、私としてはどちらでもいいのです。

では、以下はプログラムの解説です。

Yahoo! RSSニュースが https ( SSL )化されたことに伴い、以下の項目を変更しました。

●55行目:
SSL通信が多くのメモリを消費してしまうため、配列容量を sj_txtを400バイトとし、sj_txt2を600バイトとしました。
sj_txt2は天気予報の最大バイト数に合わせてあります。

●131-135行:
ブラウザに表示させる、Canvasスライダーは、多くの文字列を必要として、メモリを大幅に消費するので、コメントアウトしました。
(本来は、ブラウザに表示させる HTMLテキストは、SPIFFSファイルに入れ込んだ方が良いです。
このソースコードはかなり昔に書いたコードで、無保証とさせていただいているので、大幅な修正は見送りました。
申し訳ございません。
16x16ドットの東雲フォントを使った記事に進んでいただくか、よりメモリの大きな ESP-WROOM-32 ( ESP32 )の導入をご検討ください。
(2017/12/19)

●256-257行:
Arduino IDE 1.8.2 の場合はシリアルモニターに UTF-8 コードの日本語漢字を表示することができるようになりました。
よって、Shift_JISコード出力は不要になりました。
Arduino IDE 1.8.1以下の場合は今まで通りですので、コメントアウトを外してください。

●394行 ●397行 ●425行
EasyWebSocket Beta 1.50 から登場した、https ( SSL )ページを取得する関数です。
使い方は http と同じです。
ただ、Yahoo! RSS の天気予報は今まで通り、まだ http 通信です。

●404-409行、435-440行
先ほど述べた、Arduino IDE 1.8.2 シリアルモニター出力対応です。

●17-18行:
ここはご自分のWi-FiルーターのSSIDとパスワードに書き換えてください。

●20-22行:
UTF8→Shift_JIS 変換テーブルと美咲フォントの全角、半角ファイル名を指定してます。
必ず前にスラッシュをつけてください。

●24行:
有機EL(OLED) SSD1306のI2C通信のアドレスを指定します。
このOLED は裏面に0x78 と記載されています。
これは7bit値ですので、8bitにして、右へビットをずらすと0x3Cとなります。

●26-30行:
自作ライブラリのクラス名を指定してます。

●34行:
EasyWebSocketライブラリで、Pingをブラウザに送信するタイミングを指定してます。
ここでは30秒毎にしてます。
これが短いと通信トラフィックに影響が出ますし、長すぎるとWebSocket通信が勝手に切断されてしまいます。それを防ぐために適度な値にしてます。

●69-76行:
NTP通信のサンプルスケッチをそのまま流用しました。

●87-90行:
曜日の漢字や英字を配列にするために、ここでは構造体で定義してます。

●109-110行:
wireライブラリのクラスで、I2C通信のセットアップです。とりあえず、最大の400kHzでも動くようです。SSD1306のI2Cクロックはデータシートを見ても分かりませんでした。

●112行:
今回から登場した自作ライブラリのクラスです。
I2C通信のSSD1306 の初期化です。

oled.setup_OLED_SSD1306(OLEDのI2Cアドレス);

●116-124行:
セットアップ関数内で “Wait...” などとOLEDに一旦全角で表示させます。
そうしないとなぜか半角フォントファイルをSPIFFSファイルシステムで読み込んでくれないんですね。 これは謎です。
119行目は今回から登場したOLED_SSD1306ライブラリのクラスです。
8×8ドットをそのまま表示させると余りにも小さすぎて読み取り辛いので、2倍角にドットを拡張してます。つまり疑似16×16ドットです。

oled.OLED_2X2_Display_Out_16x127(I2Cアドレス,水平開始位置, 垂直開始位置, 8×8ドット[7], 8×8ドット[6], 8×8ドット[5], 8×8ドット[4], 8×8ドット[3], 8×8ドット[2], 8×8ドット[1], 8×8ドット[0]);

水平開始位置は0~127ドットです。右下が原点(0,0)ですので要注意です。
垂直開始位置は0~7ブロックです。つまり、SSD1306のデータシートによると、縦は8ドットで1ページという区分けになってます。
このOLEDは8×8ドットを8つ横に並べれば1列一杯の文字列を表示できますので、8つの8×8ドットをしていすることにしています。
SSD1306の詳細については過去の記事のこちらのページ  こちらのページを参照してください。

●126-166行:
ブラウザに送信するHTMLタグの<body>要素の文字列をString変数へ代入します。
自作ライブラリのEasyWebSocketの関数をふんだんに使ってます。
EWS_Canvas_Slider_T は多量のタグを排出するので、一つのString変数に2つまでしか代入できませんので注意です。7つのString変数に分散してます。

●168行:
ルーターと接続する関数です。

●171-172行:
NTPサーバーと同期します。

●182行:
自作ライブラリのEasyWebSocket関数で、スマホのブラウザとWebSocket接続のハンドシェイクを行います。
メインloop関数内で常にコネクションをチェックしていて、再接続し易いようにしてます。

●186行:
EasyWebSocket関数でブラウザからの送信されてくる文字列を受信しています。

●188-237行:
ブラウザから送信されてくる文字列配列の4番目の文字をキーワードとして、ESP-WROOM-02の行う処理を決めています。
スクロール速度、白黒反転、記事取得などの処理を決定します。
194-195行ではスクロール速度はHTMLのCanvas要素スライダーで200px幅で数値を出しているので、こんな感じになります。

●238-259行:
ブラウザのテキストボックスから送信されてくる文字列をここで抽出しています。
243行では自作ライブラリのUTF8→Shift_JIS変換を使って文字コード変換してます。

●266-319行:
ここでShift_JIS文字列から美咲フォントに変換して、文字をスクロールしています。
OLEDの1行目と2行目のスクロールは別々に計算しなくてはなりません。
267行でスクロール速度をコントロールしています。
272行で2バイトのShift_JISコードから美咲フォントを呼び出し、8×8ドットを呼び出しています。
280行ではドットを90度回転しています。つまり、このOLEDはビット列を縦方向に表示していくので、こうなります。
281行で2倍角に変換してOLEDにI2C通信出力します。
282-283行でドットを1bit左へずらしていきます。これを8回繰り返したら、新たに文字を読み込んでいけばスクロールするということになります。

●325-366行:
ここではNTPサーバーから取得した時刻をOLEDに出力しています。
331-335行がミソで、時刻数値を桁数を合わせて文字列に格納します。

●368-379行:
NTPサーバーと同期するタイミングを決めています。

●381-449行:
ブラウザのニュース記事取得ボタンが押されたら、ここでYahooのRSSサイトへニュース記事をGETリクエストしていきます。

※382行のWEBサイトへのリクエストのタイミングは十分気を付けてください! あまりに早いタイミングでリクエストを送るとDOS攻撃と判断されてアクセス拒否されたり、ひどい場合にはサーバーをダウンしかねません。
自己責任となりますので予めご了承ください。

2行別々のニュースを取得するので、2倍のプログラムが必要になるのは仕方ないですね。
398行のところで、EasyWebSocketライブラリ関数を使用してます。ニュースのホストサーバーアドレスと実際のターゲットページのアドレスを指定して、改行(’¥n’)区切りでテキストを取得して、<title>タグを目安として記事を取得しています。RSSサイトはxml形式でできているので、そのソースコードを取得するとブラウザで見ているものとは違うコードになるので注意が必要です。この部分については前回の記事を参照してください。
400行では、半角の”&”という文字はxml形式のソースコードは”&amp;”となりますので、その文字が来たら全角の”&”に置き換えています。

●452-496行:
Arduino core for ESP8266 WiFi chip のNTPサンプルスケッチをそのまま流用しています。定期的にこの関数を呼んで時刻を補正します。

以上、ソースコードの説明でした。
この解説を記述するのが一番たいへんですね。
これを省いてブログをアップできたらいいのになぁ・・・。

7.コンパイル書込み、実行させる。

では、ルーターの電源を入れて、Arduino IDEのシリアルモニターを起動させた後、コンパイル実行させてみます。

コンパイルに成功するとIDE画面ではこう表示されます。これはIDE1.6.8です。

シリアルモニターではこう表示されます。


上にある動画のように、最初はWAIT・・・と表示されて、ルーターのSSIDとパスワードを通過して、ESP-WROOM-02のローカルIPアドレスと接続されれば、時計表示だけ表示されると思います。

では、スマホのブラウザを起動してください。推奨は最新版Android で最新版Google Chrome です。WebSocket通信が安定して利用できます。
起動したら、URL入力欄にESP-WROOM-02 のローカルIPアドレスを入力してください。

HTMLタグを全て送信し終わるまで10数秒前後お待ちください。
するとシリアルモニターでこう表示されれば、WebSocket通信成功です。

そうしたら、スマホのブラウザ側ではこう表示されていると思います。

Yahoo RSS が SSL化されたことに伴い、メモリ削減のため、下図のスクロールスピード調整は表示されません。
(2017/12/19)

そうしたら、ピンク色のWeb GET ON ボタンを押してください。
それから10数秒後にOLED の1行目にYahoo RSS のトップニュースの記事タイトルがスクロールします。
2行目はYahoo RSS のエンターテインメント(芸能)ニュースのトップ記事のタイトルがスクロールします。
ただ、注意してほしいのは、ニュース記事を取得するときにはWebSocket通信を一旦強制切断しないと記事取得できません。ここでは、GETボタンを押すとライブラリ内で強制切断するようにしてます。ニュース記事取得はHTTPリクエストしなければいけないので当然といえば当然です。
ですから、その後ボタンやスライダーで操作する場合は”WS-Reconnect”ボタンを押してWebSocketを再接続しなければいけません。ご注意ください。

スクロール速度もいろいろ変えてみてください。こんな小さいディスプレイで文字が流れるとやっぱりイイですね・・・。
ただ、8×8ドットの倍角ですから、複雑な文字は判読が難しいのは止むをえません。いつか16×16ドットに挑戦してみたいとは思ってます。

白いボタンで記事を変えることもできます。
プログラムを変えれば他の記事を取得することもできると思いますが、ページのソースコードを自分で解析して、キーワードを設定しなければなりません。
このライブラリでは他のサイトの記事では可能かどうかはまだ分かりません。
因みに、Yahoo RSS サイトの記事は個人レベルで使用するには問題ないのですが、商用利用は不可らしいので注意してください。

また、記事は10分毎に取得していますので、その時にスクロールが停止してしまいます。
これを防ぐためには別のArduino などを使って、OELD表示機能と記事取得機能と分けて使うしかないでしょうね。私個人的にはこれで十分だと思っていますが・・・。

以上です。

みなさん、うまく動作しましたでしょうか?

この記事を書くのもホントに大変です。なんとかせねば・・・。

ではまた・・・。

最新記事では、ESP32 を使ってもっと良い電光掲示板ができました。
以下の記事をごらんください。

ESP32 と 有機EL SSD1331 で Yahoo News 、 天気予報 、 NTP 時計 Wi-FI ガジェットを作ってみた

コメント

  1. Kasu Kou より:

    こんなに簡単にwebを利用したデバイスが作れると思ってなかった。
    感動した!!

    • mgo-tec mgo-tec より:

      Kasu Kouさん
      コメントありがとうございます。
      こういう声をいただくと、とても励みになります。
      記事をお読みいただいただけでもそれだけで感謝感謝です。
      ありがとうございました!!!

  2. iectec より:

    今後の工作に活用させていただきます。

    これほどの良質なソースコードとノウハウを無償で提供しているとは驚きです。

    ありがとうございました

    • mgo-tec mgo-tec より:

      iectecさん

      当ブログをご覧いただき感謝いたします。
      まだまだ勉強中なので「良質」なんてとんでもないことでございます。
      まだまだ至らないところが有るかも知れませんが、今後とも当ブログをよろしくお願いいたします。

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