Pico : IndexError: list index out of range

Trying to add ‘day_length’ to the sunrise/sunset example in Pico LCD GFX
I can get day_length printed in the terminal, just not on the LCD.

import WIFI_CONFIG
import time
from math import radians, sin, cos
from random import randint
from gfx_pack import GfxPack
from network_manager import NetworkManager
import ntptime
import urequests
import uasyncio
import machine


# Helper class so that the different formats of time can be converted into one comparable format
# Makes up for the lack of a datetime module in MicroPython
class TimeObj:
    def __init__(self):
        self.secs = 0
        self.mins = 0
        self.hours = 0
        self.PM = False

    # Returns time variables as a tuple (h, m, s)
    def get_time(self):
        pm_hrs = 0
        if self.PM:
            pm_hrs = 12
        return (self.hours + pm_hrs, self.mins, self.secs)

    # Returns time variables as a single string
    def get_str(self):
        h, m, s = self.get_time()
        return "{0:02d}:{1:02d}:{2:02d}".format(h, m, s)

    # Set time variables from the sunrise-sunset API
    def parse_api(self, apiStr):
        strsplit = apiStr.split()
        if strsplit[1] == 'PM':                                            **##### <-----Line 44**
            self.PM = True
        timesplit = strsplit[0].split(':')
        self.hours = int(timesplit[0])
        self.mins = int(timesplit[1])
        self.secs = int(timesplit[2])

    # Set time variables form
    def parse_localtime(self, localtpl):
        yr, mo, dy, self.hours, self.mins, self.secs, un1, un2 = localtpl

    # Returns number of seconds since midnight
    def get_total_secs(self):
        seconds = 0
        if self.PM:
            seconds += 43200  # seconds in the first 12 hours
        seconds += (self.hours * 3600)
        seconds += (self.mins * 60)
        seconds += self.secs
        return seconds


# Instances of TimeObj helper class defined above
sunrise_obj = TimeObj()     
sunset_obj = TimeObj()
currenttime = TimeObj()
day_length_obj = TimeObj()

# Coordinates for Sheffield-on-Sea, UK
lng = -1.4659
lat = 53.3829

# Coordinates for Nnnnnnn
#lng = nnnnnnnnnnnn
#lat = nnnnnnnnnnn


# Coordinates for LA, USA
# lng = -118.2437
# lat = 34.0522
URL = 'https://api.sunrise-sunset.org/json?lat={0}&lng={1}&date=today'.format(lat, lng)
rtc = machine.RTC()

gp = GfxPack()
display = gp.display
sys_status = "Starting"
WIDTH, HEIGHT = display.get_bounds()
display.set_backlight(0.2)  # turn on the white component of the backlight

# Generate hill heights
hills1 = [randint(10, 18), randint(10, 18), randint(10, 18), randint(10, 18), randint(10, 18)]
hills2 = [randint(10, 18), randint(10, 18)]
hills3 = [randint(10, 18), randint(10, 18)]

# Sprite information for sun icon
sun = [
    [
        0b0000000100000000,
        0b0000000000000000,
        0b0010011111001000,
        0b0000100000100000,
        0b0001000000010000,
        0b0010000000001000,
        0b0010010001001000,
        0b0010000100001000,
        0b0010000000001000,
        0b0010000000001000,
        0b0001001110010000,
        0b0000100000100000,
        0b0010011111001000,
        0b0000000000000000,
        0b0000000100000000,
        0b0000000000000000
    ],
    [
        0b0000000000000000,
        0b0100000000000100,
        0b0000011111000000,
        0b0000100000100000,
        0b0001000000010000,
        0b0010010001001000,
        0b0010010001001000,
        0b1010000100001010,
        0b0010000000001000,
        0b0010010001001000,
        0b0001001110010000,
        0b0000100000100000,
        0b0000011111000000,
        0b0100000000000100,
        0b0000000000000000,
        0b0000000000000000
    ],
    [
        0b0000000100000000,
        0b0100000000000100,
        0b0010011111001000,
        0b0000100000100000,
        0b0001000000010000,
        0b0010010001001000,
        0b0010010001001000,
        0b1010000100001010,
        0b0010000000001000,
        0b0010010001001000,
        0b0001001110010000,
        0b0000100000100000,
        0b0010011111001000,
        0b0100000000000100,
        0b0000000100000000,
        0b0000000000000000
    ]
]


def get_data():
    # open the json file
    print(f'Requesting URL: {URL}')
    r = urequests.get(URL)
    # open the json data
    j = r.json()
    print(j)
    print('Data obtained!')
    r.close()
    return j



def get_sunrise():
    sun_json = get_data()
    sunrise = sun_json['results']['sunrise']
    sunrise_obj.parse_api(sunrise)
    
    sunset = sun_json['results']['sunset']
    sunset_obj.parse_api(sunset)
        
    day_length = sun_json['results']['day_length']
    day_length_obj.parse_api(day_length)                  **#### <----- Line 179**
    print('day length ')
    print(day_length)

def display_status():
    global sys_status
    display.set_pen(0)
    display.clear()
    display.set_pen(15)
    display.text(sys_status, 0, 0, WIDTH, 1)
    display.update()


def status_handler(mode, status, ip):
    # reports wifi connection status
    global sys_status
    print(mode, status, ip)
    sys_status = 'Mode: {0} Connected: {1} IP: {2}'.format(mode, status, ip)
    display_status()
    display.update()
    print('Connecting to wifi...')
    if status is not None:
        if status:
            print('Wifi connection successful!')
        else:
            print('Wifi connection failed!')


def calc_circle_points(ori_x, ori_y, r, deg):
    rads = radians(deg - 180)
    x = int(cos(rads) * r) + ori_x
    y = int(sin(rads) * r) + ori_y
    return x, y


def to_seconds(hour, mins, secs, isPM=False):
    seconds = 0
    if isPM:
        seconds += 43200  # Seconds in the first 12 hours
    seconds += (hour * 3600)
    seconds += (mins * 60)
    seconds += secs
    return seconds


def draw_hills():
    display.set_pen(12)
    for x in range(5):
        display.circle(30 * x, 64, hills1[x])
    display.set_pen(9)
    for x in range(2):
        display.circle(60 * x + 15, 64, hills2[x])
    display.set_pen(3)
    for x in range(2):
        display.circle(60 * x + 30 + 15, 64, hills3[x])


def draw_text():
    display.set_pen(15)
    #display.text("Sun Rise-Set API demo", 0, 0, WIDTH, 1)
    display.text("Sunrise: {0}".format(sunrise_obj.get_str()), 0, 8, WIDTH, 1)
    display.text("Sunset: {0}".format(sunset_obj.get_str()), 0, 16, WIDTH, 1)
    display.text("Time now: {0}".format(currenttime.get_str()), 0, 24, WIDTH, 1)
 ############   display.text("Time now: {0}".format(currenttime.get_str()), 0, 24, WIDTH, 1)

def draw_sun(sunrise, sunset, time, cycle):
    sunlight_range = sunset - sunrise
    angle = int((180 / sunlight_range) * (time - sunrise)) % 360
    pos_x, pos_y = calc_circle_points(64 - 16, 54, 50, angle)
    display.set_pen(15)
    if angle > 180:
        gp.set_backlight(0, 0, 255)
    elif angle < 90:
        r = 255
        g = ((angle / 100) * 90)
        b = 0
        gp.set_backlight(r, g, b)
    elif angle > 90:
        r = 255
        g = 100 - (((angle - 90) / 100) * 90)
        b = 0
        gp.set_backlight(r, g, b)
    for y in range(16):
        for x in range(16):
            if sun[cycle][y] & (1 << x):
                display.pixel(x + pos_x, int((y + pos_y)))


try:
    network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
    uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except Exception as e:
    print(f'Wifi connection failed! {e}')

get_sunrise()                                                                  **#####<------- Line 273**
ntptime.settime()
currenttime.parse_localtime(time.localtime())

count = 0  # Counter for animation
display.set_backlight(0.5)  # Dim white backlight to help colours show

while True:
    # Update current time class instance from RTC
    currenttime.parse_localtime(time.localtime())
    count += 1

    display.set_pen(0)
    display.clear()

    # Uncomment for the animation
    draw_sun(0, 180, count % 180, count % 3)
    draw_sun(sunrise_obj.get_total_secs(), sunset_obj.get_total_secs(), currenttime.get_total_secs(), count % 3)
    draw_hills()
    draw_text()
    display.update()
    time.sleep(0.2)

This gives me the following error - error lines highlighted in code above.

Traceback (most recent call last):
File “<stdin”>, line 273, in
File “<stdin>”, line 179, in get_sunrise
File “<stdin>”, line 44, in parse_api
IndexError: list index out of range

Thank you.

Some progress.

The API produces AM/PM times which the code looks for and removes the AM/PM, e.g., ‘sunset’: '3:55:37 PM
day_length however obviously does not have AM/PM, so the code crashes when it cannot see that included.

How to stop it looking for AM/PM on that bit of the JSON?

Or perhaps temporarily add AM/PM before it’s later removed in the process?

OK, created variable AM

AM = " AM"

Added that to the JSON object:

day_length = sun_json['results']['day_length'] + AM

Which now displays day length on the LCD:

display.text("Day Length: {0}".format(day_length_obj.get_str()), 0, 32, WIDTH, 1)

Next step is to make it show as Xhrs Xmins and not bother with seconds.
But that’s for another day!

For the original problem, you could try changing line 44 to:
if len(strsplit) > 1 and strsplit[1] == 'PM':

I don’t understand it, but it works and is probably a far more elegant solution than mine.

Thank you!

When apiStr includes ‘PM’, separated by a space, split() splits it into two parts, strsplit[0] and strsplit[1].

If there is no ’ PM’, split() only returns one part, strsplit[0], hence the index out of range error.

The len(strsplit) counts the number of parts and effectively causes the ‘if’ to fail before it tries to access the second part if it doesn’t exist.