diff --git a/Inky-Calendar/modules/inkycal_weather.py b/Inky-Calendar/modules/inkycal_weather.py index dc3dde7..6f2ca63 100644 --- a/Inky-Calendar/modules/inkycal_weather.py +++ b/Inky-Calendar/modules/inkycal_weather.py @@ -3,8 +3,10 @@ """ Weather module for Inky-Calendar software. In development... -To-do: -- make locations of icons and text dynamic +The lunar phase calculation algorithm was taken from Michael Bishop +from Github after being granted permission. Thanks, Michael Bishop for your +awesome code! + Copyright by aceisace """ from __future__ import print_function @@ -13,6 +15,8 @@ from settings import * from configuration import * from PIL import Image, ImageDraw, ImageFont import arrow +import math, decimal +dec = decimal.Decimal print('Initialising weather...', end=' ') owm = pyowm.OWM(api_key, language=language) @@ -29,132 +33,260 @@ weathericons = { } -"""Split top_section into to 2 rows""" -section_height = top_section_height // 2 -section_width = (top_section_width - top_section_height) // 3 +"""Add a border to increase readability""" +border_top = int(top_section_height * 0.05) +border_left = int(top_section_width * 0.02) -"""Allocate icon sizes""" -icon_small = section_height -icon_large = top_section_height +"""Calculate size for each weather sub-section""" +row_height = (top_section_height-(border_top*2)) // 3 +coloumn_width = (top_section_width-(border_left*2)) // 7 -"""Split top section into 4 coloums""" -section1 = 0 -section2 = icon_large + (top_section_width - icon_large) // 3 * 0 -section3 = icon_large + (top_section_width - icon_large) // 3 * 1 -section4 = icon_large + (top_section_width - icon_large) // 3 * 2 +"""Calculate paddings""" +x_padding = int( (top_section_width % coloumn_width) / 2 ) +y_padding = int( (top_section_height % row_height) / 2 ) -"""Allocate positions for icons""" -weather_icon_pos = (section1, 0) -wind_icon_pos = (section2, 0) -sun_icon_pos = (section3, 0) -temperature_icon_pos = (section4, 0) -weather_description_pos = (section2, section_height) -humidity_icon_pos = (section4, section_height) +"""Allocate sizes for weather icons""" +icon_small = row_height +icon_medium = row_height * 2 -"""Allocate positions for text""" -next_to = lambda x: (x[0]+ icon_small, x[1]) -icon_offset = section_width - icon_small +"""Calculate the x-axis position of each coloumn""" +coloumn1 = x_padding +coloumn2 = coloumn1 + coloumn_width +coloumn3 = coloumn2 + coloumn_width +coloumn4 = coloumn3 + coloumn_width +coloumn5 = coloumn4 + coloumn_width +coloumn6 = coloumn5 + coloumn_width +coloumn7 = coloumn6 + coloumn_width -wind_pos = next_to(wind_icon_pos) -temperature_pos = next_to(temperature_icon_pos) -sun_pos = next_to(sun_icon_pos) -humidity_pos = next_to(humidity_icon_pos) -weather_pos = (section2, section_height) +"""Calculate the y-axis position of each row""" +row1 = y_padding +row2 = row1 + row_height +row3 = row2 + row_height +"""Allocate positions for current weather details""" +text_now_pos = (coloumn1, row1) +weather_icon_now_pos = (coloumn1, row2) + +temperature_icon_now_pos = (coloumn2, row1) +temperature_now_pos = (coloumn2+icon_small, row1) +humidity_icon_now_pos = (coloumn2, row2) +humidity_now_pos = (coloumn2+icon_small, row2) +windspeed_icon_now_pos = (coloumn2, row3) +windspeed_now_pos = (coloumn2+icon_small, row3) + +moon_phase_now_pos = (coloumn3, row1) +sunrise_icon_now_pos = (coloumn3, row2) +sunrise_time_now_pos = (coloumn3+icon_small, row2) +sunset_icon_now_pos = (coloumn3, row3) +sunset_time_now_pos = (coloumn3+ icon_small, row3) + +"""Allocate positions for weather forecast after 3 hours""" +text_3h_pos = (coloumn4, row1) +icon_3h_pos = (coloumn4, row2) +temperature_3h_pos = (coloumn4, row3) + +"""Allocate positions for weather forecast after 6 hours""" +text_6h_pos = (coloumn5, row1) +icon_6h_pos = (coloumn5, row2) +temperature_6h_pos = (coloumn5, row3) + +"""Allocate positions for weather forecast after 9 hours""" +text_9h_pos = (coloumn6, row1) +icon_9h_pos = (coloumn6, row2) +temperature_9h_pos = (coloumn6, row3) + +"""Allocate positions for weather forecast after 12 hours""" +text_12h_pos = (coloumn7, row1) +icon_12h_pos = (coloumn7, row2) +temperature_12h_pos = (coloumn7, row3) + +"""Windspeed (m/s) to beaufort (index of list) conversion""" +windspeed_to_beaufort = [0.02, 1.5, 3.3, 5.4, 7.9, 10.7, 13.8, 17.1, 20.7, + 24.4, 28.4, 32.6, 100] + +"""Function to convert tempertures from kelvin to celcius or fahrenheit""" +def to_units(kelvin): + if units == 'metric': + conversion = str(int(kelvin - 273.15)) + '°C' + else: + conversion = str(int((kelvin - 273.15) * 9/5 + 32)) + 'F' + return conversion + +"""Function to convert time objects to specified format 12/24 hours""" +"""Simple means just the hour and if 12 hours, am/pm as well""" +def to_hours(datetime_object, simple = False): + if hours == '24': + if simple == True: + converted_time = datetime_object.format('H') + else: + converted_time = datetime_object.format('HH:mm') + else: + if simple == True: + converted_time = datetime_object.format('H a') + else: + converted_time = datetime_object.format('hh:mm') + return str(converted_time) #def main(): +try: + """Connect to Openweathermap API and fetch weather data""" + if top_section == "Weather" and api_key != "" and owm.is_API_online() is True: + #try: + print("Fetching weather data from openweathermap API...",end = ' ') + current_weather_setup = owm.weather_at_place(location) + weather = current_weather_setup.get_weather() -"""Connect to Openweathermap API and fetch weather data""" -if top_section == "Weather" and api_key != "" and owm.is_API_online() is True: - try: - print("Fetching weather data from openweathermap...",end = ' ') - observation = owm.weather_at_place(location) + """Set-up and get weather forecast data""" + forecast = owm.three_hours_forecast(location) print("Done") - weather = observation.get_weather() - weathericon = weather.get_weather_icon_name() - Humidity = str(weather.get_humidity()) - cloudstatus = str(weather.get_clouds()) - weather_description = (str(weather.get_detailed_status())) - - """Add the icons at the correct positions""" - print('Adding weather info and icons to the image...', end = ' ') - write_text(icon_small, icon_small, '\uf055', temperature_icon_pos, - font = w_font, adapt_fontsize = True) # Temperature icon - - write_text(icon_large, icon_large, weathericons[weathericon], - weather_icon_pos, font = w_font, adapt_fontsize = True) # Weather icon - - write_text(icon_small, icon_small, '\uf07a', humidity_icon_pos, font = w_font, - adapt_fontsize = True) #Humidity icon - - write_text(icon_small,icon_small, '\uf050', wind_icon_pos, font = w_font, - adapt_fontsize = True) #Wind icon - - """Format and write the temperature and windspeed""" - if units == "metric": - Temperature = str(int(weather.get_temperature(unit='celsius')['temp'])) - windspeed = str(int(weather.get_wind()['speed'])) - - write_text(icon_offset, section_height, Temperature+'°C', temperature_pos) - - write_text(icon_offset,section_height, windspeed+" km/h", wind_pos) - - else: - Temperature = str(int(weather.get_temperature('fahrenheit')['temp'])) - windspeed = str(int(weather.get_wind()['speed']*0.621)) - - write_text(icon_offset, section_height, Temperature+' F', temperature_pos) - - write_text(icon_offset,section_height, windspeed+" mph", wind_pos) - - """write the humidity at the given position""" - write_text(icon_offset, section_height, Humidity+'%', humidity_pos) - + """Round the hour to the nearest multiple of 3""" now = arrow.now(tz=get_tz()) - sunrise = arrow.get(weather.get_sunrise_time()).to(get_tz()) - sunset = arrow.get(weather.get_sunset_time()).to(get_tz()) + hour_gap = (now.hour % 3) - """Add the sunrise/sunset icon and display the time""" - if (now <= sunrise and now <= sunset) or (now >= sunrise and now >= sunset): - write_text(icon_small, icon_small, '\uf051', sun_icon_pos, font = w_font, - adapt_fontsize = True) - if hours == "24": - write_text(icon_offset, section_height, sunrise.format('H:mm'), sun_pos) - else: - write_text(icon_offset, section_height, sunrise.format('h:mm'), sun_pos) - else: - write_text(icon_small, '\uf052', sun_icon_pos, font = w_font, - adapt_fontsize = True) - if hours == "24": - write_text(icon_offset, section_height, sunset.format('H:mm'), sun_pos) - else: - write_text(icon_offset, section_height, sunset.format('h:mm'), sun_pos) + """Prepare timings for forecasts""" + in_3h = now.replace(hours = + hour_gap + 3) + in_6h = now.replace(hours = + hour_gap + 6) + in_9h = now.replace(hours = + hour_gap + 9) + in_12h = now.replace(hours = + hour_gap + 12) + + """Prepare forecast objects for the specified timings""" + forecast_3h = forecast.get_weather_at(in_3h.datetime) + forecast_6h = forecast.get_weather_at(in_6h.datetime) + forecast_9h = forecast.get_weather_at(in_9h.datetime) + forecast_12h = forecast.get_weather_at(in_12h.datetime) + + """Get the current temperature and forcasts temperatures""" + temperature_now = to_units(weather.get_temperature()['temp']) + temperature_3h = to_units(forecast_3h.get_temperature()['temp']) + temperature_6h = to_units(forecast_6h.get_temperature()['temp']) + temperature_9h = to_units(forecast_9h.get_temperature()['temp']) + temperature_12h = to_units(forecast_12h.get_temperature()['temp']) + + """Get current and forecast weather icon names""" + weather_icon_now = weather.get_weather_icon_name() + weather_icon_3h = forecast_3h.get_weather_icon_name() + weather_icon_6h = forecast_6h.get_weather_icon_name() + weather_icon_9h = forecast_9h.get_weather_icon_name() + weather_icon_12h = forecast_12h.get_weather_icon_name() + + """Parse current weather details""" + sunrise_time_now = arrow.get(weather.get_sunrise_time()).to(get_tz()) + sunset_time_now = arrow.get(weather.get_sunset_time()).to(get_tz()) + humidity_now = str(weather.get_humidity()) + cloudstatus_now = str(weather.get_clouds()) + weather_description_now = str(weather.get_detailed_status()) + windspeed_now = weather.get_wind(unit='meters_sec')['speed'] + + beaufort = str([windspeed_to_beaufort.index(_) for _ in windspeed_to_beaufort + if windspeed_now < _][0]) - """Add a short weather description""" - write_text(section2+section3-icon_offset, section_height, - weather_description, weather_pos) + """Calculate the moon phase""" + def get_moon_phase(): + diff = now - arrow.get(2001, 1, 1) + days = dec(diff.days) + (dec(diff.seconds) / dec(86400)) + lunations = dec("0.20439731") + (days * dec("0.03386319269")) + position = lunations % dec(1) + index = math.floor((position * dec(8)) + dec("0.5")) + return {0: '\uf095',1: '\uf099',2: '\uf09c',3: '\uf0a0', + 4: '\uf0a3',5: '\uf0a7',6: '\uf0aa',7: '\uf0ae' }[int(index) & 7] + + moonphase = get_moon_phase() + + print('Adding weather details on the image...', end = ' ') + + """Add weather details in column 1""" + write_text(coloumn_width, row_height, 'now', text_now_pos) + write_text(icon_medium, icon_medium, weathericons[weather_icon_now], + weather_icon_now_pos, font = w_font, adapt_fontsize = True) + + """Add weather details in column 2""" + write_text(icon_small, icon_small, '\uf053', temperature_icon_now_pos, + font = w_font, adapt_fontsize = True) + write_text(icon_small, icon_small, '\uf07a', humidity_icon_now_pos, + font = w_font, adapt_fontsize = True) + write_text(icon_small, icon_small, '\uf050', windspeed_icon_now_pos, + font = w_font, adapt_fontsize = True) + + write_text(coloumn_width-icon_small, row_height, + temperature_now, temperature_now_pos) + write_text(coloumn_width-icon_small, row_height, humidity_now+'%', + humidity_now_pos) + write_text(coloumn_width-icon_small, row_height, beaufort, + windspeed_now_pos) + + """Add weather details in column 3""" + write_text(coloumn_width, row_height, moonphase , moon_phase_now_pos, + font = w_font, adapt_fontsize = True) + write_text(icon_small, icon_small, '\uf051', sunrise_icon_now_pos, + font = w_font, adapt_fontsize = True) + write_text(icon_small, icon_small, '\uf052', sunset_icon_now_pos, + font = w_font, adapt_fontsize = True) + + write_text(coloumn_width-icon_small, row_height, to_hours(sunrise_time_now), + sunrise_time_now_pos) + write_text(coloumn_width-icon_small, row_height, to_hours(sunset_time_now), + sunset_time_now_pos) + + """Add weather details in column 4""" + write_text(coloumn_width, row_height, to_hours(in_3h, simple=True), + text_3h_pos) + write_text(coloumn_width, row_height, weathericons[weather_icon_3h], + icon_3h_pos, font = w_font, adapt_fontsize = True) + write_text(coloumn_width, row_height, temperature_3h, + temperature_3h_pos) + + """Add weather details in column 5""" + write_text(coloumn_width, row_height, to_hours(in_6h, simple=True), + text_6h_pos) + write_text(coloumn_width, row_height, weathericons[weather_icon_6h], + icon_6h_pos, font = w_font, adapt_fontsize = True) + write_text(coloumn_width, row_height, temperature_6h, + temperature_6h_pos) + + """Add weather details in column 6""" + write_text(coloumn_width, row_height, to_hours(in_9h, simple=True), + text_9h_pos) + write_text(coloumn_width, row_height, weathericons[weather_icon_9h], + icon_9h_pos, font = w_font, adapt_fontsize = True) + write_text(coloumn_width, row_height, temperature_9h, + temperature_9h_pos) + + """Add weather details in column 7""" + write_text(coloumn_width, row_height, to_hours(in_12h, simple=True), + text_12h_pos) + write_text(coloumn_width, row_height, weathericons[weather_icon_12h], + icon_12h_pos, font = w_font, adapt_fontsize = True) + write_text(coloumn_width, row_height, temperature_12h, + temperature_12h_pos) + + + """Add seperators between section4 and section7""" + draw = ImageDraw.Draw(image) + line_start_y = int(top_section_height*0.1) + line_end_y = int(top_section_height*0.9) + + draw.line((coloumn4, line_start_y, coloumn4, line_end_y), fill='black') + draw.line((coloumn5, line_start_y, coloumn5, line_end_y), fill='black') + draw.line((coloumn6, line_start_y, coloumn6, line_end_y), fill='black') + draw.line((coloumn7, line_start_y, coloumn7, line_end_y), fill='black') + draw.line((0, top_section_height-border_top, top_section_width-border_left, + top_section_height-border_top), fill='red', width=3) print('Done'+'\n') - - """Show the fetched weather data""" - print("Today's weather report: The current Temperature is {0}°C. The " - "relative humidity is {1} %. The current windspeed is {2} km/h. " - "The sunrise today was at {3}. The sunset is at {4}. The weather can " - "be described with: {5}".format(Temperature, Humidity, windspeed, - sunrise.format('H:mm'), sunset.format('H:mm'), weather_description)) - image.crop((0,0, top_section_width, top_section_height)).save('weather.png') - except Exception as e: - """If no response was received from the openweathermap - api server, add the cloud with question mark""" - print('__________OWM-ERROR!__________') - print('Reason: ',e) - write_text(icon_large, icon_large, '\uf07b', weather_icon_pos, - font = w_font, adapt_fontsize = True) - pass +except Exception as e: + """If no response was received from the openweathermap + api server, add the cloud with question mark""" + print('__________OWM-ERROR!__________') + print('Reason: ',e) + write_text(icon_medium, icon_medium, '\uf07b', weather_icon_now_pos, + font = w_font, adapt_fontsize = True) + message = 'No internet connectivity or API timeout' + write_text(coloumn_width*6, row_height, message, humidity_icon_now_pos) + pass #if __name__ == '__main__':