LED Clock – Part 2

In part 1 of this series I demonstrated how I created a clock using my 64×32 LED panel. In this blog I will expand upon this experiment and add current weather observations as a scrolling ticker across the bottom of the panel.

The first step to add weather observations to the clock is to register with Open Weather Map (openweathermap.org) and obtain an API key. Registration is free, and the free access API is sufficient for the needs of the clock.

You will also need to download and install the pyOWM module from pypi.org/project/pyowm, so the clock can query Open Weather Map for weather observations.

I created a config.py file to contain the OWM API key, as well as the ID for the city for which I want the weather observations. I used the command import config to merge the contents of the config.py file with the clock code. The contents of the config.py file looks like this.

# list of city Ids
# http://bulk.openweathermap.org/sample/
weather_city = 4138106
weather_api_key = '1234567890abcdef'
# number of minutes between weather updates
weather_update_rate = 15

Because the free OWM API only allows a limited number of API calls (60/min), I have limited the update of the weather observations to every 15 minutes. You can adjust this number as you wish, but I found that the observations don’t change that quickly and this value won’t exceed your allotted calls.

The next step was to create a get_weather() function to obtain the weather observations and return them as a formatted string to the run() loop. The function is straightforward and is listed below.

I also created a helper function, get_wind_direction(), to translate the degree data returned from OWM, into meaningful compass directions.

def get_weather():
    """
    This function reads the weather API key and city of interest from the config.py file.
    Specific weather info to display is extracted from the weather observations object. 
    """
    weather_string = ''
    weather_api_key = config.weather_api_key
    weather_city = config.weather_city
    weather_client = pyowm.OWM(weather_api_key)
    observations = weather_client.weather_at_id(config.weather_city)
    weather = observations.get_weather()
    loc = observations.get_location().get_name()
    
    # get weather
    temp_f = weather.get_temperature('fahrenheit')['temp']
    temp_c = weather.get_temperature('celsius')['temp']
    wind = weather.get_wind(unit='miles_hour')['speed']
    wind_dir = get_wind_direction(weather.get_wind()['deg'])
    humidity = weather.get_humidity()
    status = weather.get_detailed_status()
    
    # get forecast
    forecaster = weather_client.three_hours_forecast(loc).get_forecast()
    forecast = forecaster.get_weathers()[0].get_detailed_status()
    
    # build weather scrolling text
    weather_str = '{} weather: {}f  {}c  {}% hum  {}@{}mph  {}  Forecast: {}'.format(loc, int(temp_f), int(temp_c),
                                                                          int(humidity), wind_dir, int(wind),
                                                                          status, forecast)
    print('weather updated @ {}'.format(datetime.datetime.now()))   
    return weather_str

def get_wind_direction(deg):
    """
    Return the cardinal directions for the wind
    """
    if 0 < int(deg) <= 23 or 338 >= int(deg):
        return 'N'
    elif 23 < int(deg) <= 68:
        return 'NE'
    elif 68 < int(deg) <= 113:
        return 'E'
    elif 113 < int(deg) <= 158:
        return 'SE'
    elif 158 < int(deg) <= 203:
        return 'S'
    elif 203 < int(deg) <= 248:
        return 'SE'
    elif 248 < int(deg) <= 293:
        return 'W'
    else:
        return 'NW'

The final step to was to call get_weather() during the main clock loop, but only according to the config.weather_update_rate.

def run(matrix):
    """
    Run the clock.
    """
    # setup canvas
    canvas = matrix.CreateFrameCanvas()
    
    # fill it with black
    canvas.Fill(0, 0, 0)
    
    # setup the fonts for the clock
    font = graphics.Font()
    font.LoadFont("../rpi-rgb-led-matrix/fonts/5x7.bdf")
    time_font = graphics.Font()
    time_font.LoadFont("../rpi-rgb-led-matrix/fonts/7x13.bdf")
    
    # text will be yellow
    textColor = graphics.Color(255, 235, 59)

    # set initial values
    now = datetime.datetime.now()
    weather_string = 'No weather data.'
    date_string = now.strftime('%b %d, %Y')
    time_string = now.strftime('%H:%M:%S')   
    big_x = 64
    last_switch = now
    last_fetch = now
    show_dow = False
    
    # setup for first run
    init = True     
    
    while True:
        
        # get the current time
        now = datetime.datetime.now()

        # fetch weather every X minutes
        if init or (now - last_fetch).seconds > config.weather_update_rate * 60:
            weather_string = get_weather()
            last_fetch = datetime.datetime.now()
            
        # switch dow and date every X sec
        if (now - last_switch).seconds > config.face_flip_rate:
            last_switch = datetime.datetime.now()
            if show_dow == True:
                show_dow = False
            else:
                show_dow = True
            
        # display clock    
        if show_dow == False:
            now = datetime.datetime.now()  # so seconds tick
            date_string = now.strftime('%A')
            time_string = now.strftime('%-I:%M %p')
        else:
            date_string = now.strftime('%b %d, %Y')
            time_string = now.strftime('%H:%M:%S')
            
        # switch off init mode after through above code once
        init = False
        
        # fill convas with black
        canvas.Fill(0, 0, 0)
        
        # calculate element positions
        time_pos = int((64 - len(time_string) * 7) / 2)
        date_pos = int((64 - len(date_string) * 5) / 2)
        
        # put elements on canvas
        graphics.DrawText(canvas, time_font, time_pos, 11, textColor, time_string)
        graphics.DrawText(canvas, font, date_pos, 20, textColor, date_string)
        graphics.DrawText(canvas, font, big_x, 30, textColor, weather_string)

        # calculate scroll horizontal position
        big_x = big_x - 1
        if big_x < len(weather_string) * -5:
            big_x = 64
        
        # display the clock
        canvas = matrix.SwapOnVSync(canvas)
        
        # so you can read the scrolling message
        time.sleep(.07)

Notice there is now an additional graphics.DrawText() call to place the weather_string on the canvas. Also notice the y coordinates of each graphics.DarwText() command have been tweaked slightly to make room for the scrolling text. Each time the loop runs, the scrolling text is placed at the big_x coordinate on the canvas. big_x is reduced by the width of one character (5) each time the loop runs. The loop runs so fast that without a short sleep() it would scroll by too fast to read.

Here’s what the clock looks like with the scrolling weather.

The code for this blog series can be downloaded from GitHub.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.