◯やりたいこと
(1)で作成したモーションセンサーのWroom02(WR02)用のスケッチです。
◯やったこと
以下がWR02用のスケッチです。
ATtiny85(AT85)と同様にデータ保存にリングバッファ(RB)を取り入れWR02特有のものとしてエンキュー・デキュー用のアドレスポインタにRTCメモリを使っています。RTCメモリは通電さえしていればディープスリープしても内容が保持されるRAMなので書込み制限を気にしなくて済み気が楽です。またWiFi周りでノンブロッキング化を少々進めてWiFi接続性能の向上を図っています。
/** * * WR02_MOS v0.1.2 (ok) * AT85からUARTで送られてくるデータをWiFiでSQLに送信する * **/ // BSP(Board Support Package) // ESP8266 Boards v3.1.2 // FlushSize ESP01:1MB Wroom023D:2MB Wroom02:4MB // Built-in Library (Arduino IDE v1.8.19) #include <Arduino.h> #include <time.h> #include <EEPROM.h> #include <HardwareSerial.h> // BSP Built-in Library #include <ESP8266WiFi.h> extern "C" { #include "user_interface.h" } // Installed Library // Global Variable /* WiFi */ #define WIFI_SSID "YOUR_SSID" #define WIFI_PASS "YOUR_PASS" /* TCP/IP */ IPAddress ip (YOUR_IP); // ex. (192,168,0,2) IPAddress gw (YOUR_GW); IPAddress dns (YOUR_DNS); // Google:(8,8,8,8) IPAddress mask (YOUR_MASK); // ex. (255,255,255,0) /* UART */ #define UART_TX 1 #define UART_RX 3 HardwareSerial hs(0); /* Reset */ #define RES_EN 2 /* SQL Server IP */ #define SQL_HOST "SQL_IP" #define SQL_PORT SQL_PORT /* NTP DATA */ #define NTP_SRV1 "133.243.238.244" // "ntp.nict.jp" #define NTP_SRV2 "ntp.jst.mfeed.ad.jp" /* RTC Memory */ #define RTC_CODE 1234 #define RTC_TOP 64 // #define RTC_END 192 // #define RTC_SIZE RTC_END-RTC_TOP // 128個(512byte) #define ADR_CODE RTC_TOP #define ADR_RBT RTC_TOP+1 #define ADR_RBE RTC_TOP+2 /* EEPROM */ #define EEP_SIZE 2048 // EEPROM size #define RB_MAX EEP_SIZE/MD_SIZE // RB(リングバッファ)最大値 /* Measurement DATA */ int MD_SIZE = sizeof(time_t); // time_t(4byte) #define MD_MAX 125 // UART受信可能なデータ数 time_t MDATA [MD_MAX]; /* Time */ time_t UXTM = 0; // UnixTime uint32_t STTM = 0; // スタート時間 milli() /* Function Prototype */ ///// ハードウエア 初期設定 void setup() { // Start time STTM = millis(); // pinモード設定 digitalWrite(RES_EN, LOW); pinMode (RES_EN, OUTPUT); // IRセンサー リセット禁止 ir_resDE(); // UART /* シリアル起動, ESP8266起動時レポートの送信待ち */ hs.begin(19200); delay(200); /* ESP8266起動時レポート用の改行送信 */ hs.print('\n'); delay(5); // EEPROM EEPROM.begin(EEP_SIZE); // RTC memory rtcm_chk(); } void loop() { // ESP8266起動時レポートのATtiny側での処理待ち delay(1000); // 初期値 String line = ""; // Xon送信後 ATからの NTP,SOH待ち //while (hs.available()>0) {char d = hs.read();} unsigned long tout = millis(); while (millis() - tout < 3000) { hs.print("Xon\n"); line = read_line(2000); /* データ転送 mode */ if (line.equals("SOH")) break; /* 時刻転送 mode */ if (line.equals("NTP")) { /* WiFi接続 */; if (wifi_connect(60000)) { if (ntp_connect(10000)) { /* NTP時刻 ->UART ->AT85 */ UXTM = time(NULL); } } hs.println(UXTM); // NTP->AT85 delay(5); wifi_end(); } delay(100); } // 返信なしはスリープ if (line.length()<=0) wifi_end(); // 受信開始(受信データはメモリに格納->高速処理) int mdcnt = 0; unsigned long timeout = millis(); while (millis() - timeout < 5000) { line = read_line(1500); /* 受信タイムアウト */ if (line.length()<=0) break; /* 全データ受信終了 */ else if (line.equals("EOT")) break; /* 受信データ格納 */ else MDATA[mdcnt] = line.toInt(); mdcnt ++; } // MD->RB保存(EEPROMにゆっくり格納) for (int i=0; i<mdcnt; i++) rb_eque(MDATA[i]); // RBにデータがあればデータ送信へ int num = rb_num(); if (num>0) { // WiFi接続チェック(接続成功は データ送信) if (wifi_connect(60000)) { /* リングバッファにデータがあれば送信 */ while (true) { /* データがなければ終了 */ UXTM = rb_peek(); if (!UXTM) break; /* 送信失敗は終了 */ if (!md_send(UXTM)) break; /* 送信成功でキューポインタをinc */ rb_dque(); delayy(100); } } } // 送信終了 wifi_end(); } ///// IR Control //// // IRセンサー許可 inline void ir_resEN() { digitalWrite(RES_EN, HIGH);} // IRセンサー禁止 inline void ir_resDE() { digitalWrite(RES_EN, LOW);} ///// UART Control ///// // 1行読込(タイムアウト付) String read_line(int timeout) { String line = ""; unsigned long tout = millis(); while (millis() - tout < timeout) { if (hs.available() > 0) { line = hs.readStringUntil('\n'); line.trim(); return line; } } return ""; } ///// WiFi Control ///// /* WiFi接続開始処理 * <引数> Timeout: 接続制限時間(0 or 省略は接続するまで無限ループ) * <戻値> 接続:true 未接続:False */ boolean wifi_connect() { return wifi_connect(0); } boolean wifi_connect(int timeout) { unsigned long start = millis(); unsigned long now = start; //int retry = 0; // WiFi 初期化 wifi_init(); // WiFiが未接続の場合のみbeginする if (WiFi.status() != WL_CONNECTED) { WiFi.begin(WIFI_SSID, WIFI_PASS); delayy(500); } // WiFi接続(指定時間内) 接続:true 接続失敗:false while (WiFi.status() != WL_CONNECTED) { if (timeout>0 && millis()-start >= timeout) return false; delayy(500); } return true; } /* WiFi 初期化処理 */ boolean wifi_init() { // WiFi初期化 (順番大事) // disconnect(true)されて再接続の場合 mode,configの再設定が必須 WiFi.mode(WIFI_STA); // 1.WiFi モード設定 WiFi.persistent(false); // 2.WiFi設定情報 保存しない if (!WiFi.config(ip, gw, mask, dns)) { // 3.固定ip 設定 delayy(100); return false; } delayy(100); return true; } /* WiFi stop処理 */ void wifi_stop() { // WiFiが接続の場合のみdisconnectする if (WiFi.status() != WL_CONNECTED) { WiFi.disconnect(true); } } /* 終了処理 */ void wifi_end() { // WiFi切断 wifi_stop(); // Uptime 表示 //@@ hs.print("WR02("); //@@ hs.print((millis()-STTM)/1000.0); //@@ hs.println("sec)"); // Reset 許可 ir_resEN(); // sleep ESP.deepSleep(0); //ESP.deepSleep(60*1000*1000); delay(100); } boolean md_send(time_t uxtm) { ~ ここでデータを加工し送信する } ///// NTP Control ///// /* NTP 同期処理(ノンブロッキング風) * <引数> Timeout[ms]:同期制限時間(0 or 省略は同期するまで無限ループ) * <戻値> Unixtime or 0(エラー) */ time_t ntp_connect() { return ntp_connect(0); } time_t ntp_connect(int timeout) { // WiFi接続している時のみ時間取得する if (WiFi.status() != WL_CONNECTED) { return 0; } // 初期化 unsigned long now; unsigned long start; unsigned long itvl = 250; // NTP再接続時のインターバル時間 unsigned long last = 0; // ノンブロッキング用時間変数 // タイムゾーン,NTPサーバー設定 configTzTime("JST-9", NTP_SRV1, NTP_SRV2, NTP_SRV3); /* 最初に一定時間待機すると同期時にループが省かれる(短時間化) */ delayy(50); // NTPサーバー接続 (timeout秒間 接続確認) start = millis(); while (millis()-start < timeout) { // NTP接続はノンブロッキング風(非同期)で now = millis(); if (now-last >= itvl) { last = now; // 現在時刻の取得 time_t uxtm = time(NULL); // unix時間 // NPT同期チェック if(uxtm>1755000000) { return uxtm; } // 未接続の場合,0.3-0.5sec おきに再接続 itvl = random(300, 500); } delayy(1); } // 同期不可 エラーリターン //@@ hs.println("Connection failed"); return 0; } ///// Time Controle ///// // 日時文字列"yyyy-mm-dd hh:mm:ss"を unixtimeに変換 time_t dat2uxt(String str) { // String->char 変換 int len = str.length()+1; char chr[len]; str.toCharArray(chr, len); // 日時文字列を tm構造に格納 struct tm t; sscanf(chr, "%d-%d-%d %d:%d:%d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec); t.tm_year -= 1900; t.tm_mon -= 1; // 上記 tm構造から unixtimeを求めてリターン return mktime(&t); } // unixtimeを日時文字列"yyyy-mm-dd hh:mm:dd"に変換 String uxt2dat(time_t t) { char date[64]; // フォーマット作成 size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); // unixtime->日時文字列 strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&t)); return date; } ///// Ringbuffer Control ///// // RBポインタ inc inline int rb_inc(int idx) { if (++idx >= RB_MAX) return 0; else return idx; } // RB内のデータ数を求める int rb_num() { /* RTC->RBtop,RBend */ int rbt = rtcm_get(ADR_RBT); int rbe = rtcm_get(ADR_RBE); if (rbt > rbe) rbe += RB_MAX; return rbe - rbt; } // RBポインタから EEPROMのアドレスを求める inline int rb_idx2adr(int idx) {return idx * MD_SIZE;} // エンキュー(enqueue) val->EEP void rb_eque(time_t val) { /* RTC->RBend */ int rbe = rtcm_get(ADR_RBE); /* val->EEP */ int adr = rb_idx2adr(rbe); EEPROM.put(adr, val); EEPROM.commit(); /* RBend inc */ rbe = rb_inc(rbe); rtcm_put(ADR_RBE, rbe); } // デキュー(dequeue) EEP->val time_t rb_dque() { /* RTC->RBtop,RBend */ int rbt = rtcm_get(ADR_RBT); int rbe = rtcm_get(ADR_RBE); if (rbt==rbe) return false; /* EEPROM->val */ time_t val; int adr = rb_idx2adr(rbt); EEPROM.get(adr, val); /* RBtop inc */ rbt = rb_inc(rbt); rtcm_put(ADR_RBT, rbt); return val; } // peek(非破壊読み出し) time_t rb_peek() { /* RTC->RBtop,RBend */ int rbt = rtcm_get(ADR_RBT); int rbe = rtcm_get(ADR_RBE); if (rbt==rbe) return false; /* EEPROM->val */ time_t val; int adr = rb_idx2adr(rbt); EEPROM.get(adr, val); return val; } ///// RTCmemory(ESP8266) Control ///// // RTCメモリ 初期化チェック void rtcm_chk() { int code = rtcm_get(ADR_CODE); if (code!=RTC_CODE) { //@@ ss.println("RTC"); delay(2); /* RTCメモリ ヘッダクリア */ rtcm_put(ADR_CODE, RTC_CODE); rtcm_put(ADR_RBT , 0); rtcm_put(ADR_RBE , 0); } } // RTCメモリに書込む bool rtcm_put(uint32_t addr, uint32_t val) { return system_rtc_mem_write(addr, &val, sizeof(val)); } // RTCメモリから読出す uint32_t rtcm_get(uint32_t addr) { uint32_t val = 0; system_rtc_mem_read(addr, &val, sizeof(val)); return val; } ///// Other Control ///// /* yield delay(ノンブロッキング ディレイ) */ inline void delayy(unsigned long tm) { unsigned long now = millis(); while (millis()-now < tm) { yield();} }
<初期定義>
WR02の起動はAT85のデータFullによるリセット信号により行われます。
初期定義では必要なモジュールのインクルードや各種定数・グローバル変数を定義、別途インストールが必要なモジュールは<Installed Library>にまとめてあります。ちなみにボードの指定が間違っていると<BSP Built-in Library>でエラーが発生します。
<setup>
WR02、シリアル、EEPROM、RTCメモリの設定を行います。シリアル設定では厄介なESP8266の起動メッセージを送信し終わるまで200msほど無駄に待機させています。さらにその起動メッセージをAT85が処理し新規に1行待ち状態にするために追加で"\n"を送信しています。実を言うとこれに気がつくまでどうしてもハンドシェイクがうまくいかず結構ハマってしまっていました。
<loop>
loopは
・ハンドシェイク用にAT85にXonを送信、一定時間返信がなければスリープ
・AT85からの返信がNTPならばNTPから得たUnixtimeを送信しスリープ
・ 〃 SOHならばデータ受信モードに入る
・受信ロストを避けるためEOTまでのデータを一旦メモリに格納
・受信したデータをRBに格納
・WiFiに接続しデータを送信
・データ送信に失敗した場合は次回送信用にRBデータは未消去のまま
・スリープ
という流れになっています。 モーターや高周波ノイズなどで一時的にWiFi通信ができなかった場合の再送信に備えデータは非破壊読み出しで取得しています。RBに残っていれば次回WiFi接続時に全送信されます。もちろん送信に成功すればデキューされデータは削除されます。RBはこの辺の処理が簡単なので助かります。
実はこの方式にしてからロガーからのデータ欠損率が劇的に減少していて、データの取得率はほぼ100%に上昇しています。ノンブロッキング化との相乗効果かもしれません。なんだもっと早く気がついていればよかったと少々後悔していたところです。
<関数>
関数は以下のグループでまとめてあります。
・IR(赤外線センサー)
・UART
・WiFi
・NTP
・Datatime
・RB(リングバッファ)
・RTCメモリ
・その他
何だかんだいってスケッチがかなり長文化。関数は他のスケッチと共用できるものも多くライブラリ化すればいいのでしょうがIDEのライブラリ化はいまだ挑戦したことがありません。サブルーチンの感覚で本体に付随させているのでダラダラと長いスケッチになっています。スケッチの読みにくさ、素人ということで御勘弁です。