ESP-13を使ってみる

1日1回決まった時間にDeepSleepから復帰する


前回は毎時0分にDeepSleepから復帰する方法を紹介しま した。
そこで、今回は1日1回、決まった時間にDeepSleepから復帰する方法を紹介します。
但し、DeepSleepの最大Sleep時間は約70分なので、実際は何回かSleepから復帰して、必要な時だけNTPを使って現在時刻を確 認します。

使用したスケッチは以下のスケッチです。
スケッチの先頭のコメントの様に動作します。
DeepSleep復帰時間は13:20にしましたが、特に意味はありません。

RTCのユーザエリアは以下の用途で使っています。
・先頭の4バイト:CRCとして使用し初回起動か、DeepSleepからの復帰かの判断に使います。
・4バイト目:Sleepから復帰した時の動作モード
MQTTサーバーはローカルなサーバーを使いました。
/*
 RTC User Memoryを使用するDeelSleepモードのテスト
 rtcData.data[0]を以下のように使用する
  =0:NTPクライアントモード
  =1:MQTTクライアントモード
      rtcData.data[1]は起動目的時刻[時]
      rtcData.data[2]は起動目的時刻[分]
  =2:単純Sleepモード
      rtcData.data[1]は単純Sleepする回数

  例
  10:00 電源ON NTPで時間を確認 rtcData.data[0]=0->2 rtcData.data[1]=0->2   70分Sleep
  11:08 復 帰                   rtcData.data[0]=2->0 rtcData.data[1]=2->1   70分Sleep
  12:17 復帰   NTPで時間を確認 rtcData.data[0]=0->0 rtcData.data[1]=1->0   62分Sleep
  13:19 復帰   NTPで時間を確認 rtcData.data[0]=0->1 rtcData.data[1]=0->0    1分Sleep
  13:20 復帰   MQTT送信        rtcData.data[0]=1->2 rtcData.data[1]=0->20  70分Sleep
  14:28 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=20->19 70分Sleep
  15:36 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=19->18 70分Sleep
  16:44 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=18->17 70分Sleep
  17:52 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=17->16 70分Sleep
  19:00 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=16->15 70分Sleep
  20:08 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=15->14 70分Sleep
  21:16 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=14->13 70分Sleep
  22:24 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=13->12 70分Sleep
  23:32 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=12->11 70分Sleep
   0:40 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=11->10 70分Sleep
   1:48 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=10->9  70分Sleep
   2:56 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=9->8   70分Sleep
   4:04 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=8->7   70分Sleep
   5:12 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=7->6   70分Sleep
   6:20 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=6->5   70分Sleep
   7:28 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=5->4   70分Sleep
   8:36 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=4->3   70分Sleep
   9:44 復 帰                   rtcData.data[0]=2->2 rtcData.data[1]=3->2   70分Sleep
  10:52 復 帰                   rtcData.data[0]=2->0 rtcData.data[1]=2->1   70分Sleep
  12:00 復帰   NTPで時間を確認 rtcData.data[0]=0->0 rtcData.data[1]=0->0   79分Sleep
  13:19 復帰   NTPで時間を確認 rtcData.data[0]=0->0 rtcData.data[1]=0->0    1分Sleep
  13:20 復帰   MQTT送信        rtcData.data[0]=1->2 rtcData.data[1]=0->20  70分Sleep
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiUdp.h>
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <Ticker.h>

#define TARGET_HOR 13 // DeepSleep復帰時刻(時)
#define TARGET_MIN 20 // DeepSleep復帰時刻(分)
#define TARGET_SEC 0  // DeepSleep復帰時刻(秒)
#define LED_PIN 4

// Update these with values suitable for your network.
const char* ssid = "アクセスポイントのSSID";
const char* password = "アクセスポイントのパスワード";
const char* mqtt_server = "192.168.10.40";
//const char* mqtt_server = "broker.hivemq.com";

    long diff;

    // 現在日時を設定
//    Serial.print("jst=");
//    Serial.print(jst);
    setTime(jst);
    // 指定日時(当日)のUnixTime
    time_tg = getTime(hh,mm,ss,day(),month(),year());
//    Serial.print(" time_tg=");
//    Serial.print(time_tg);
    // 指定日時(翌日)のUnixTime
    time_tn = time_tg + 60*60*24;
//    Serial.print(" time_tn=");
//    Serial.println(time_tn);
    diff=time_tg-jst;
    if (diff < 0) {
      diff=time_tn-jst;
    }
    showTime("Now",jst);
    showTime("Target",time_tg);
    Serial.print("diff=");
    Serial.println(diff);
    showTime("Result",jst+diff);
    return diff;
}

/*
  *  次のMM:SSまでのSleep秒を求める
  *  jst: 現在日時(日本時間のUNIXTIME)
  *  mm : 指定時間(分)
  *  ss : 指定時間(秒)
*/
long getSleepSecond2(time_t jst, int mm, int ss) {
    time_t time_tg;
    time_t time_tn;
    long diff;

    // 現在日時を設定
    Serial.print("jst=");
    Serial.println(jst);
    setTime(jst);
//    showNow("Now");
    // 1時間後のUnixTime
    time_tg = getTime(hour()+1,mm,ss,day(),month(),year());
    diff=time_tg-jst;
    showTime("Now",jst);
    showTime("Target",time_tg);
//    Serial.print("diff=");
//    Serial.println(diff);
    showTime("Result",jst+diff);
    return diff;
}

void flush_led() {
  static bool flag = true;
  if (flag) digitalWrite(LED_PIN,LOW);
  if (!flag) digitalWrite(LED_PIN,HIGH);
  flag=!flag;
}

void setup() {
  long sleep;
 
  delay(500);
  Serial.begin(9600);
  Serial.println();
  Serial.println();
  Serial.println();
  Serial.println("Wake Up");
 
  // RTC memoryからデータを読み込む
  if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("Read: ");
    printMemory(10);
  // 読み込んだデータでCRC32を計算する
    uint32_t crcOfData = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
    Serial.print("CRC32 of data: ");
    Serial.println(crcOfData, HEX);
    Serial.print("CRC32 read from RTC: ");
    Serial.println(rtcData.crc32, HEX);
  // CRC32が一致しないので初回起動時
    if (crcOfData != rtcData.crc32) {
      Serial.println("CRC32 in RTC memory doesn't match CRC32 of data. Data is probably invalid!");
      for (int i = 0; i < sizeof(rtcData); i++) {
        rtcData.data[i] = 0;
      }
    }
 // CRC32が一致したのでDeelSleepからの復帰
    else {
      Serial.println("CRC32 check ok, data is probably valid.");
    }
  }

  // NTPクライントモード
  if (rtcData.data[0] == 0) {
    rtcData.data[3]++;
    pinMode(LED_PIN,OUTPUT);
    digitalWrite(LED_PIN,LOW);
    ticker1.attach_ms(50, flush_led);
    setup_wifi();
    ticker1.detach();
    Serial.println("Starting UDP Client");
    udp.begin(localPort);
  // 時刻リクエストを送信
    sendNTPpacket(timeServer);

  // MQTTクライアントモード
  } else if (rtcData.data[0] == 1) {
    setup_wifi();
    Serial.println("Starting MQTT Client");
    client.setServer(mqtt_server, 1883);
    client.setCallback(callback);
    server_connect();
    client.loop();
  //
  // ここに何か処理を書く(例えばセンサーからのデータ読み込み)
  //
    sprintf(topic,"DeepSleep/ESP8266-%06x",ESP.getChipId());
    sprintf(msg,"%02d %02d:%02d %d",
    rtcData.data[0],rtcData.data[1],rtcData.data[2],rtcData.data[3]);
    Serial.println(msg);
    client.publish(topic, msg);
    client.disconnect();
    WiFi.disconnect();

    rtcData.data[0] = 2; // 次回起動は単純Sleepモード
    rtcData.data[1] = 21; // 単純Sleepする回数(21*70*60=24時間30分)
    rtcData.data[2] = 0;
    rtcData.data[3] = 0;

    // CRC32を再計算
    rtcData.crc32 = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
    // RTC memoryにデータを書き込む
    if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
      Serial.println("Write: ");
      printMemory(10);
    }

   // 次回は70分後に起動する(DeepSleepの最大Sleep時間は約71分)
    sleep = (70 * 60);
   
  // DEEP SLEEPモード突入命令
    Serial.println("DEEP SLEEP START!!");
    ESP.deepSleep(sleep * 1000 * 1000 , WAKE_RF_DEFAULT);
    delay(1000);

  // 単純Sleepモード
  } else {
    rtcData.data[1]--;
    if (rtcData.data[1] == 1) {
      rtcData.data[0] = 0; // 次回起動はNTPモード
    }
    sleep = (70 * 60); // 再び70分のSleep

    // CRC32を再計算
    rtcData.crc32 = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
    // RTC memoryにデータを書き込む
    if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
      Serial.println("Write: ");
      printMemory(10);
    }

  // DEEP SLEEPモード突入命令
    Serial.println("DEEP SLEEP START!!");
    ESP.deepSleep(sleep * 1000 * 1000 , WAKE_RF_DEFAULT);
    delay(1000);
  }
}

void loop()
{
  long sleep;
  long sleep2;


  // NTPサーバからのパケット受信
  int cb = udp.parsePacket();
  if (cb) {
    Serial.print("packet received, length=");
    Serial.println(cb);
    // バッファに受信データを読み込む
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // 時刻情報はパケットの40バイト目からはじまる4バイトのデータ
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);

    // NTPタイムスタンプは64ビットの符号無し固定小数点数(整数部32ビット、小数部32ビット)
    // 1900年1月1日0時との相対的な差を秒単位で表している
    // 小数部は切り捨てて、秒を求めている
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = " );
    Serial.println(secsSince1900);

    // NTPタイムスタンプをUNIXタイムに変換する
    // UNITタイムは1970年1月1日0時からはじまる
    // 1900年から1970年の70年を秒で表すと2208988800秒になる
    const unsigned long seventyYears = 2208988800UL;
    // NTPタイムスタンプから70年分の秒を引くとUNIXタイムが得られる
    time_t epoch = secsSince1900 - seventyYears;
    Serial.print("Unix time = ");
    Serial.println(epoch);
    // 日本時間のUNITタイム
    time_t jst = epoch + (9 * 60 * 60);
   
    // 目的の時間までのSleep時間を求める
//    sleep=getSleepSecond2(jst,TARGET_MIN,TARGET_SEC);
    sleep=getSleepSecond(jst,TARGET_HOR,TARGET_MIN,TARGET_SEC);
    Serial.print("sleep = ");
    Serial.println(sleep);
    sleep2 = sleep;
    WiFi.disconnect();

    Serial.print("sleep2 = ");
    Serial.println(sleep2);
    if (sleep2 <= 60) {
      sleep = sleep2;
      rtcData.data[0] = 1; // 次回起動はMQTTモード
      rtcData.data[1] = TARGET_HOR;
      rtcData.data[2] = TARGET_MIN;
    } else if (sleep2 <= 4200) {
      sleep = sleep2 - 60;
      rtcData.data[0] = 0; // 次回起動はNTPモード
      rtcData.data[1] = 0;
      rtcData.data[2] = 0;
    } else if (sleep2 < 8400) {
      sleep = sleep2 - 4200;
      rtcData.data[0] = 2; // 次回起動は単純Sleepモード
      rtcData.data[1] = 2; // 単純Sleepする回数(最低2回)
      rtcData.data[2] = 0;
    } else {
      sleep = 4200;
      rtcData.data[0] = 2; // 次回起動は単純Sleepモード
      rtcData.data[1] = sleep2 / 4200; // 単純Sleepする回数(最低2回)
      rtcData.data[2] = 0;
    }

    // CRC32を再計算
    rtcData.crc32 = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
    // RTC memoryにデータを書き込む
    if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
      Serial.println("Write: ");
      printMemory(10);
    }
    Serial.print("sleep = ");
    Serial.println(sleep);
    showTime("Next Wake Up",jst+sleep);
   
    // DEEP SLEEPモード突入命令
    Serial.println("DEEP SLEEP START!!");
    ESP.deepSleep(sleep * 1000 * 1000 , WAKE_RF_DEFAULT);
    delay(1000);
  }
}

OrangePi-PCでMQTTのSubscriberを作成し、受信した日時とMQTTのメッセージをファイルに記録するようにしました。
結果は以下の通り、13時20分にDeepSleepから復帰して、MQTTでデータを送信しています。
Wifiアクセスポイントの接続に大体7秒ぐらいかかるので、MQTTサーバーに接続するのは少し遅れた時間となります。
[ ]内の最後の数字はNTPにアクセスした回数です。
起動直後は目的時間までのSleep時間が分からないので、何回かNTPでSleep時間を確認していることが分かります。