7.スケッチの入力
Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。 その対策方法は以下のページにあります。 JR 風 Yahoo! ニュース 電光掲示板 の https ( SSL )対策 合わせてご参照ください。 その場合、SD_EasyWebSocket は Beta ver 1.50 をインストールする必要があり、スケッチも大幅変更する必要があります。 (2017/4/11時点)
では、Arduino IDE で以下のスケッチを入力してください。
ライセンスは MIT としました。
Web上でソースコードを公開する場合にはライセンスを明記しないと自由に配布できないそうなので、Arduino core for ESP8266 ライブラリのLGPL v2 と親和性のあるMITにしました。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* ESPr Developer or ESP-WROOM-02 or ESP8266
* Adafruit OLED SSD1351 Breakout Board – 16-bit Color 1.5″ w/microSD holder
* This Sketch is The MIT License (MIT)
* Copyright (c) 2016 mgo-tec
* License reference URL --> https://opensource.org/licenses/mit-license.php
* JPEGデコーダは「ないん」さん作成。http://yushakobo.jp/pluis9/2014/06/jpegdecoder/
*/
#include <OLED_SSD1351bv2.h> //beta ver 2.0
#include <SD_UTF8toSJIS.h> //beta ver 1.0.1
#include <SD_ShinonomeFONTread.h> //beta ver 1.0.1
#include <SD_EasyWebSocket.h> //beta ver1.47
#include <SD_MisakiFNT_read.h> //beta ver 1.1
#include <JPEGDecoder.h> //ないんさん作成
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SD.h>
#include <SPI.h>
#include <Hash.h>
#include <TimeLib.h> //timeライブラリver1.4の場合
SD_UTF8toSJIS u8ts;
SD_ShinonomeFONTread SFR;
OLED_SSD1351bv2 oled;
SD_MisakiFNT_read MFR;
SD_EasyWebSocket ews;
const uint8_t sclk = 14; //SPI clock
const uint8_t mosi =13; //Master Output Slave Input ESP8266=Master,BME280=slave
const uint8_t miso =12; //Master Input Slave Output
const uint8_t cs_SD = 15; //SD card CS
const uint8_t cs_OLED = 0;
const uint8_t DCpin = 5; //OLED DC(Data/Command)
const uint8_t RSTpin = 4; //OLED Reset
const char* ssid = "xxxx"; //ご自分のルーターのSSIDを入力
const char* password = "xxxx"; //ご自分のルーターのパスワードを入力
const char* UTF8SJIS_file = "font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名
const char* Shino_Zen_Font_file = "font/shnmk16.bdf"; //全角東雲フォントファイル名を定義
const char* Shino_Half_Font_file = "font/shnm8x16.bdf"; //半角東雲フォントファイル名を定義
const char* Misaki_Zen_Font_file = "font/MSKG13KU.FNT"; //全角美咲フォントファイル名を定義
const char* Misaki_Half_Font_file = "font/mgotec48.FNT"; //半角美咲フォントファイル名を定義
const char* HTM_head_file1 = "EWS/LIPhead1.txt"; //HTMLヘッダファイル1
const char* HTM_head_file2 = "EWS/LIPhead2.txt"; //HTMLヘッダファイル2
const char* HTML_body_file = "EWS/LIPhtml.htm"; //HTML body要素ファイル
const char* dummy_file = "EWS/dummy.txt"; //HTMLファイル連結のためのダミーファイル
char PicFile[14] = "/PICtmp.jpg"; //スマホから送信された画像ファイルをSDカードのルートに保存するファイル名を指定。filename Max 8文字, 拡張子3文字
enum { MaxTxt = 800 , DispChar = 16}; //MaxTxt = スクロールする文字列の最大数(半角数)
//DispChar = ディスプレイに表示できる半角文字数
uint8_t Sino_font_buf[MaxTxt][16]; //東雲(しののめ)フォントバッファ
uint8_t SnnmDotOut[DispChar][16];
uint8_t Next_buf[DispChar][16];
uint8_t dummy_Sino_font_buf[16];
File UtoS; //ファイルハンドル名定義
File SinoZ;
File SinoH;
File MisakiZ;
File MisakiH;
int PingSendTime = 30000; //スマホとのWebSocket通信接続確認の為のPing送信間隔設定(30秒)
IPAddress LIP; //ESP-WROOM-02(ESP8266) ローカルIPアドレス自動取得用
String html_str1 = "";
String html_str2 = "";
String html_str3 = "";
//-------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 LastNTP_Get = 0;
//-----時計表示引数初期化------------------------------
boolean clock_Disp_Set = true, clock_Disp_OUT = true;
uint8_t clock_Select = 0;
uint32_t sec_colon_time = 0;
uint8_t h_t = 25, min_t = 61; //初回時刻表示させるために、時刻の有り得ない数値で予め初期化しておく
//-----Yahoo記事取得引数初期化-------------------------
uint32_t WebGetTime = 0;
boolean Web_first_get = true, Text_Box_On = false;
uint8_t Yahoo_get = 1;
//-----電光掲示板文字列およびスクロール引数初期化-------
uint8_t sj_txt[MaxTxt]; //Shift_JISコード収納配列
uint16_t sj_cnt = 0; //Shift_JISコード配列カウント数
uint16_t sj_length; //Shift_JISコード長
uint32_t SCLtime; //文字列スクロールスピードでmillisを取得して代入する引数
uint16_t Scrolle_speed = 8; //文字列スクロールインターバル時間(8ms)
uint8_t scl_cnt = 0; //フォントのスクロールカウント数
//------JR風電光掲示板引数初期化-----------------------
uint8_t Red, Green, Blue; //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
uint8_t jrbRed = 7, jrbGreen = 14, jrbBlue = 7; //JR風電光掲示板のベース色
uint8_t BG_red = 0, BG_green = 0, BG_blue = 0; //OLED全体の背景色
uint8_t jrX0 = 0, jrY0 = 0; //JR風電光掲示板位置の左上座標(看板描画原点)
uint8_t picX0 = 16, picY0 = 74; //JPEG画像表示位置の左上座標(描画原点)
uint8_t msgRed = 0, msgGreen = 63, msgBlue = 0; //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
String blink_msg = "電車がまいります";
boolean blink_1th_in = true; //起動時に点滅文字列を表示するかどうか
boolean blink_on = true; //点滅文字列ON/OFF判定
//**************セットアップ************************************
void setup() {
Serial.begin(115200);
uint16_t i, j;
delay(1000);
ews.AP_Connect(ssid, password); //ルーター(アクセスポイント)へ接続
delay(1000);
LIP = WiFi.localIP(); //ESP8266のローカルIPアドレスを自動取得
Serial.println();
Serial.println(F("closing connection"));
delay(1000);
SPI.begin();
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2); //これは実はMODE3
oled.SSD1351bv2_Init(sclk, mosi, cs_OLED, DCpin, RSTpin); //OLED初期化
jpegDraw_65k_color(PicFile); //SDカードに保存された画像をOLEDに出力する
SD.begin(cs_SD,40000000);
Serial.println("card initialized.");
UtoS = SD.open(UTF8SJIS_file, FILE_READ);
if (UtoS == NULL) {
Serial.print("**Utf8Sjis.tbl File not found");
return;
}else{
Serial.println("Utf8Sjis.tbl File OK!");
}
SinoH = SD.open(Shino_Half_Font_file, FILE_READ);
if (SinoH == NULL) {
Serial.print("shnm8x16.bdf File not found");
return;
}else{
Serial.println("shnm8x16.bdf File OK!");
}
SinoZ = SD.open(Shino_Zen_Font_file, FILE_READ);
if (SinoZ == NULL) {
Serial.print("shnmk16.bdf File not found");
return;
}else{
Serial.println("shnmk16.bdf File OK!");
}
MisakiZ = SD.open(Misaki_Zen_Font_file, FILE_READ);
if (MisakiZ == NULL) {
Serial.print("MSKG13KU.FNT File not found");
return;
}else{
Serial.println("MSKG13KU.FNT File OK!");
}
MisakiH = SD.open(Misaki_Half_Font_file, FILE_READ);
if (MisakiH == NULL) {
Serial.print("mgotec48 File not found");
return;
}else{
Serial.println("mgotec48 File OK!");
}
uint8_t MSK_font_buf[32][8];
//-------------JR風看板設定-------------------------------
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_RectFill(jrX0, jrY0, jrX0 + 127, jrY0 + 73, jrbRed, jrbGreen, jrbBlue); //看板ベース
Red = 0, Green = 0, Blue = 0;
oled.SSD1351bv2_RectFill(jrX0, jrY0+34, jrX0 + 127, jrY0 + 70, Red, Green, Blue); //電光掲示板部
Red = 0, Green = 63, Blue = 0;
oled.SSD1351bv2_RectFill(jrX0+50, jrY0+5, jrX0+60, jrY0+33, Red, Green, Blue); //JR路線色
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "山手線", sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
Red = 31, Green = 63, Blue = 31;
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, 6, Sino_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "渋谷方面", sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
Red = 31, Green = 63, Blue = 31;
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, 8, Sino_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "YamanoteLine", sj_txt, &sj_length); //最大半角12文字
uint16_t Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, Misaki_Length, MSK_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, " for Shibuya", sj_txt, &sj_length); //最大半角16文字
Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, Misaki_Length, MSK_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "--:--", sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
Red = 0, Green = 63, Blue = 0;
BG_red = 0, BG_green = 0, BG_blue = 0;
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+32, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "普通", sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
Red = 31, Green = 30, Blue = 0;
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "渋谷", sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+72, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "方 ", sj_txt, &sj_length);
Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+104, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "面 ", sj_txt, &sj_length);
Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+104, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "10", sj_txt, &sj_length);
Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
Red = 0, Green = 63, Blue = 0;
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+112, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, "両", sj_txt, &sj_length);
Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+116, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
//-------------------------------------------------------------
for(i=0; i<16; i++) { //初期化しておく
for(j=0; j<16; j++){
Sino_font_buf[i][j] = 0;
SnnmDotOut[i][j] = 0;
Next_buf[i][j] = 0;
}
}
//NTPサーバーから時刻を取得---------------------------
Udp.begin(localPort);
setSyncProvider(getNtpTime);
delay(1000);
scl_cnt = 0;
sj_cnt = 16;
SCLtime = millis();
prevDisplay = now();
LastNTP_Get = millis();
}
//********************メインループ**************************************
void loop() {
//WebSocket ローカルIPアドレスを自動取得して、ハンドシェイク(コネクション確立)
ews.EWS_Dev_AutoLIP_HandShake(cs_SD, HTM_head_file1, LIP, HTM_head_file2, HTML_body_file, html_str1, html_str2, html_str3, dummy_file);
int16_t i, j;
String ret_str; //スマホからWebSocket通信で送られてくるテキストデータを格納
ret_str = ews.EWS_ESP8266DataReceive_SD_write(PingSendTime, cs_SD, PicFile); //スマホからバイナリ形式ファイルが送られて来たらSDカードへ格納
if(ret_str == "_Binary"){ //スマホからのバイナリデータを受信した場合
Serial.println("WebSocket Binary Receive Complete!\r\n");
jpegDraw_65k_color(PicFile); //SDカードに保存されたJPEG画像をOLEDに出力する
Serial.println("JPEG display complete----------");
ret_str="";
}
if(ret_str != "_close"){ //スマホブラウザから受信した文字列を判別して動作を決める
if(ret_str != "\0"){
if(ret_str != "Ping"){
Serial.println(ret_str);
if(ret_str[0] != 't'){
switch(ret_str[4]){
case 'Y': //Yahoo記事GET
Web_first_get = true; Text_Box_On = false; Yahoo_get = 1;
break;
case 'b': //点滅文字列OFF
blink_on = false;
break;
case 'B': //点滅文字列ON
blink_on = true;
blink_1th_in = true;
break;
case 'M': //スクロール文字色選択
switch(ret_str[7]){
case 'G':
msgRed = 0, msgGreen = 63, msgBlue = 0; //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
break;
case 'O':
msgRed = 31, msgGreen = 30, msgBlue = 0;
break;
case 'W':
msgRed = 31, msgGreen = 63, msgBlue = 31;
break;
case 'B':
msgRed = 0, msgGreen = 0, msgBlue = 31;
break;
case 'R':
msgRed = 31, msgGreen = 0, msgBlue = 0;
break;
}
break;
case 'R': //電車路線色選択
switch(ret_str[9]){
case 'G':
Red = 0, Green = 63, Blue = 0;
break;
case 'O':
Red = 31, Green = 30, Blue = 0;
break;
case 'Y':
Red = 31, Green = 63, Blue = 0;
break;
case 'S':
Red = 0, Green = 40, Blue = 31;
break;
case 'B':
Red = 0, Green = 0, Blue = 31;
break;
case 'D':
Red = 0, Green = 30, Blue = 0;
break;
}
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_RectFill(jrX0+50, jrY0+5, jrX0+60, jrY0+33, Red, Green, Blue); //JR路線色
break;
case 'S': //スクロールスピード
uint16_t ws_data = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
Scrolle_speed = floor((200-ws_data)/(200/40)); //スマホスライダー値(0-200)で、値を(40-0)に変更したい場合
Serial.print("Scrolle_speed = "); Serial.println(Scrolle_speed);
break;
}
}else if(ret_str[0] == 't'){
uint8_t JR_Sino_SJtxt[32], JR_Msk_SJtxt[32];
uint16_t JR_Sino_SJlength, JR_Msk_SJlength, msk_fnt_length;
uint8_t JR_Sino_font_buf[16][16];
uint8_t JR_Msk_font_buf[16][8];
String txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1);
uint16_t t_len = txt.length();
for(i=0; i<16; i++){ //初期化しておく
for(j=0; j<16; j++){
JR_Sino_font_buf[i][j] = 0;
if(j<8) JR_Msk_font_buf[i][j] = 0;
}
}
switch(ret_str[1]){
case '7': //点滅メッセージ変換
blink_msg = txt;
break;
case '8': //スクロールメッセージ変換
if(t_len < 20){
for(i=0; i<(20-t_len); i++){ //文字数が少ない場合にスペースを入れる
txt += ' ';
}
}
txt = " " + txt + " ";
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
for(i=0; i<sj_length; i++) Serial.write(sj_txt[i]);
Serial.println();
for(i=0; i<16; i++) dummy_Sino_font_buf[i] = Sino_font_buf[0][i];
Text_Box_On = true;
scl_cnt = 0; sj_cnt = 0;
blink_1th_in = true;
break;
default: //JR風看板、固定文字変換
switch(ret_str[2]){
case 'S': //東雲フォント変換
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, JR_Sino_SJtxt, &JR_Sino_SJlength);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, JR_Sino_SJtxt, JR_Sino_SJlength, JR_Sino_font_buf);
for(i=0; i<JR_Sino_SJlength; i++) Serial.write(JR_Sino_SJtxt[i]);
Serial.println();
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
switch(ret_str[1]){
case '1': //路線文字変更
Red = 31, Green = 63, Blue = 31; //白色
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, JR_Sino_SJlength, JR_Sino_font_buf);
break;
case '3': //方向文字変更
Red = 31, Green = 63, Blue = 31; //白色
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, JR_Sino_SJlength, JR_Sino_font_buf);
break;
case '5': //電車種別変更
Red = 31, Green = 30, Blue = 0; //オレンジ
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, JR_Sino_SJlength, JR_Sino_font_buf);
break;
case '6': //行き先変更
Red = 31, Green = 30, Blue = 0; //オレンジ
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+72, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, JR_Sino_SJlength, JR_Sino_font_buf);
break;
}
break;
case 'M': //美咲フォント変換
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, JR_Msk_SJtxt, &JR_Msk_SJlength);
Serial.print("JR_Msk_SJlength=");Serial.println(JR_Msk_SJlength);
msk_fnt_length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, JR_Msk_SJtxt, JR_Msk_SJlength, JR_Msk_font_buf);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
switch(ret_str[1]){
case '2': //路線小文字変更
oled.SSD1351bv2_RectFill(jrX0, jrY0+21, jrX0+49, jrY0+33, jrbRed, jrbGreen, jrbBlue);
Red = 31, Green = 63, Blue = 31; //白色
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, msk_fnt_length, JR_Msk_font_buf);
break;
case '4': //方向小文字変更
oled.SSD1351bv2_RectFill(jrX0+61, jrY0+21, jrX0+127, jrY0+33, jrbRed, jrbGreen, jrbBlue);
Red = 31, Green = 63, Blue = 31; //白色
oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, msk_fnt_length, JR_Msk_font_buf);
break;
}
break;
}
break;
}
txt = "";
}
ret_str = "";
}
}
}else if(ret_str == "_close"){
ret_str = "";
}
if(Text_Box_On == false){ //Yahoo!ニュースGET
if(Web_first_get == true || millis()-WebGetTime > 600000UL){ //Web記事を10分毎に取得
String news_str;
String news1_1_target_ip;
char Web_h[3], Web_m[3];
sprintf(Web_h, "%02d", hour());//ゼロを空白で埋める場合は%2dとする
sprintf(Web_m, "%02d", minute());
news_str = "◆ " + String(Web_h) + ":" + String(Web_m) + " ";
switch(Yahoo_get){
case 1:
news1_1_target_ip = "/rss/topics/top-picks.xml"; // Yahoo!ニューストピックストップ
news_str += ews.EWS_Web_Get("news.yahoo.co.jp", news1_1_target_ip, '\n', "</rss>", "<title>", "</title>", "◆ ");
break;
}
news_str += " ";
Serial.print("\r\nWebGet str = ");
news_str.replace("&","&"); //XMLソースの場合、&が正しく表示されないので、全角に置き換える
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, news_str, sj_txt, &sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
for(i=0; i<16; i++) dummy_Sino_font_buf[i] = Sino_font_buf[0][i]; //1文字目を予め入力しておく
for(uint16_t iy=0; iy<sj_length; iy++){
Serial.write(sj_txt[iy]);
yield();
}
Serial.println(); Serial.print("sj_length="); Serial.println(sj_length);
news_str ="";
scl_cnt = 0; sj_cnt = 0;
Web_first_get = false;
blink_1th_in = true;
WebGetTime = millis();
}
}
if(blink_on == true){ //点滅文字列表示
if((sj_cnt == sj_length-1 && scl_cnt == 8) || blink_1th_in == true){
uint16_t blink_sj_length;
uint8_t blink_sj_txt[16];
uint8_t blink_font_buf[16][16];
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, blink_msg, blink_sj_txt, &blink_sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, blink_sj_txt, blink_sj_length, blink_font_buf);
Red = 31; Green = 0; Blue = 0;
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
for(i=0; i<3; i++){ //3回点滅
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR(jrX0, jrY0+52, Red, Green, Blue, 16, blink_font_buf);
delay(500);
oled.SSD1351bv2_RectFill(jrX0, jrY0+52, 127, jrY0+68, BG_red, BG_green, BG_blue);
delay(500);
}
blink_1th_in = false;
}
}
if(millis()-SCLtime > Scrolle_speed){ //文字スクロール
oled.Scroller_8x16Dot_Replace2(DispChar, Next_buf, SnnmDotOut, dummy_Sino_font_buf, Sino_font_buf, sj_length, &scl_cnt, &sj_cnt);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR(jrX0, jrY0+52, msgRed, msgGreen, msgBlue, 16, SnnmDotOut);
SCLtime = millis();
}
if(millis()-LastNTP_Get > 3600000UL){ //1時間ごとに時刻補正
setSyncProvider(getNtpTime);
LastNTP_Get = millis();
Serial.println("---------------NTP time acquisition completed");
}
Clock_Display(clock_Select);
}
//***************時計表示関数(時、分のみ表示)*****************************
void Clock_Display(uint8_t sec_or_ymd){
String time_str;
uint8_t time_sj_txt[25];
uint16_t time_sj_length;
char h_chr[3], m_chr[3];
uint8_t time_dot[32][16];
int i;
Red = 0, Green = 63, Blue = 0;
if(h_t != hour()){ //時間が変わったら時間表示
sprintf(h_chr, "%2d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, String(h_chr), time_sj_txt, &time_sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, time_sj_txt, time_sj_length, time_dot);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+32, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, time_sj_length, time_dot);
h_t = hour();
}
if(min_t != minute()){ //分が変わったら分表示
sprintf(m_chr, "%02d", minute());//ゼロを空白で埋める場合は%2dとすれば良い
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
u8ts.UTF8_to_SJIS_str_cnv(UtoS, String(m_chr), time_sj_txt, &time_sj_length);
SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, time_sj_txt, time_sj_length, time_dot);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+56, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, time_sj_length, time_dot);
min_t = minute();
}
if(now() != prevDisplay){ //ここからコロン点滅表示。(図形表示)
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_RectFill(jrX0+51, jrY0+40, jrX0+52, jrY0+41, Red, Green, Blue);
oled.SSD1351bv2_RectFill(jrX0+51, jrY0+47, jrX0+52, jrY0+48, Red, Green, Blue);
prevDisplay = now();
sec_colon_time = millis();
}else if(millis()-sec_colon_time >= 500){
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_RectFill(jrX0+51, jrY0+40, jrX0+52, jrY0+41, BG_red, BG_green, BG_blue);
oled.SSD1351bv2_RectFill(jrX0+51, jrY0+47, jrX0+52, jrY0+48, BG_red, BG_green, BG_blue);
}
}
//************JPEG画像65kカラー表示関数************************************************
void jpegDraw_65k_color(char* filename){
char str[100];
uint8 *pImg;
int x,y,bx,by;
uint8_t R, G, B;
SD.begin(cs_SD,40000000);
JpegDec.init(); //この関数だけライブラリに各自新規追加して作らなければならない
JpegDec.decode(filename,0);
Serial.print("Width :"); Serial.println(JpegDec.width);
Serial.print("Height :"); Serial.println(JpegDec.height);
Serial.print("Components:"); Serial.println(JpegDec.comps);
Serial.print("MCU / row :"); Serial.println(JpegDec.MCUSPerRow);
Serial.print("MCU / col :"); Serial.println(JpegDec.MCUSPerCol);
Serial.print("Scan type :"); Serial.println(JpegDec.scanType);
Serial.print("MCU width :"); Serial.println(JpegDec.MCUWidth);
Serial.print("MCU height:"); Serial.println(JpegDec.MCUHeight);
Serial.println("");
sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
Serial.println(str);
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_RectFill(jrX0, picY0, jrX0+127, picY0+53, 0, 0, 0); //OLEDに黒画面出力
SPI.setFrequency(40000000);
SPI.setDataMode(SPI_MODE0);
while(JpegDec.read()){
pImg = JpegDec.pImage ;
for(by=0; by<JpegDec.MCUHeight; by++){
for(bx=0; bx<JpegDec.MCUWidth; bx++){
x = JpegDec.MCUx * JpegDec.MCUWidth + bx;
y = JpegDec.MCUy * JpegDec.MCUHeight + by;
if(x<JpegDec.width && y<JpegDec.height){
if(JpegDec.comps == 1){ // Grayscale
R = round(pImg[0]/(256/32));
G = round(pImg[0]/(256/64));
B = round(pImg[0]/(256/32));
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_1pixel_DisplayOut(picX0+x, picY0+y, R, G, B); //OLEDに1pixelづつ65kカラーで出力
}else{ // RGB
if(x < 128 && y < 128){
R = round(pImg[0]/(256/32));
G = round(pImg[1]/(256/64));
B = round(pImg[2]/(256/32));
SPI.setFrequency(20000000);
SPI.setDataMode(SPI_MODE2);
oled.SSD1351bv2_1pixel_DisplayOut(picX0+x, picY0+y, R, G, B); //OLEDに1pixelづつ65kカラーで出力
}
}
}
pImg += JpegDec.comps ;
}
}
}
}
//*************************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){
memset(packetBuffer, 0, NTP_PACKET_SIZE);
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
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
【解説】
過去の記事と重複しているところは省略していますのでご了承ください。
●8行目:
今回でバージョンアップした、OLED SSD1351 beta ver 2.0 のライブラリインクルードです。
過去のライブラリとの競合を避けるために、ライブラリの名前を変えました。bv2 という文字が追加されています。
以前の記事で紹介したSPI通信高速化で、ESP-WROOM-02 ( ESP8266 ) のGPIO ダイレクトアクセスを使っていますので、以前のスケッチはそのままでは使えませんのでご注意ください。
そして、ver2.0 では、GPIOダイレクトアクセスが他のピンに影響を与えないように PIN_OUT_SET などを指定するようにしました。
●11行目:
当方自作のSDカード用EasyWebSocketライブラリです。
スマホのブラウザと ESP-WROOM-02 ( ESP8266 ) をリアルタイムで双方向通信( WebSocket )をするためのものです。
今回でbeta ver 1.47 になっていますので注意してください。
新しいバージョンのライブラリを使う時には、古いライブラリのフォルダごと削除して、再インストールしてください。
その時に必ずIDEを再起動してください。
●12行目:
今回新たに追加した、SDカード読み取り用 8×8 ドット美咲フォントライブラリです。
以前のSPIFFS用ライブラリは使えませんのでご注意ください。
●13行目:
「ないん」さん作成のJPEGデコーダのインクルードです。
●14-18行:
Arduino core for ESP8266 Wi-Fi chip 標準ライブラリです。
●19行:
Arduino 標準のTimeライブラリです。
●21-25行:
当方自作ライブラリのクラス名を定義します。
自由な名前をつけることができます。
●27-33行:
ESPr Developer ( ESP-WROOM-02, ESP8266 )のGPIOピン設定です。
SDカードライブラリでSPI通信のピン番号が決まっていますので、それに合わせて他の番号も割り振っています。
ただし、注意していただきたいのは、16番ピンはリセット以外に使わないようにしてください。
なぜかというと、SPI高速化によるGPIOダイレクトアクセスでは16番ピンは割り当てられません。
OLED_SSD1351bv2ライブラリでは高速化に影響しないリセットピンだけはdigitalWriteを使っているので、16番ピンを割り当てることができます。
●35-36行:
ご自分のルーター(Wi-Fiアクセスポイント)のSSIDとパスワードに、xxxx部分を書き換えてください。
●38-45行:
micro SD カードにあるフォントファイルやHTMLファイルを定義してます。
●46行:
空のdummyファイルを定義してます。
今回はブラウザに表示するWebページのHTMLのbody要素部分を全てmicro SDカードに保存していて、ESP8266からは送信してません。ですから、連結するファイルは不要なのですが、ファイルが存在しないとエラーになるので、空のダミーファイルの存在が必要です。
●48行:
ブラウザから送信されたJPEG画像ファイルをmicro SDカードのルートに保存しておく一時ファイルを指定してます。
このファイル名配列はポインタではなく、固定長の配列である必要があります。
理由は、画像が送信されると、297行目の関数で前の画像ファイルを削除して新たに保存し直します。その時に、SDカードライブラリのremove関数を使ってますが、それはポインタのファイル名ではエラーになってしまう為です。
●50行:
スクロールする文字数を半角で800にしてますが、あまり多くしてしまうとメモリエラーになります。
このプログラムではSRAMをかなり消費していますので、ご注意ください。
●65行:
SD_EasyWebSocketライブラリで、beta ver 1.47 から追加した ESP8266 のローカルIPアドレス自動取得のためのものです。
114行で自動取得して、292行で使用してます。
●70-78行:
NTPサーバー時刻取得設定です。
ESP8266ボードライブラリのサンプルスケッチをそのまま流用してます。
●114行:
ここでESPr Developer ( ESP-WROOM-02, ESP8266 ) のローカルIPアドレスを自動取得しています。
●120-122行:
以前の記事で書きましたが、OLED_SSD1351 のSPI通信はMODE3です。
しかし、ロジックアナライザーで測定してみると、ESP8266標準のSPIライブラリではSPI_MODE2 がMODE3 でした。おそらくライブラリの名称誤りだと思います。
そして、最大周波数は20MHzでした。
SDカードはESP8266の安定周波数の最高40MHzで読み取るので、OLEDを表示する指令を出す度に周波数やモードの指定替えが必要になります。
●126行:
593-650行で作ったJPEG画像の表示関数を実行しています。
SSD1351 のカラーは最大262000色を表示できますが、電光掲示板の文字列スクロールの高速化を図るために、65000色で表示するようにしてます。
●128行:
ESP-WROOM-02 ( ESP8266 ) は、内部周波数を最大の160MHz にしてますが、SPI通信は理論上80MHz まで指定できます。
ただ、安定動作のためには、
160/4 = 40MHz
が望ましいのです。
●131-165行:
SDカードのファイル読み取りハンドル取得
●168-269行:
セットアップ内のこの部分で、OLED SSD1351 ディスプレイにJR風 電光掲示板 のベースを表示させてます。
Arduino IDE に日本語文字列を入力した場合、文字コードはUTF-8 になるので、Shift_JISコードに変換してます。
その時にはSDカードから読み取るのでSPI周波数を40MHz にして、OLED に表示するときにはSPI周波数を20MHz にしてます。
198行では、今回新たに作ったSDカード読み取り用、美咲フォントの文字列一括変換関数です。
以前の記事の関数ではforループを使って1文字ずつ取得していましたが、今回はちょっと進化しました。
美咲フォントの半角は4×8ドットですが、マイコンの扱える最少ビットは8bitなので、半角と全角を判別して、ビットを詰めるというところの工夫が必要でした。
今回はうまく変換できるようになったと思います。
半角、全角混在の美咲フォントの場合、4bitのものをそれぞれビットを詰めて空白を埋める作業をするので、Shift_JISバイト長と美咲フォントのfont_bufバイト長は異なります。
ですから、Misaki_Length という引数を用意して、美咲フォントバイト長を新たに取得する必要があります。
●292行:
SD_EasyWebSocketライブラリ、beta ver 1.47 で新たに追加した関数です。
HTMLヘッダファイルのローカルIPアドレス部分をESP8266で自動取得するために、ヘッダファイルを分割してIPアドレスをマージしてます。
今回のスケッチでは、ESP8266 のSRAMメモリ節約のために、HTMLタグを全てSDカードに保存しています。
body要素がまとまっているので、パソコンで編集するとリアルタイムでWebページの表示確認ができるので良いと思います。
この関数はメインループ内で常にスマホからのアクセスがあるかどうかチェックしています。
アクセスがあったら、HTMLタグをブラウザへ吐き出します。
アクセスが無い場合はスルーします。
●297行:
スマホブラウザからボタンが押されたり、文字列の送信があった場合に、WebSocket通信でテキストメッセージを受信する関数です。
バイナリデータの場合は指定したファイル名でSDカードに上書き保存します。
先ほど述べたように、ファイル名はポインタはダメで、必ず固定長配列です。
●298-303行:
バイナリデータを受信したら、OLED SSD1351 ディスプレイにJPEG画像を表示させます。
この辺は以下の記事で説明したことと同じです。
スマホのJPEG写真や画像をWi-Fiで飛ばして OLED ( 有機EL )に表示させてみた
●305-469行:
ここで、スマホのブラウザからWebSocket通信で受信したテキスト文字列を判別して、有機EL ( OLED ) ディスプレイの動作を決めています。
ブラウザのボタンが押されたときには
100|Yahoo
というテキストを受信します。
スクロールスピードがタッチされたら、最初の3文字の数値が変化したテキストが送られてきます。
ブラウザのテキストボックスから文字列が送信されてきた場合には、
t1S|山手線
というテキストを受信します。
これは文字配列になっているので、switch文で分類できるわけです。
この辺の詳しい解説は過去のEasyWebSocketライブラリの解説を参考にしていただければと思いますので、ここでは省略します。
385-461行で、ブラウザから送られてきたテキスト文字列から、固定文字列、点滅文字列、スクロール文字列へそれぞれ分類し、さらに東雲フォント、美咲フォントかを判別してそれぞれ変換してます。
先にも述べましたが、8×8ドットの美咲フォントは半角が4×8ドットなので、8ビット単位のfont_buf にする場合、半角を詰めて空白を埋めて変換するので、Shift_JISのバイト長と、美咲フォントのバイト長が異なります。
よって、新たにmsk_fnt_lengthという引数を用意して、美咲フォントバイト長を取得する必要があります。
●471-505行:
ここでは、10分毎に Yahoo! RSSサイトのトップニューストピックスを取得しています。
472行で記事取得間隔を極端に短くしてしまうと、Yahoo! RSSサーバーに負担をかけてしまい、Dos攻撃と判断されてしまうので十分注意してください。
免責事項でも記述しておりますが、当ブログのスケッチやライブラリを使って起こったいかなるトラブルも当方では責任を負いませんので、予めご了承ください。一切無保証です。
マイコンの誤作動や、高速リセット繰り返しなどのエラーも考えられるので、ここの部分は十分注意してください。
483行で記事を取得してターゲットテキストを抽出してます。
●507-527行:
ここでは、有機EL ( OLED )ディスプレイに表示される、「電車がまいります」などの点滅文字列を変換してます。
点滅している間はdelay関数を使っているので、全ての動作が止まります。
●529-535行:
530行を毎度通る度に文字列のドット(pixel) を1ずつずらしてスクロールしてます。
●537-541行:
1時間ごとにNTPサーバーから時刻を取得して、ESPr Developer ( ESP-WROOM-02, ESP8266 ) の内部時計を補正してます。
●546-591行:
ここでは、時計表示をしています。
時間と分のみです。
コロンは文字列ではなく、小さい四角形を描いて表示させてます。
●593-650行:
「ないん」さん作成のJPEGデコーダライブラリを使って、SDカードから読み込んだJPEG画像ファイルを有機EL ( OLED ) に表示させてます。
ここでは、65000色カラーで表示させてます。
以前の記事では262000カラーで表示させてますが、文字列高速スクロールのために画質を落としています。
先ほど述べたように、600行の JpegDec.init(); 関数は自分でライブラリに新たに追加しなければなりません。
これが無いと、スマホから画像の送信は1回切りとなってしまいます。
●652-688行:
これは、Arduino core for ESP8266 のNTPサーバーサンプルスケッチそのままの流用です。
コメント
初めまして、いつも楽しく拝見しています。
今回のスケッチをコンパイルしようとしたところ、530行の”Scroller_8x16Dot_Replace2″の所で止まってしまいました。ライブラリーの方には”Scroller_8x16Dot_Replace”しか無い様で、スケッチとgithubにある”OLED_SSD1351bv2″ライブラリーのバージョンが違うようです、いかがでしょうか。
masaさん
大変失礼いたしました。
たまたま再アップできる環境に居たので、早速再アップしました。
こちらのチェックが足りませんでした。
多くの方にご迷惑をおかけしたかもしれません。
個人運営ブログだと、ところどころ抜けてしまいますね。
こうやって、コメントでご指摘いただけると、とても助かります。
ありがとうございました。
以後、十分に気を付けてアップしたいと思います。m(_ _)m
mgo-tecさん
早速対応して頂きありがとうございました。
やってみたところうまく行きました。まだArduinoはやり始めたばかりの初心者ですが、丁寧に説明してくださっているのでとても分かりやすいです。
mgo-tecさんが開発された技術で、なにか自分でも出来ないかと思っています。
これからも応援していますので、頑張って下さい。
こちらこそ、今回はとても助けていただきました。
このご指摘が無かったら、より多くの方にご迷惑をかけてしまったかもしれません。
ほんとに感謝しています。
m(_ _)m
個人的にはまだまだ雑な記事だと思っておりますが、こういうお声をいただくと、辛い記事アップでも何とか乗り越えられそうです。
ガンバります!!
ありがとうございました。