adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.09k stars 1.21k forks source link

New version of CircuitPython 7.1.0 and associated new libraries seems to have broken deep sleep on MagTag #5839

Closed wavesailor closed 2 years ago

wavesailor commented 2 years ago

CircuitPython version

Adafruit CircuitPython 7.1.0 on 2021-12-28; Adafruit MagTag with ESP32S2
Board ID:adafruit_magtag_2.9_grayscale

Code/REPL

import time
import terminalio
import displayio
import adafruit_imageload
from adafruit_display_text import label
from adafruit_magtag.magtag import MagTag
from secrets import secrets

# --| USER CONFIG |--------------------------
METRIC = False  # set to True for metric units
# -------------------------------------------

# ----------------------------
# Define various assets
# ----------------------------
BACKGROUND_BMP = "/bmps/weather_bg.bmp"
ICONS_LARGE_FILE = "/bmps/weather_icons_70px.bmp"
ICONS_SMALL_FILE = "/bmps/weather_icons_20px.bmp"
ICON_MAP = ("01", "02", "03", "04", "09", "10", "11", "13", "50")
DAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
MONTHS = (
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
)
magtag = MagTag()

# ----------------------------
# Backgrounnd bitmap
# ----------------------------
magtag.graphics.set_background(BACKGROUND_BMP)

# ----------------------------
# Weather icons sprite sheet
# ----------------------------
icons_large_bmp, icons_large_pal = adafruit_imageload.load(ICONS_LARGE_FILE)
icons_small_bmp, icons_small_pal = adafruit_imageload.load(ICONS_SMALL_FILE)

# /////////////////////////////////////////////////////////////////////////

def get_data_source_url(api="onecall", location=None):
    """Build and return the URL for the OpenWeather API."""
    if api.upper() == "FORECAST5":
        URL = "https://api.openweathermap.org/data/2.5/forecast?"
        URL += "q=" + location
    elif api.upper() == "ONECALL":
        URL = "https://api.openweathermap.org/data/2.5/onecall?exclude=minutely,hourly,alerts"
        URL += "&lat={}".format(location[0])
        URL += "&lon={}".format(location[1])
    else:
        raise ValueError("Unknown API type: " + api)

    return URL + "&appid=" + secrets["openweather_token"]

def get_latlon():
    """Use the Forecast5 API to determine lat/lon for given city."""
    magtag.url = get_data_source_url(api="forecast5", location=secrets["openweather_location"])
    magtag.json_path = ["city"]
    raw_data = magtag.fetch()
    return raw_data["coord"]["lat"], raw_data["coord"]["lon"]

def get_forecast(location):
    """Use OneCall API to fetch forecast and timezone data."""
    resp = magtag.network.fetch(get_data_source_url(api="onecall", location=location))
    json_data = resp.json()
    return json_data["daily"], json_data["current"]["dt"], json_data["timezone_offset"]

def make_banner(x=0, y=0):
    """Make a single future forecast info banner group."""
    day_of_week = label.Label(terminalio.FONT, text="DAY", color=0x000000)
    day_of_week.anchor_point = (0, 0.5)
    day_of_week.anchored_position = (0, 10)

    icon = displayio.TileGrid(
        icons_small_bmp,
        pixel_shader=icons_small_pal,
        x=25,
        y=0,
        width=1,
        height=1,
        tile_width=20,
        tile_height=20,
    )

    day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
    day_temp.anchor_point = (0, 0.5)
    day_temp.anchored_position = (50, 10)

    group = displayio.Group(x=x, y=y)
    group.append(day_of_week)
    group.append(icon)
    group.append(day_temp)

    return group

def temperature_text(tempK):
    if METRIC:
        return "{:3.0f}C".format(tempK - 273.15)
    else:
        return "{:3.0f}F".format(32.0 + 1.8 * (tempK - 273.15))

def wind_text(speedms):
    if METRIC:
        return "{:3.0f}m/s".format(speedms)
    else:
        return "{:3.0f}mph".format(2.23694 * speedms)

def update_banner(banner, data):
    """Update supplied forecast banner with supplied data."""
    banner[0].text = DAYS[time.localtime(data["dt"]).tm_wday][:3].upper()
    banner[1][0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
    banner[2].text = temperature_text(data["temp"]["day"])

def update_today(data, tz_offset=0):
    """Update today info banner."""
    date = time.localtime(data["dt"])
    sunrise = time.localtime(data["sunrise"] + tz_offset)
    sunset = time.localtime(data["sunset"] + tz_offset)

    today_date.text = "{} {} {}, {}".format(
        DAYS[date.tm_wday].upper(),
        MONTHS[date.tm_mon - 1].upper(),
        date.tm_mday,
        date.tm_year,
    )
    today_icon[0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
    today_morn_temp.text = temperature_text(data["temp"]["morn"])
    today_day_temp.text = temperature_text(data["temp"]["day"])
    today_night_temp.text = temperature_text(data["temp"]["night"])
    today_humidity.text = "{:3d}%".format(data["humidity"])
    today_wind.text = wind_text(data["wind_speed"])
    today_sunrise.text = "{:2d}:{:02d} AM".format(sunrise.tm_hour, sunrise.tm_min)
    today_sunset.text = "{:2d}:{:02d} PM".format(sunset.tm_hour - 12, sunset.tm_min)

def go_to_sleep(current_time):
    """Enter deep sleep for time needed."""
    # compute current time offset in seconds
    hour, minutes, seconds = time.localtime(current_time)[3:6]
    seconds_since_midnight = 60 * (hour * 60 + minutes) + seconds
    three_fifteen = (3 * 60 + 15) * 60
    # wake up 15 minutes after 3am
    seconds_to_sleep = (24 * 60 * 60 - seconds_since_midnight) + three_fifteen
    print(
        "Sleeping for {} hours, {} minutes".format(
            seconds_to_sleep // 3600, (seconds_to_sleep // 60) % 60
        )
    )
    magtag.exit_and_deep_sleep(seconds_to_sleep)

# ===========
# U I
# ===========
today_date = label.Label(terminalio.FONT, text="?" * 30, color=0x000000)
today_date.anchor_point = (0, 0)
today_date.anchored_position = (15, 13)

city_name = label.Label(
    terminalio.FONT, text=secrets["openweather_location"], color=0x000000
)
city_name.anchor_point = (0, 0)
city_name.anchored_position = (15, 24)

today_icon = displayio.TileGrid(
    icons_large_bmp,
    pixel_shader=icons_small_pal,
    x=10,
    y=40,
    width=1,
    height=1,
    tile_width=70,
    tile_height=70,
)

today_morn_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_morn_temp.anchor_point = (0.5, 0)
today_morn_temp.anchored_position = (118, 59)

today_day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_day_temp.anchor_point = (0.5, 0)
today_day_temp.anchored_position = (149, 59)

today_night_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_night_temp.anchor_point = (0.5, 0)
today_night_temp.anchored_position = (180, 59)

today_humidity = label.Label(terminalio.FONT, text="100%", color=0x000000)
today_humidity.anchor_point = (0, 0.5)
today_humidity.anchored_position = (105, 95)

today_wind = label.Label(terminalio.FONT, text="99m/s", color=0x000000)
today_wind.anchor_point = (0, 0.5)
today_wind.anchored_position = (155, 95)

today_sunrise = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
today_sunrise.anchor_point = (0, 0.5)
today_sunrise.anchored_position = (45, 117)

today_sunset = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
today_sunset.anchor_point = (0, 0.5)
today_sunset.anchored_position = (130, 117)

today_banner = displayio.Group()
today_banner.append(today_date)
today_banner.append(city_name)
today_banner.append(today_icon)
today_banner.append(today_morn_temp)
today_banner.append(today_day_temp)
today_banner.append(today_night_temp)
today_banner.append(today_humidity)
today_banner.append(today_wind)
today_banner.append(today_sunrise)
today_banner.append(today_sunset)

future_banners = [
    make_banner(x=210, y=18),
    make_banner(x=210, y=39),
    make_banner(x=210, y=60),
    make_banner(x=210, y=81),
    make_banner(x=210, y=102),
]

magtag.splash.append(today_banner)
for future_banner in future_banners:
    magtag.splash.append(future_banner)

# ===========
#  M A I N
# ===========
print("Getting Lat/Lon...")
latlon = get_latlon()
print(secrets["openweather_location"])
print(latlon)

print("Fetching forecast...")
forecast_data, utc_time, local_tz_offset = get_forecast(latlon)

print("Updating...")
update_today(forecast_data[0], local_tz_offset)
for day, forecast in enumerate(forecast_data[1:6]):
    update_banner(future_banners[day], forecast)

print("Refreshing...")
time.sleep(magtag.display.time_to_refresh + 1)
magtag.display.refresh()
time.sleep(magtag.display.time_to_refresh + 1)

print("Sleeping...")
go_to_sleep(utc_time + local_tz_offset)
#  entire code will run again after deep sleep cycle
#  similar to hitting the reset button

Behavior

The code works BUT it does not seem to go into deep sleep so it does not take long for the battery to run flat.

I'm using the standard code from the Weather example: https://learn.adafruit.com/magtag-weather/

This all worked perfectly before I upgraded to the latest version of CircuitPython 7.1.0 and associated new libraries

adafruit-circuitpython-adafruit_magtag_2.9_grayscale-en_US-7.1.0.uf2
adafruit-circuitpython-bundle-7.x-mpy-20220105.zip

Description

No response

Additional information

These are the libraries I have installed on the MagTag

[adafruit_bitmap_font]   
[adafruit_display_shapes]
[adafruit_display_text]
[adafruit_imageload]
[adafruit_io]
[adafruit_magtag]
[adafruit_portalbase]
adafruit_datetime.mpy 
adafruit_fakerequests.mpy
adafruit_miniqr.mpy  
adafruit_pixelbuf.mpy 
adafruit_requests.mpy 
neopixel.mpy
simpleio.mpy
DavePutz commented 2 years ago

I tested this using both CP 7.0.0 and 7.1.0; monitoring with a USB power monitor. In both cases, once the script goes into deep sleep I see about 200uA of current draw. I was not able to test using a battery.

dhalbert commented 2 years ago

I also tested the program above, using 7.0.0 and 7.1.0, and got about 230uA during sleeping. I then also tested using an extremely simple deep sleep example, from https://learn.adafruit.com/deep-sleep-with-circuitpython/alarms-and-sleep#timealarm-deep-sleep-3078700-7. Again, the sleep current was about 230uA. This value is typical, and is what we first saw when deep sleep was first introduced in CircuitPython, in 6.2.0.

I am testing with a Nordic PPK2 power monitor, which simulates a battery. When I am testing, there is nothing plugged in to the USB port.

I am not sure why you are seeing increased battery consumption. @wavesailor Are you powering this through the battery connector, with nothing plugged in to the USB port, or are you using the USB port for power?

wavesailor commented 2 years ago

This is strange now - I went back and reloaded the code from the Weather App in code.py and reset it. It seems to run fine now with the original code from the Weather App.

I have my own version where I added and changed a few things which is where I first discovered the problem. So I'm loading my code into code.py and testing it out. I'll give feedback over the next few days.

PS. What do you use to measure the current draw?

dhalbert commented 2 years ago

PS. What do you use to measure the current draw?

I am using this :https://www.adafruit.com/product/5048. That's what I used for measurements and the screenshots in https://learn.adafruit.com/deep-sleep-with-circuitpython/power-consumption.

patja commented 2 years ago

I did some testing with a MagTag project using CircuitPython 7.1.1 and did not see this problem. I put an Extech 380942 clamp ammeter on the positive battery wire and saw current draw bounce around 80 - 120 mA while running, and then drop to about 2 mA while in deep sleep. I ran through several sleep/wake cycles and it never varied.

dhalbert commented 2 years ago

I am going to close this for now. @wavesailor If you can reproduce, please let us know and reopen.

dhalbert commented 2 years ago

@patja Thank you for testing!

patja commented 2 years ago

@wavesailor I noticed your code shows a different method of going into deep sleep than I had seen documented. Is there a reason you are not using the alarm module as suggested here: https://learn.adafruit.com/deep-sleep-with-circuitpython/alarms-and-sleep

Just a suggestion, but maybe try using the alarm module and see if the behavior changes.

dhalbert commented 2 years ago

magtag.exit_and_deep_sleep() is a convenience routine that ultimately calls the alarm module exit_and_deep_sleep(). I just reviewed the library code and it looks OK.

wavesailor commented 2 years ago

I redid Circuit python and libraries from scratch. This seemed to have solved my issue. Thanks