Metro M4 Airlift Lite Sleep?

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Metro M4 Airlift Lite Sleep?

Post by asyork »

I'm running Arduino IDE code that only needs to update on button press or every 15 minutes, whichever is sooner, and I'm planning on making it battery powered (2x18650's with a barrel jack connector, probably overkill, but I'd still like to optimize). It uses an eink display, so no power needs to be supplied during sleep. It also runs into a lot of WiFi/communication errors and needs to be reset, I'd like to do that automatically in software, but haven't figured that one out either.

Here is what I am working with, https://learn.adafruit.com/epaper-weather-station

My research has led me here, https://github.com/adafruit/Adafruit_SleepyDog. I added the sleep function near the end of the void loop().
Here is what I added to the bottom of the void loop() so I can see if it does it.

Code: Select all

  Serial.println("Going to sleep");
  int sleepMS = Watchdog.sleep(1000);
  #if defined(USBCON) && !defined(USE_TINYUSB)
  USBDevice.attach();
  #endif
  Serial.print("I'm awake now! I slept for ");
  Serial.print(sleepMS, DEC);
  Serial.println(" milliseconds.");
  Serial.println();
The 1000 was just to test the sleep timer, but buttons don't interrupt it, and setting it too high seems to prevent it from waking up. I'd like to figure out what the max timer is and use that or 15 min if possible. It also never reconnects to the serial monitor after waking. That will be fine when I move it to battery, but it makes it difficult to figure out what is going on now. Buttons not interrupting it might not be the end of the world either, as I'll likely set it up where I want it and leave it sitting on my desk forever once I'm done, but it would be nice to maintain the functionality if possible.

I am fairly new to this and programming in general, particularly C++. I have some python and java experience, so I know some programming basics.

Edit: I tried using the program here viewtopic.php?f=52&t=137543 that seems to have worked for some people in a similar situation, but it isn't working for me.
Edit 2: Some more messing around with the code got it to sometimes reconnect to the serial monitor program and I at least got the max sleep time of 8000ms and can tell it is properly looping through the sleep function. I also realized another potential problem, will sleeping prevent millis() from counting up? If so, I can base the update on the number of times it has slept.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Metro M4 Airlift Lite Sleep?

Post by User_UMjT7KxnxP8YN8 »

If you want the M4's ATSAMD51 processor to be automatically reset if your software hangs up, you can use the watchdog timer. There's a good example at https://github.com/SapientHetero/Watchd ... SAMD51J19A.

If you want to "manually" reset the processor in your code, you can call this ATSAMD51-specific function.

Code: Select all

/*******************************************************************************************************************************************************************************************
   reboot

   This function forces the system to reboot immediately
 *******************************************************************************************************************************************************************************************/
 void reboot(void) {

  //*DBL_TAP_PTR = DBL_TAP_MAGIC;
  //SCB->AIRCR = (0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk;  // force reboot
    __DSB();                                                   // Ensure all outstanding memory accesses included
                                                               //   buffered write are completed before reset 
  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                 SCB_AIRCR_SYSRESETREQ_Msk);                   // Keep priority group unchanged
  __DSB();                                                     // Ensure completion of memory access
  while(1);                                                    // wait until reset
  
}   // reboot

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

Thanks, testing out the reboot code. I tried the watchdog I posted and didn't have any luck (program would even run, so I most likely did something wrong), I'll try out the one you posted this weekend.

Edit: The software I'm using hangs on purpose waiting for the user to reboot, so it was easy to know where to put the reboots. May not need the watchdog if this stays stable.

Edit 2: Finally hit an error and the auto reboot worked flawlessly.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

I'm having the same issue I've had with other watchdogs timers I've tried in that once I add them the code just stops working. This one is at least rebooting the device as expected, but I can't get the timer to reset where it needs to or something. Maybe the wifi connection just takes longer than 8 seconds and I won't be able to use it in this sketch, which would be a shame. I want to turn this into a desktop device I can just leave running.

Here's my code:

Code: Select all

// SPDX-FileCopyrightText: 2019 Dan Cogliano for Adafruit Industries
//
// SPDX-License-Identifier: MIT


extern "C" {
#include "wdtFunctions.h"
}

#include <time.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ThinkInk.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoJson.h>        //https://github.com/bblanchon/ArduinoJson
#include <SPI.h>
#include <WiFiNINA.h>

#include "secrets.h"
#include "OpenWeatherMap.h"

#include "Fonts/meteocons48pt7b.h"
#include "Fonts/meteocons24pt7b.h"
#include "Fonts/meteocons20pt7b.h"
#include "Fonts/meteocons16pt7b.h"

#include "Fonts/moon_phases20pt7b.h"
#include "Fonts/moon_phases36pt7b.h"

#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>

#define SRAM_CS     8
#define EPD_CS      10
#define EPD_DC      9
#define EPD_RESET -1
#define EPD_BUSY -1

#define NEOPIXELPIN   40

// This is for the 2.7" tricolor EPD
ThinkInk_270_Tricolor_C44 gfx(EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);

AirliftOpenWeatherMap owclient(&Serial);
OpenWeatherMapCurrentData owcdata;
OpenWeatherMapForecastData owfdata[3];

Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, NEOPIXELPIN, NEO_GRB + NEO_KHZ800);

/*******************************************************************************************************************************************************************************************
   reboot

   This function forces the system to reboot immediately
 *******************************************************************************************************************************************************************************************/
void reboot(void) {

  //*DBL_TAP_PTR = DBL_TAP_MAGIC;
  //SCB->AIRCR = (0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk;  // force reboot
  __DSB();                                                   // Ensure all outstanding memory accesses included
  //   buffered write are completed before reset
  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                 SCB_AIRCR_SYSRESETREQ_Msk);                   // Keep priority group unchanged
  __DSB();                                                     // Ensure completion of memory access
  while (1);                                                   // wait until reset

}   // reboot

const char *moonphasenames[29] = {
  "New Moon",
  "Waxing Crescent",
  "Waxing Crescent",
  "Waxing Crescent",
  "Waxing Crescent",
  "Waxing Crescent",
  "Waxing Crescent",
  "Quarter",
  "Waxing Gibbous",
  "Waxing Gibbous",
  "Waxing Gibbous",
  "Waxing Gibbous",
  "Waxing Gibbous",
  "Waxing Gibbous",
  "Full Moon",
  "Waning Gibbous",
  "Waning Gibbous",
  "Waning Gibbous",
  "Waning Gibbous",
  "Waning Gibbous",
  "Waning Gibbous",
  "Last Quarter",
  "Waning Crescent",
  "Waning Crescent",
  "Waning Crescent",
  "Waning Crescent",
  "Waning Crescent",
  "Waning Crescent",
  "Waning Crescent"
};

int8_t readButtons(void) {
  uint16_t reading = analogRead(A3);
  //Serial.println(reading);

  if (reading > 600) {
    return 0; // no buttons pressed
  }
  if (reading > 400) {
    return 4; // button D pressed
  }
  if (reading > 250) {
    return 3; // button C pressed
  }
  if (reading > 125) {
    return 2; // button B pressed
  }
  return 1; // Button A pressed
}

bool wifi_connect() {

  wdtClear();

  Serial.print("Connecting to WiFi... ");

  WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    displayError("Communication with WiFi module failed!");
    while (true);
  }

  String fv = WiFi.firmwareVersion();
  if (fv < "1.0.0") {
    Serial.println("Please upgrade the firmware");
  }

  neopixel.setPixelColor(0, neopixel.Color(0, 0, 7));
  neopixel.show();
  if (WiFi.begin(WIFI_SSID, WIFI_PASSWORD) == WL_CONNECT_FAILED)
  {
    Serial.println("WiFi connection failed!");
    displayError("WiFi connection failed!");
    return false;
  }

  int wifitimeout = 15;
  int wifistatus;
  while ((wifistatus = WiFi.status()) != WL_CONNECTED && wifitimeout > 0) {
    wdtClear();
    delay(1000);
    Serial.print(".");
    wifitimeout--;
  }
  if (wifitimeout == 0)
  {
    Serial.println("WiFi connection timeout with error " + String(wifistatus));
    displayError("WiFi connection timeout with error " + String(wifistatus));
    neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
    neopixel.show();
    return false;
  }
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
  Serial.println("Connected");
  return true;
}

void wget(String &url, int port, char *buff)
{
  int pos1 = url.indexOf("/", 0);
  int pos2 = url.indexOf("/", 8);
  String host = url.substring(pos1 + 2, pos2);
  String path = url.substring(pos2);
  Serial.println("to wget(" + host + "," + path + "," + port + ")");
  wget(host, path, port, buff);
}

void wget(String &host, String &path, int port, char *buff)
{
  //WiFiSSLClient client;
  WiFiClient client;

  neopixel.setPixelColor(0, neopixel.Color(0, 0, 7));
  neopixel.show();
  client.stop();
  if (client.connect(host.c_str(), port)) {
    Serial.println("connected to server");
    // Make a HTTP request:
    client.println(String("GET ") + path + String(" HTTP/1.0"));
    client.println("Host: " + host);
    client.println("Connection: close");
    client.println();

    uint32_t bytes = 0;
    int capturepos = 0;
    bool capture = false;
    int linelength = 0;
    char lastc = '\0';
    while (true)
    {
      while (client.available()) {
        char c = client.read();
        //Serial.print(c);
        if ((c == '\n') && (lastc == '\r'))
        {
          if (linelength == 0)
          {
            capture = true;
          }
          linelength = 0;
        }
        else if (capture)
        {
          buff[capturepos++] = c;
          //Serial.write(c);
        }
        else
        {
          if ((c != '\n') && (c != '\r'))
            linelength++;
        }
        lastc = c;
        bytes++;
      }

      // if the server's disconnected, stop the client:
      if (!client.connected()) {
        //Serial.println();
        Serial.println("disconnecting from server.");
        client.stop();
        buff[capturepos] = '\0';
        Serial.println("captured " + String(capturepos) + " bytes");
        break;
      }
    }
  }
  else
  {
    Serial.println("problem connecting to " + host + ":" + String(port));
    buff[0] = '\0';
  }
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
}

int getStringLength(String s)
{
  int16_t  x = 0, y = 0;
  uint16_t w, h;
  gfx.getTextBounds(s, 0, 0, &x, &y, &w, &h);
  return w + x;
}

/*
  return value is percent of moon cycle ( from 0.0 to 0.999999), i.e.:

  0.0: New Moon
  0.125: Waxing Crescent Moon
  0.25: Quarter Moon
  0.375: Waxing Gibbous Moon
  0.5: Full Moon
  0.625: Waning Gibbous Moon
  0.75: Last Quarter Moon
  0.875: Waning Crescent Moon

*/
float getMoonPhase(time_t tdate)
{

  time_t newmoonref = 1263539460; //known new moon date (2010-01-15 07:11)
  // moon phase is 29.5305882 days, which is 2551442.82048 seconds
  float phase = abs( tdate - newmoonref) / (double)2551442.82048;
  phase -= (int)phase; // leave only the remainder
  if (newmoonref > tdate)
    phase = 1 - phase;
  return phase;
}

void displayError(String str)
{
  // show error on display
  neopixel.setPixelColor(0, neopixel.Color(7, 0, 0));
  neopixel.show();

  Serial.println(str);

  gfx.setTextColor(EPD_BLACK);
  gfx.powerUp();
  gfx.clearBuffer();
  gfx.setTextWrap(true);
  gfx.setCursor(10, 60);
  gfx.setFont(&FreeSans12pt7b);
  gfx.print(str);
  gfx.display();
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
}

void displayHeading(OpenWeatherMapCurrentData &owcdata)
{

  time_t local = owcdata.observationTime + owcdata.timezone;
  struct tm *timeinfo = gmtime(&local);
  char datestr[80];
  // date
  //strftime(datestr,80,"%a, %d %b %Y",timeinfo);
  strftime(datestr, 80, "%a, %b %d", timeinfo);
  gfx.setFont(&FreeSans18pt7b);
  gfx.setCursor((gfx.width() - getStringLength(datestr)) / 2, 30);
  gfx.print(datestr);

  // city
  strftime(datestr, 80, "%A", timeinfo);
  gfx.setFont(&FreeSansBold12pt7b);
  gfx.setCursor((gfx.width() - getStringLength(owcdata.cityName)) / 2, 60);
  gfx.print(owcdata.cityName);
}

void displayForecastDays(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
  for (int i = 0; i < count; i++)
  {
    // day

    time_t local = owfdata[i].observationTime + owcdata.timezone;
    struct tm *timeinfo = gmtime(&local);
    char strbuff[80];
    strftime(strbuff, 80, "%I", timeinfo);
    String datestr = String(atoi(strbuff));
    strftime(strbuff, 80, "%p", timeinfo);
    // convert AM/PM to lowercase
    strbuff[0] = tolower(strbuff[0]);
    strbuff[1] = tolower(strbuff[1]);
    datestr = datestr + " " + String(strbuff);
    gfx.setFont(&FreeSans9pt7b);
    gfx.setCursor(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(datestr)) / 2, 94);
    gfx.print(datestr);

    // weather icon
    String wicon = owclient.getMeteoconIcon(owfdata[i].icon);
    gfx.setFont(&meteocons20pt7b);
    gfx.setCursor(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(wicon)) / 2, 134);
    gfx.print(wicon);

    // weather main description
    gfx.setFont(&FreeSans9pt7b);
    gfx.setCursor(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(owfdata[i].main)) / 2, 154);
    gfx.print(owfdata[i].main);

    // temperature
    int itemp = (int)(owfdata[i].temp + .5);
    int color = EPD_BLACK;
    if ((OWM_METRIC && itemp >= METRIC_HOT) || (!OWM_METRIC && itemp >= ENGLISH_HOT))
      color = EPD_RED;
    gfx.setTextColor(color);
    gfx.setFont(&FreeSans9pt7b);
    gfx.setCursor(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(String(itemp))) / 2, 172);
    gfx.print(itemp);
    gfx.drawCircle(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(String(itemp))) / 2 + getStringLength(String(itemp)) + 6, 163, 3, color);
    gfx.drawCircle(i * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(String(itemp))) / 2 + getStringLength(String(itemp)) + 6, 163, 2, color);
    gfx.setTextColor(EPD_BLACK);
  }
}

void displayForecast(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
  gfx.powerUp();
  gfx.clearBuffer();
  neopixel.setPixelColor(0, neopixel.Color(0, 7, 0));
  neopixel.show();

  gfx.setTextColor(EPD_BLACK);
  displayHeading(owcdata);

  displayForecastDays(owcdata, owfdata, count);
  gfx.display();
  gfx.powerDown();
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
}

void displayAllWeather(OpenWeatherMapCurrentData &owcdata, OpenWeatherMapForecastData owfdata[], int count = 3)
{
  gfx.powerUp();
  gfx.clearBuffer();
  neopixel.setPixelColor(0, neopixel.Color(0, 7, 0));
  neopixel.show();

  gfx.setTextColor(EPD_BLACK);

  // date string
  time_t local = owcdata.observationTime + owcdata.timezone;
  struct tm *timeinfo = gmtime(&local);
  char datestr[80];
  // date
  //strftime(datestr,80,"%a, %d %b %Y",timeinfo);
  strftime(datestr, 80, "%a, %b %d %Y", timeinfo);
  gfx.setFont(&FreeSans9pt7b);
  gfx.setCursor((gfx.width() - getStringLength(datestr)) / 2, 14);
  gfx.print(datestr);

  // weather icon
  String wicon = owclient.getMeteoconIcon(owcdata.icon);
  gfx.setFont(&meteocons24pt7b);
  gfx.setCursor((gfx.width() / 3 - getStringLength(wicon)) / 2, 56);
  gfx.print(wicon);

  // weather main description
  gfx.setFont(&FreeSans9pt7b);
  gfx.setCursor((gfx.width() / 3 - getStringLength(owcdata.main)) / 2, 72);
  gfx.print(owcdata.main);

  // temperature
  gfx.setFont(&FreeSansBold24pt7b);
  int itemp = owcdata.temp + .5;
  int color = EPD_BLACK;
  if ((OWM_METRIC && (int)itemp >= METRIC_HOT) || (!OWM_METRIC && (int)itemp >= ENGLISH_HOT))
    color = EPD_RED;
  gfx.setTextColor(color);
  gfx.setCursor(gfx.width() / 3 + (gfx.width() / 3 - getStringLength(String(itemp))) / 2, 58);
  gfx.print(itemp);
  gfx.setTextColor(EPD_BLACK);

  // draw temperature degree as a circle (not available as font character
  gfx.drawCircle(gfx.width() / 3 + (gfx.width() / 3 + getStringLength(String(itemp))) / 2 + 8, 58 - 30, 4, color);
  gfx.drawCircle(gfx.width() / 3 + (gfx.width() / 3 + getStringLength(String(itemp))) / 2 + 8, 58 - 30, 3, color);

  // draw moon
  // draw Moon Phase
  float moonphase = getMoonPhase(owcdata.observationTime);
  int moonage = 29.5305882 * moonphase;
  //Serial.println("moon age: " + String(moonage));
  // convert to appropriate icon
  String moonstr = String((char)((int)'A' + (int)(moonage * 25. / 30)));
  gfx.setFont(&moon_phases20pt7b);
  // font lines look a little thin at this size, drawing it a few times to thicken the lines
  gfx.setCursor(2 * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(moonstr)) / 2, 56);
  gfx.print(moonstr);
  gfx.setCursor(2 * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(moonstr)) / 2 + 1, 56);
  gfx.print(moonstr);
  gfx.setCursor(2 * gfx.width() / 3 + (gfx.width() / 3 - getStringLength(moonstr)) / 2, 56 - 1);
  gfx.print(moonstr);

  // draw moon phase name
  int currentphase = moonphase * 28. + .5;
  gfx.setFont(); // system font (smallest available)
  gfx.setCursor(2 * gfx.width() / 3 + max(0, (gfx.width() / 3 - getStringLength(moonphasenames[currentphase])) / 2), 62);
  gfx.print(moonphasenames[currentphase]);


  displayForecastDays(owcdata, owfdata, count);
  gfx.display();
  gfx.powerDown();
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();

}

void displayCurrentConditions(OpenWeatherMapCurrentData &owcdata)
{
  gfx.powerUp();
  gfx.clearBuffer();
  neopixel.setPixelColor(0, neopixel.Color(0, 7, 0));
  neopixel.show();

  gfx.setTextColor(EPD_BLACK);
  displayHeading(owcdata);

  // weather icon
  String wicon = owclient.getMeteoconIcon(owcdata.icon);
  gfx.setFont(&meteocons48pt7b);
  gfx.setCursor((gfx.width() / 2 - getStringLength(wicon)) / 2, 156);
  gfx.print(wicon);

  // weather main description
  gfx.setFont(&FreeSans9pt7b);
  gfx.setCursor(gfx.width() / 2 + (gfx.width() / 2 - getStringLength(owcdata.main)) / 2, 160);
  gfx.print(owcdata.main);

  // temperature
  gfx.setFont(&FreeSansBold24pt7b);
  int itemp = owcdata.temp + .5;
  int color = EPD_BLACK;
  if ((OWM_METRIC && (int)itemp >= METRIC_HOT) || (!OWM_METRIC && (int)itemp >= ENGLISH_HOT))
    color = EPD_RED;
  gfx.setTextColor(color);
  gfx.setCursor(gfx.width() / 2 + (gfx.width() / 2 - getStringLength(String(itemp))) / 2, 130);
  gfx.print(itemp);
  gfx.setTextColor(EPD_BLACK);

  // draw temperature degree as a circle (not available as font character
  gfx.drawCircle(gfx.width() / 2 + (gfx.width() / 2 + getStringLength(String(itemp))) / 2 + 10, 130 - 26, 4, color);
  gfx.drawCircle(gfx.width() / 2 + (gfx.width() / 2 + getStringLength(String(itemp))) / 2 + 10, 130 - 26, 3, color);

  gfx.display();
  gfx.powerDown();
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
}

void displaySunMoon(OpenWeatherMapCurrentData &owcdata)
{

  gfx.powerUp();
  gfx.clearBuffer();
  neopixel.setPixelColor(0, neopixel.Color(0, 7, 0));
  neopixel.show();

  gfx.setTextColor(EPD_BLACK);
  displayHeading(owcdata);

  // draw Moon Phase
  float moonphase = getMoonPhase(owcdata.observationTime);
  int moonage = 29.5305882 * moonphase;
  // convert to appropriate icon
  String moonstr = String((char)((int)'A' + (int)(moonage * 25. / 30)));
  gfx.setFont(&moon_phases36pt7b);
  gfx.setCursor((gfx.width() / 3 - getStringLength(moonstr)) / 2, 140);
  gfx.print(moonstr);

  // draw moon phase name
  int currentphase = moonphase * 28. + .5;
  gfx.setFont(&FreeSans9pt7b);
  gfx.setCursor(gfx.width() / 3 + max(0, (gfx.width() * 2 / 3 - getStringLength(moonphasenames[currentphase])) / 2), 110);
  gfx.print(moonphasenames[currentphase]);

  // draw sunrise/sunset

  // sunrise/sunset times
  // sunrise

  time_t local = owcdata.sunrise + owcdata.timezone + 30;  // round to nearest minute
  struct tm *timeinfo = gmtime(&local);
  char strbuff[80];
  strftime(strbuff, 80, "%I", timeinfo);
  String datestr = String(atoi(strbuff));
  strftime(strbuff, 80, ":%M %p", timeinfo);
  datestr = datestr + String(strbuff) + " - ";
  // sunset
  local = owcdata.sunset + owcdata.timezone + 30; // round to nearest minute
  timeinfo = gmtime(&local);
  strftime(strbuff, 80, "%I", timeinfo);
  datestr = datestr + String(atoi(strbuff));
  strftime(strbuff, 80, ":%M %p", timeinfo);
  datestr = datestr + String(strbuff);

  gfx.setFont(&FreeSans9pt7b);
  int datestrlen = getStringLength(datestr);
  int xpos = (gfx.width() - datestrlen) / 2;
  gfx.setCursor(xpos, 166);
  gfx.print(datestr);

  // draw sunrise icon
  // sun icon is "B"
  String wicon = "B";
  gfx.setFont(&meteocons16pt7b);
  gfx.setCursor(xpos - getStringLength(wicon) - 12, 174);
  gfx.print(wicon);

  // draw sunset icon
  // sunset icon is "A"
  wicon = "A";
  gfx.setCursor(xpos + datestrlen + 12, 174);
  gfx.print(wicon);

  gfx.display();
  gfx.powerDown();
  neopixel.setPixelColor(0, neopixel.Color(0, 0, 0));
  neopixel.show();
}

void setup() {
  neopixel.begin();
  neopixel.show();

  gfx.begin();
  Serial.println("ePaper display initialized");
  gfx.setRotation(2);
  gfx.setTextWrap(false);

  Serial.println("Initializing WDT");

  wdtInit();                                   // enable WDT
}

void loop() {
  char data[4000];
  static uint32_t timer = millis();
  static uint8_t lastbutton = 1;
  static bool firsttime = true;

  int button = readButtons();

  wdtClear();

  // update weather data at specified interval or when button 4 is pressed
  if ((millis() >= (timer + 1000 * 60 * UPDATE_INTERVAL)) || (button == 4) || firsttime)
  {
    Serial.println("getting weather data");
    firsttime = false;
    timer = millis();
    int retry = 6;
    while (!wifi_connect())
    {
      wdtClear();
      delay(5000);
      retry--;
      if (retry < 0)
      {
        displayError("Can not connect to WiFi, press reset to restart");
        reboot();
        while (1);
      }
    }
    String urlc = owclient.buildUrlCurrent(OWM_KEY, OWM_LOCATION);
    Serial.println(urlc);
    retry = 6;
    do
    {
      retry--;
      wget(urlc, 80, data);
      if (strlen(data) == 0 && retry < 0)
      {
        displayError("Can not get weather data, press reset to restart");
        reboot();
        while (1);
      }
    }
    while (strlen(data) == 0);
    Serial.println("data retrieved:");
    Serial.println(data);
    retry = 6;
    while (!owclient.updateCurrent(owcdata, data))
    {
      wdtClear();
      retry--;
      if (retry < 0)
      {
        displayError(owclient.getError());
        reboot();
        while (1);
      }
      delay(5000);
    }

    String urlf = owclient.buildUrlForecast(OWM_KEY, OWM_LOCATION);
    Serial.println(urlf);
    wget(urlf, 80, data);
    Serial.println("data retrieved:");
    Serial.println(data);
    if (!owclient.updateForecast(owfdata[0], data, 0))
    {
      displayError(owclient.getError());
      reboot();
      while (1);
    }
    if (!owclient.updateForecast(owfdata[1], data, 2))
    {
      displayError(owclient.getError());
      reboot();
      while (1);
    }
    if (!owclient.updateForecast(owfdata[2], data, 4))
    {
      displayError(owclient.getError());
      reboot();
      while (1);
    }

    switch (lastbutton)
    {
      case 1:
        displayAllWeather(owcdata, owfdata, 3);
        break;
      case 2:
        displayCurrentConditions(owcdata);
        break;
      case 3:
        displaySunMoon(owcdata);
        break;
    }
  }

  if (button == 0) {
    return;
  }

  Serial.print("Button "); Serial.print(button); Serial.println(" pressed");

  if (button == 1) {
    displayAllWeather(owcdata, owfdata, 3);
    lastbutton = button;
  }
  if (button == 2) {
    //displayForecast(owcdata,owfdata,3);
    displayCurrentConditions(owcdata);
    lastbutton = button;
  }
  if (button == 3) {
    displaySunMoon(owcdata);
    lastbutton = button;
  }

  // wait until button is released
  while (readButtons()) {
    delay(10);
  }

}
Serial shows it reaching "Connecting to WiFi..." before rebooting.

I put 'wdtClear();' in every loop with a delay that I found, in the main loop, and in the wifi connection function. I'll keep looking over ti to see if I missed something, but I'm leaning towards it not working with a wifi connection due to times involved.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Metro M4 Airlift Lite Sleep?

Post by User_UMjT7KxnxP8YN8 »

If you sit in a loop waiting for any period of time you should include a call to wdtClear() in your loop. Otherwise the WDT times out and reboots.

Locating every place you need to do this in a large existing application can be tedious but it can be done. When I did it, I developed a WDT interrupt handler that stored the PC at the time of the WDT interrupt to non-initialized RAM so I could recover and print it after rebooting. Time doesn't permit me to go search for that just now, but I thought that perhaps knowing it can be done would be useful to you.

Incidentally, I run this on a device that I leave running 24/7 for months (actually 2+ years and running). Note that if you use a Serial Monitor in your sketch this will be a problem for you, as you'll have to restart it (or a terminal emulator) after the WDT reboot to get past a wait for the serial port to open in setup(). Otherwise your program will wait in setup() forever.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Metro M4 Airlift Lite Sleep?

Post by User_UMjT7KxnxP8YN8 »

Oh, I just realized that you're probably using the Adafruit ESP32 WiFi library. That's likely to be what's breaking it, as you suspected.

Try this... make your WiFi connection FIRST, THEN initialize the WDT.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

It makes the wifi connection once every 15 minutes to download a little bit of data and display it. I don't see a way to stop the WDT completely. I suppose I could have it reboot every 15 minutes instead. I'll give that some thought.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Metro M4 Airlift Lite Sleep?

Post by User_UMjT7KxnxP8YN8 »

You can set the WDT to a longer period by using WDT_CONFIG_PER_CYC16384 instead of WDT_CONFIG_PER_CYC8192 in wdtInit(). Or you could leave it at 8 seconds and increase it to 16 when reconnecting, or you could disable the WDT when reconnecting.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

Ah, thanks. I've been looking into WDT for two different projects on different chips, and was thinking 8 sec was the max on this chip. Must have been the other one.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

I keep adding wdtClear();s to new parts of it and it's making it further and further through the code. The wifi was able to successfully connect after changing it to a 16 second timeout.

I have it working pretty decently right now, but I'm going to leave it connected to the comm port for a while before I move it to my desk and call it good.

Thanks for the help.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Metro M4 Airlift Lite Sleep?

Post by User_UMjT7KxnxP8YN8 »

You're very welcome. Glad to hear you're making progress.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

Well, I moved it to battery power with 7000mAh worth of lipos and two days later I think it killed both lipos by over draining them. I had, apparently wrongly, assumed that the 5-7v barrel jack would cut off if it got below 5v. My two new lipos are down to 1.6v and 1.2v, which I believe means it's time to recycle them after one use. Guess I learned something at least.

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: Metro M4 Airlift Lite Sleep?

Post by adafruit_support_mike »

Do you have one of our LiPo chargers?

The MCP738x series of chargers have a ‘preconditioning’ stage that trickle-charges low voltage cells until they reach 2.9V.

You can also try charging them from a 3.3V source through a 100k resistor. It only takes about seventeen electrons to get a LiPo up to 2.9V, and sub-1mA charging at 3.3V is safe enough that you can DIY the circuit. You can even bypass the protection circuit and charge the cell directly under that specific set of conditions.

At worst, the cells will still refuse to charge and you won’t be any worse off.

User avatar
asyork
 
Posts: 43
Joined: Wed Mar 24, 2021 7:07 am

Re: Metro M4 Airlift Lite Sleep?

Post by asyork »

I do, but it only has a standard lipo connector and the the lipos I over discharged are 18650s. I connected them to my Nitecore Digicharger in low mode after seeing your comment and it seems to be charging them slowly. They may be salvageable.

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: Metro M4 Airlift Lite Sleep?

Post by adafruit_support_mike »

Glad to hear it!

Over-discharging LiPos isn’t fatal as long as you don’t make it a habit. The major risk is that charging at less than about 2.9V can make metallic lithium plate out of the electrolyte/polymer. If that happens long enough you can get a solid metal path from one battery plate to the other, which equals boom. A few passes through a low-current precharge stage are survivable though.

To avoid overdischarge, you might want to use a 3.3V voltage monitor:

https://www.adafruit.com/product/3428

You can use that to build a circuit that disconnects the LiPos before theyir voltage drops too low.

Locked
Please be positive and constructive with your questions and comments.

Return to “Metro, Metro Express, and Grand Central Boards”