Breakout Weather Dark Sky convert to Open Meteo

Trying to convert weather.py to work with Open Meteo as Dark Sky API no longer exists.

Got so far, by getting “ValueError: Location should be a string” error.

What have I missed, broken etc?
Thank you.
N.B., Much of the commented-out code is the old Dark Sky code.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import time
import datetime
import glob
import logging
import sys

# try:
import requests
import geocoder
#     import lxml
#     from bs4 import BeautifulSoup
#    from PIL import Image
#     from PIL import ImageFont
#     from PIL import ImageDraw
# except ImportError:
#     print("""
# This script requires several modules to run correctly.
# Install with:
# sudo pip install requests geocoder beautifulsoup4
# sudo apt install python{v}-lxml python{v}-pil
# """.format(v="" if sys.version_info.major == 2 else sys.version_info.major))
#     sys.exit(1)

# import bme680
# from luma.core.interface.serial import spi
# from luma.core.error import DeviceNotFoundError
# from luma.oled.device import sh1106




TEMPERATURE_UPDATE_INTERVAL = 0.1  # in seconds

# Default to Sheffield-on-Sea for location
CITY = "Sheffield"
COUNTRYCODE = "GB"

# Used to calibrate the sensor
TEMP_OFFSET = 0.0


logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"))


print("""This Pimoroni Breakout Garden example requires a
BME680 Environmental Sensor Breakout and a 1.12" OLED Breakout.
This example turns your Breakout Garden into a mini weather display
combining indoor temperature and pressure data with a weather icon
indicating the current local weather conditions.
Press Ctrl+C a couple times to exit.
""")


# Convert a city name and country code to latitude and longitude
def get_coords(address):
    g = geocoder.arcgis(address)
    coords = g.latlng
    logging.info("Location coordinates: %s", coords)
    return coords


###### Query Dark Sky (https://darksky.net/) to scrape current weather data
###### def get_weather(coords):
######    weather = {}
######    try:
######        res = requests.get("https://darksky.net/forecast/{}/uk212/en".format(","
######                           .join([str(c) for c in coords])))
######        if res.status_code == 200:
######            soup = BeautifulSoup(res.content, "lxml")
######            curr = soup.find("span", "currently")
######            if curr:
######                img_name = curr.img["alt"].split()[0]
######                logging.info("Weather summary: %s", img_name)
######                weather["summary"] = img_name
######    except requests.exceptions.RequestException as e:
######        logging.error("Could not get weather data from DarkSky: {}".format(e))
######        pass
######
######    return weather


def get_weather(coords):
    # Get the Inky Open Meteo weather data for the given location
    location_string = "{city}, {countrycode}".format(city=CITY, countrycode=COUNTRYCODE)
    weather = get_weather(location_string)


# Query OpenMeteo (https://open-meteo.com) to get current weather data
def get_weather(address):
    coords = get_coords(address)
    weather = {}
    res = requests.get("https://api.open-meteo.com/v1/forecast?latitude=" + str(coords[0]) + "&longitude=" + str(coords[1]) + "&current_weather=true")
    if res.status_code == 200:
        j = json.loads(res.text)
        current = j["current_weather"]
        weather["temperature"] = current["temperature"]
        weather["windspeed"] = current["windspeed"]
        weather["weathercode"] = current["weathercode"]
        return weather
    else:
        return weather

###### This maps the weather summary from Dark Sky
###### to the appropriate weather icons
###### icon_map = {
######     "snow": ["snow", "sleet"],
######     "rain": ["rain"],
######     "cloud": ["fog", "cloudy", "partly-cloudy-day", "partly-cloudy-night"],
######     "sun": ["clear-day", "clear-night"],
######     "storm": [],
######     "wind": ["wind"]
###### }

# Inky:  This maps the weather code from Open Meteo
# Inky:  to the appropriate weather icons
# Inky:  Weather codes from https://open-meteo.com/en/docs
icon_map = {
    "snow": [71, 73, 75, 77, 85, 86],
    "rain": [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82],
    "cloud": [1, 2, 3, 45, 48],
    "sun": [0],
    "storm": [95, 96, 99],
    "wind": []
}



# Pre-load icons into a dictionary with PIL
icons = {}

for icon in glob.glob("icons/*.png"):
    icon_name = icon.split("/")[1].replace(".png", "")
    icon_image = Image.open(icon)
    icons[icon_name] = icon_image


location_string = "{city}, {countrycode}".format(city=CITY,
                                                 countrycode=COUNTRYCODE)
coords = get_coords(location_string)


def get_weather_icon(weather):
    if weather:
        summary = weather["summary"]

        for icon in icon_map:
            if summary in icon_map[icon]:
                logging.info("Weather icon: %s", icon)
                return icons[icon]
        logging.error("Could not determine icon for weather")
        return None
    else:
        logging.error("No weather information provided to get icon")
        return None


# Get initial weather data for the given location
weather_icon = get_weather_icon(get_weather(coords))


# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)

# Set up BME680 sensor
sensor = bme680.BME680()

sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_temp_offset(TEMP_OFFSET)

# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
                                       'Roboto-Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
                                       'Roboto-Black.ttf'))
rr_24 = ImageFont.truetype(rr_path, 24)
rb_20 = ImageFont.truetype(rb_path, 20)
rr_12 = ImageFont.truetype(rr_path, 12)

# Fetch sensor dating first so that device settings take effect
sensor.get_sensor_data()
# Initial values
low_temp = sensor.data.temperature
high_temp = sensor.data.temperature
curr_date = datetime.date.today().day

last_checked = time.time()

# Main loop
while True:
    # Limit calls to Dark Sky to 1 per minute
    if time.time() - last_checked > 60:
        weather_icon = get_weather_icon(get_weather(coords))
        last_checked = time.time()

    # Load in the background image
    background = Image.open("images/weather.png").convert(oled.mode)

    # Place the weather icon and draw the background
    if weather_icon:
        background.paste(weather_icon, (10, 46))
    draw = ImageDraw.ImageDraw(background)

    # Gets temp. and press. and keeps track of daily min and max temp
    if sensor.get_sensor_data():
        temp = sensor.data.temperature
        press = sensor.data.pressure
        if datetime.datetime.today().day == curr_date:
            if temp < low_temp:
                low_temp = temp
            elif temp > high_temp:
                high_temp = temp
        else:
            curr_date = datetime.datetime.today().day
            low_temp = temp
            high_temp = temp

        # Write temp. and press. to image
        draw.text((8, 22), "{0:4.0f}".format(press),
                  fill="white", font=rb_20)
        draw.text((86, 12), u"{0:2.0f}°".format(temp),
                  fill="white", font=rb_20)

        # Write min and max temp. to image
        draw.text((80, 0), u"max: {0:2.0f}°".format(high_temp),
                  fill="white", font=rr_12)
        draw.text((80, 110), u"min: {0:2.0f}°".format(low_temp),
                  fill="white", font=rr_12)

    # Write the 24h time and blink the separator every second
    if int(time.time()) % 2 == 0:
        draw.text((4, 98), datetime.datetime.now().strftime("%H:%M"),
                  fill="white", font=rr_24)
    else:
        draw.text((4, 98), datetime.datetime.now().strftime("%H %M"),
                  fill="white", font=rr_24)

    # These lines display the temp. on the thermometer image
    draw.rectangle([(97, 43), (100, 86)], fill="black")
    temp_offset = 86 - ((86 - 43) * ((temp - 20) / (32 - 20)))
    draw.rectangle([(97, temp_offset), (100, 86)], fill="white")

    # Display the completed image on the OLED
    oled.display(background)

    time.sleep(TEMPERATURE_UPDATE_INTERVAL)

I’m thinking its not liking “Sheffield” ?
I live in Sydney, Nova Scotia, Canada.
If I use city_name = "Sydney" I’d get a similar error to yours with “pytz”.
If I use

city_name = "Halifax"
time_zone = "Canada/Atlantic"

All was good.

Didn’t work here. This is the error in full:

Traceback (most recent call last):
  File "weather_2.py", line 165, in <module>
    weather_icon = get_weather_icon(get_weather(coords))
  File "weather_2.py", line 97, in get_weather
    coords = get_coords(address)
  File "weather_2.py", line 63, in get_coords
    g = geocoder.arcgis(address)
  File "/usr/local/lib/python3.7/dist-packages/geocoder/api.py", line 398, in arcgis
    return get(location, provider='arcgis', **kwargs)
  File "/usr/local/lib/python3.7/dist-packages/geocoder/api.py", line 190, in get
    raise ValueError("Location should be a string")
ValueError: Location should be a string

I seem to have fixed that error, something was wrong, somewhere in here:

#def get_weather(coords):
#    # Get the Inky Open Meteo weather data for the given location
#    location_string = "{city}, {countrycode}".format(city=CITY, countrycode=COUNTRYCODE)
#    weather = get_weather(location_string)

And so on to the next issue! :-)

Traceback (most recent call last):
File “weather_2.py”, line 167, in
background = Image.open(“images/runner.png”).convert(device)
NameError: name ‘device’ is not defined

Struggling with the options for ‘device’ vs ‘oled’

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import time
import datetime
import glob
import logging
import json
from demo_opts import get_device
from font_fredoka_one import FredokaOne
from PIL import Image, ImageDraw, ImageFont



try:
    from smbus2 import SMBus
except ImportError:
    from smbus import SMBus
from bme280 import BME280


from sys import exit




try:
    import requests
except ImportError:
    exit("This script requires the requests module\nInstall with: sudo pip install requests")

try:
    import geocoder
except ImportError:
    exit("This script requires the geocoder module\nInstall with: sudo pip install geocoder")

print("""Test""")




CITY = "Sheffield"
COUNTRYCODE = "GB"
WARNING_TEMP = 25.0




# Used to calibrate the sensor
TEMP_OFFSET = 0.0


logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"))


print("""This Pimoroni Breakout Garden example requires a
BME680 Environmental Sensor Breakout and a 1.12" OLED Breakout.
This example turns your Breakout Garden into a mini weather display
combining indoor temperature and pressure data with a weather icon
indicating the current local weather conditions.
Press Ctrl+C a couple times to exit.
""")

# Convert a city name and country code to latitude and longitude
def get_coords(address):
    g = geocoder.arcgis(address)
    coords = g.latlng
    return coords
    
    
       
        
        
# Query OpenMeteo (https://open-meteo.com) to get current weather data
def get_weather(address):
    coords = get_coords(address)
    weather = {}
    res = requests.get("https://api.open-meteo.com/v1/forecast?latitude=" + str(coords[0]) + "&longitude=" + str(coords[1]) + "&current_weather=true")
    if res.status_code == 200:
        j = json.loads(res.text)
        current = j["current_weather"]
        weather["temperature"] = current["temperature"]
        weather["windspeed"] = current["windspeed"]
        weather["weathercode"] = current["weathercode"]
        return weather
    else:
        return weather        
        
        

icon_map = {
    "snow": [71, 73, 75, 77, 85, 86],
    "rain": [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82],
    "cloud": [1, 2, 3, 45, 48],
    "sun": [0],
    "storm": [95, 96, 99],
    "wind": []
}



# Pre-load icons into a dictionary with PIL
icons = {}

for icon in glob.glob("icons/*.png"):
    icon_name = icon.split("/")[1].replace(".png", "")
    icon_image = Image.open(icon)
    icons[icon_name] = icon_image


location_string = "{city}, {countrycode}".format(city=CITY,
                                                 countrycode=COUNTRYCODE)
coords = get_coords(location_string)


def get_weather_icon(weather):
    if weather:
        summary = weather["summary"]

        for icon in icon_map:
            if summary in icon_map[icon]:
                logging.info("Weather icon: %s", icon)
                return icons[icon]
        logging.error("Could not determine icon for weather")
        return None
    else:
        logging.error("No weather information provided to get icon")
        return None


# Get initial weather data for the given location
# weather_icon = get_weather_icon(get_weather(coords))

# Get the weather data for the given location
location_string = "{city}, {countrycode}".format(city=CITY, countrycode=COUNTRYCODE)
weather = get_weather(location_string)



# Set up OLED
# Launching from terminal, so don't seem to need this?
#oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)



# Initialise the BME280
bus = SMBus(1)
bme280 = BME280(i2c_dev=bus)
temperature = bme280.get_temperature()
pressure = bme280.get_pressure()
humidity = bme280.get_humidity()
print('{:05.2f}*C {:05.2f}hPa {:05.2f}%'.format(temperature, pressure, humidity))

last_checked = time.time()

# Main loop
while True:
    # Limit calls to Dark Sky to 1 per minute
    if time.time() - last_checked > 60:
        weather_icon = get_weather_icon(get_weather(coords))
        last_checked = time.time()
        device = get_device()

    # Load in the background image
    # Use random image until it works
    background = Image.open("images/runner.png").convert(device)

    # Place the weather icon and draw the background
    if weather_icon:
        background.paste(weather_icon, (10, 46))
    draw = ImageDraw.ImageDraw(background)

    # Gets temp. and press. and keeps track of daily min and max temp
    if sensor.get_sensor_data():
        temp = sensor.data.temperature
        press = sensor.data.pressure
        if datetime.datetime.today().day == curr_date:
            if temp < low_temp:
                low_temp = temp
            elif temp > high_temp:
                high_temp = temp
        else:
            curr_date = datetime.datetime.today().day
            low_temp = temp
            high_temp = temp

        # Write temp. and press. to image
        draw.text((8, 22), "{0:4.0f}".format(press),
                  fill="white", font=rb_20)
        draw.text((86, 12), u"{0:2.0f}°".format(temp),
                  fill="white", font=rb_20)

        # Write min and max temp. to image
        draw.text((80, 0), u"max: {0:2.0f}°".format(high_temp),
                  fill="white", font=rr_12)
        draw.text((80, 110), u"min: {0:2.0f}°".format(low_temp),
                  fill="white", font=rr_12)

    # Write the 24h time and blink the separator every second
    if int(time.time()) % 2 == 0:
        draw.text((4, 98), datetime.datetime.now().strftime("%H:%M"),
                  fill="white", font=rr_24)
    else:
        draw.text((4, 98), datetime.datetime.now().strftime("%H %M"),
                  fill="white", font=rr_24)

    # These lines display the temp. on the thermometer image
    draw.rectangle([(97, 43), (100, 86)], fill="black")
    temp_offset = 86 - ((86 - 43) * ((temp - 20) / (32 - 20)))
    draw.rectangle([(97, temp_offset), (100, 86)], fill="white")

    # Display the completed image on the OLED
    oled.display(background)

    time.sleep(TEMPERATURE_UPDATE_INTERVAL)

    # Limit calls to Dark Sky to 1 per minute
    if time.time() - last_checked > 60:
        weather_icon = get_weather_icon(get_weather(coords))
        last_checked = time.time()
        device = get_device() #### this line is the problem ####

    # Load in the background image
    # Use random image until it works
    background = Image.open("images/runner.png").convert(device)

the marked line is gated behind an if statement, so is only set when the if statement is true… assuming “get_device” always returns the same value, you can just move that line to right before the main loop

Moved get_device, it now stops with:

I2C device not found on address: 0x3C

Yet just a few lines before, it’s showing readings from the sensor meaning it could find the sensor device at that point.

If I run it from terminal with:

python3 weather_2.py --display sh1106 --height 128 --rotate 2 --interface spi --gpio-data-command 9 --spi-device 1

It fails with:

TypeError: argument 1 must be str, not sh1106

Which suggests to me, it’s looking for the Inky which the code was originally written for, not the 1.12" OLED

I see two commented lines in your previous post that may end up causing you problems (including your current one.
# weather_icon = get_weather_icon(get_weather(coords))
presets the weather icon that gets pasted into the graphic (which is updated at the begining of the main loop, but only pasted in if present, so minor, but might block initial display)
and
#oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
which is called from
oled.display(background)
near the end of your main loop (to output the built image to the display)… since “oled” isn’t defined elshwhere it can’t find it.

Still no luck.

Decided to park this project for a bit, hoping it’ll make more sense to me later.