
| Current Path : /usr/lib/python3/dist-packages/meteo_qt/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //usr/lib/python3/dist-packages/meteo_qt/meteo_qt.py |
# Purpose: System tray weather application
# Weather data: http://openweathermap.org
# Author: Dimitrios Glentadakis dglent@free.fr
# License: GPLv3
import logging
import logging.handlers
import os
import platform
import sys
import urllib.request
from functools import partial
from socket import timeout
from lxml import etree
import json
import time
import datetime
import traceback
from io import StringIO
from PyQt5.QtCore import (
PYQT_VERSION_STR, QT_VERSION_STR, QCoreApplication, QByteArray,
QLibraryInfo, QLocale, QSettings, Qt, QThread, QTimer, QTranslator,
pyqtSignal, pyqtSlot, QTime
)
from PyQt5.QtGui import (
QColor, QCursor, QFont, QIcon, QImage, QMovie, QPainter, QPixmap,
QTransform, QTextDocument
)
from PyQt5.QtWidgets import (
QDialog, QAction, QApplication, QMainWindow, QMenu, QSystemTrayIcon, qApp,
QVBoxLayout, QHBoxLayout, QLabel, QGridLayout
)
try:
import qrc_resources
import settings
import searchcity
import conditions
import about_dlg
except ImportError:
from meteo_qt import qrc_resources
from meteo_qt import settings
from meteo_qt import searchcity
from meteo_qt import conditions
from meteo_qt import about_dlg
__version__ = "1.5"
class SystemTrayIcon(QMainWindow):
units_dico = {
'metric': '°C',
'imperial': '°F',
' ': '°K'
}
def __init__(self, parent=None):
super(SystemTrayIcon, self).__init__(parent)
self.settings = QSettings()
self.cityChangeTimer = QTimer()
self.cityChangeTimer.timeout.connect(self.update_city_gif)
self.language = self.settings.value('Language') or ''
self.temp_decimal_bool = self.settings.value('Decimal') or False
# initialize the tray icon type in case of first run: issue#42
self.tray_type = self.settings.value('TrayType') or 'icon&temp'
self.cond = conditions.WeatherConditions()
self.temporary_city_status = False
self.conditions = self.cond.trans
self.clouds = self.cond.clouds
self.wind = self.cond.wind
self.wind_dir = self.cond.wind_direction
self.wind_codes = self.cond.wind_codes
self.inerror = False
self.tentatives = 0
url_prefix = 'http://api.openweathermap.org/data/2.5'
self.baseurl = f'{url_prefix}/weather?id='
self.accurate_url = f'{url_prefix}/find?q='
self.day_forecast_url = f'{url_prefix}/forecast?id='
self.forecast6_url = f'{url_prefix}/forecast/daily?id='
self.wIconUrl = 'http://openweathermap.org/img/w/'
apikey = self.settings.value('APPID') or ''
self.appid = '&APPID=' + apikey
self.forecast_icon_url = self.wIconUrl
self.timer = QTimer(self)
self.timer.timeout.connect(self.refresh)
self.menu = QMenu()
self.citiesMenu = QMenu(self.tr('Cities'))
desktops_no_left_click = ['ubuntu', 'budgie-desktop']
if os.environ.get('DESKTOP_SESSION') in desktops_no_left_click:
# Missing left click on Unity environment issue 63
self.panelAction = QAction(
QCoreApplication.translate(
"Tray context menu",
"Toggle Window",
"Open/closes the application window"
),
self
)
self.panelAction.setIcon(QIcon(':/panel'))
self.menu.addAction(self.panelAction)
self.panelAction.triggered.connect(self.showpanel)
self.tempCityAction = QAction(self.tr('&Temporary city'), self)
self.refreshAction = QAction(
QCoreApplication.translate(
'Action to refresh the weather infos from the server',
'&Refresh',
'Systray icon context menu'
),
self
)
self.settingsAction = QAction(self.tr('&Settings'), self)
self.aboutAction = QAction(self.tr('&About'), self)
self.exitAction = QAction(self.tr('Exit'), self)
self.exitAction.setIcon(QIcon(':/exit'))
self.aboutAction.setIcon(QIcon(':/info'))
self.refreshAction.setIcon(QIcon(':/refresh'))
self.settingsAction.setIcon(QIcon(':/configure'))
self.tempCityAction.setIcon(QIcon(':/tempcity'))
self.citiesMenu.setIcon(QIcon(':/bookmarks'))
self.menu.addAction(self.settingsAction)
self.menu.addAction(self.refreshAction)
self.menu.addMenu(self.citiesMenu)
self.menu.addAction(self.tempCityAction)
self.menu.addAction(self.aboutAction)
self.menu.addAction(self.exitAction)
self.settingsAction.triggered.connect(self.config)
self.exitAction.triggered.connect(qApp.quit)
self.refreshAction.triggered.connect(self.manual_refresh)
self.aboutAction.triggered.connect(self.about)
self.tempCityAction.triggered.connect(self.tempcity)
self.systray = QSystemTrayIcon()
self.systray.setContextMenu(self.menu)
self.systray.activated.connect(self.activate)
self.systray.setIcon(QIcon(':/noicon'))
self.systray.setToolTip(self.tr('Searching weather data...'))
self.notification = ''
self.hPaTrend = 0
self.trendCities_dic = {}
self.notifier_id = ''
self.temp_trend = ''
self.systray.show()
# The dictionnary has to be intialized here. If there is an error
# the program couldn't become functionnal if the dictionnary is
# reinitialized in the weatherdata method
self.weatherDataDico = {}
# The traycolor has to be initialized here for the case when we cannot
# reach the tray method (case: set the color at first time usage)
self.traycolor = ''
self.days_dico = {
'0': self.tr('Mon'),
'1': self.tr('Tue'),
'2': self.tr('Wed'),
'3': self.tr('Thu'),
'4': self.tr('Fri'),
'5': self.tr('Sat'),
'6': self.tr('Sun')
}
self.precipitation = self.cond.rain
self.wind_direction = self.cond.wind_codes
self.wind_name_dic = self.cond.wind
self.clouds_name_dic = self.cond.clouds
self.beaufort_sea_land = self.cond.beaufort
self.hpa_indications = self.cond.pressure
self.uv_risk = self.cond.uv_risk
self.uv_recommend = self.cond.uv_recommend
self.doc = QTextDocument()
self.create_overview()
self.city = self.settings.value('City') or ''
self.country = self.settings.value('Country') or ''
self.id_ = self.settings.value('ID') or ''
self.current_city_display = f'{self.city}_{self.country}_{self.id_}'
self.cities_menu()
self.refresh()
def create_overview(self):
self.overviewcitydlg = QDialog()
self.setCentralWidget(self.overviewcitydlg)
self.total_layout = QVBoxLayout()
# ----First part overview day -----
self.over_layout = QVBoxLayout()
self.dayforecast_layout = QHBoxLayout()
self.dayforecast_temp_layout = QHBoxLayout()
self.city_label = QLabel()
self.over_layout.addWidget(self.city_label)
self.icontemp_layout = QHBoxLayout()
self.icon_label = QLabel()
self.icontemp_layout.addWidget(self.icon_label)
self.temp_label = QLabel()
self.temp_label.setWordWrap(True)
self.icontemp_layout.addWidget(self.temp_label)
self.over_layout.addLayout(self.icontemp_layout)
self.weather_label = QLabel()
self.weather_label.setWordWrap(True)
self.icontemp_layout.addWidget(self.weather_label)
self.icontemp_layout.addStretch()
self.over_layout.addLayout(self.dayforecast_layout)
self.over_layout.addLayout(self.dayforecast_temp_layout)
# ------Second part overview day---------
self.over_grid = QGridLayout()
# Feels Like
self.feels_like_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label (For the temperature)',
'Feels like',
'Weather info panel'
)
)
)
self.feels_like_value = QLabel()
# Wind
self.wind_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label before the wind description',
'Wind',
'Weather info panel'
)
)
)
self.wind_label.setAlignment(Qt.AlignTop)
self.windLabelDescr = QLabel('None')
self.wind_icon_label = QLabel()
self.wind_icon_label.setAlignment(Qt.AlignLeft)
self.wind_icon = QPixmap(':/arrow')
# Clouds
self.clouds_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label for the cloudiness (%)',
'Cloudiness',
'Weather info panel'
)
)
)
self.clouds_name = QLabel()
# Pressure
self.pressure_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label for the pressure (hPa)',
'Pressure',
'Weather info panel'
)
)
)
self.pressure_value = QLabel()
# Humidity
self.humidity_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label for the humidity (%)',
'Humidity',
'Weather info panel'
)
)
)
self.humidity_value = QLabel()
# Precipitation
self.precipitation_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Precipitation type (no/rain/snow)',
'Precipitation',
'Weather overview dialogue'
)
)
)
self.precipitation_value = QLabel()
# Sunrise Sunset Daylight
self.sunrise_label = QLabel(
'<font color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label for the sunrise time (hh:mm)',
'Sunrise',
'Weather info panel'
)
)
)
self.sunset_label = QLabel(
'<font color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Label for the sunset (hh:mm)',
'Sunset',
'Weather info panel'
)
)
)
self.sunrise_value = QLabel()
self.sunset_value = QLabel()
self.daylight_label = QLabel(
'<font color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Daylight duration',
'Daylight',
'Weather overview dialogue'
)
)
)
self.daylight_value_label = QLabel()
# --UV---
self.uv_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Ultraviolet index',
'UV',
'Label in weather info dialogue'
)
)
)
self.uv_label.setAlignment(Qt.AlignTop)
self.uv_value_label = QLabel()
# Ozone
self.ozone_label = QLabel(
'<font size="3" color=><b>{}</b></font>'.format(
QCoreApplication.translate(
'Ozone data title',
'Ozone',
'Label in weather info dialogue'
)
)
)
self.ozone_value_label = QLabel()
self.over_grid.addWidget(self.feels_like_label, 0, 0)
self.over_grid.addWidget(self.feels_like_value, 0, 1)
self.over_grid.addWidget(self.wind_label, 1, 0)
self.over_grid.addWidget(self.windLabelDescr, 1, 1)
self.over_grid.addWidget(self.wind_icon_label, 1, 2)
self.over_grid.addWidget(self.clouds_label, 2, 0)
self.over_grid.addWidget(self.clouds_name, 2, 1)
self.over_grid.addWidget(self.pressure_label, 3, 0)
self.over_grid.addWidget(self.pressure_value, 3, 1)
self.over_grid.addWidget(self.humidity_label, 4, 0)
self.over_grid.addWidget(self.humidity_value, 4, 1, 1, 3) # align left
self.over_grid.addWidget(self.precipitation_label, 5, 0)
self.over_grid.addWidget(self.precipitation_value, 5, 1)
self.over_grid.addWidget(self.sunrise_label, 6, 0)
self.over_grid.addWidget(self.sunrise_value, 6, 1)
self.over_grid.addWidget(self.sunset_label, 7, 0)
self.over_grid.addWidget(self.sunset_value, 7, 1)
self.over_grid.addWidget(self.daylight_label, 8, 0)
self.over_grid.addWidget(self.daylight_value_label, 8, 1)
self.over_grid.addWidget(self.uv_label, 9, 0)
self.over_grid.addWidget(self.uv_value_label, 9, 1)
# # -------------Forecast-------------
self.forecast_days_layout = QHBoxLayout()
self.forecast_icons_layout = QHBoxLayout()
self.forecast_minmax_layout = QHBoxLayout()
# ----------------------------------
self.total_layout.addLayout(self.over_layout)
self.total_layout.addLayout(self.over_grid)
self.total_layout.addLayout(self.forecast_icons_layout)
self.total_layout.addLayout(self.forecast_days_layout)
self.total_layout.addLayout(self.forecast_minmax_layout)
self.overviewcitydlg.setLayout(self.total_layout)
self.setWindowTitle(self.tr('Weather status'))
def overviewcity(self):
self.forecast_weather_list = []
self.dayforecast_weather_list = []
self.icon_list = []
self.dayforecast_icon_list = []
self.unit_temp = self.units_dico[self.unit]
# ----First part overview day -----
# Check for city translation
cities_trans = self.settings.value('CitiesTranslation') or '{}'
cities_trans_dict = eval(cities_trans)
city_notrans = (
'{0}_{1}_{2}'.format(
self.weatherDataDico['City'],
self.weatherDataDico['Country'],
self.weatherDataDico['Id']
)
)
if city_notrans in cities_trans_dict:
city_label = cities_trans_dict[city_notrans]
else:
city_label = (
'{0}, {1}'.format(
self.weatherDataDico['City'],
self.weatherDataDico['Country']
)
)
self.city_label.setText(
f'<font size="4"><b>{city_label}</b></font>'
)
self.icon_label.setPixmap(self.wIcon)
self.temp_label.setText(
'<font size="5"><b>{0} {1}{2}</b></font>'.format(
'{0:.1f}'.format(float(self.weatherDataDico['Temp'][:-1])),
self.unit_temp,
self.temp_trend
)
)
self.weather_label.setText(
f'<font size="3"><b>{self.weatherDataDico["Meteo"]}</b></font>'
)
self.feels_like_value.setText(
'{0} {1}'.format(
self.weatherDataDico['Feels_like'][0],
self.weatherDataDico['Feels_like'][1]
)
)
# Wind
wind_unit = self.settings.value('Unit') or 'metric'
wind_unit_speed_config = self.settings.value('Wind_unit') or 'df'
if wind_unit_speed_config == 'bf':
self.bft_bool = True
else:
self.bft_bool = False
self.unit_system = ' m/s '
self.unit_system_wind = ' m/s '
if wind_unit == 'imperial':
self.unit_system = ' mph '
self.unit_system_wind = ' mph '
wind_speed = '{0:.1f}'.format(float(self.weatherDataDico['Wind'][0]))
windTobeaufort = str(self.convertToBeaufort(wind_speed))
if self.bft_bool is True:
wind_speed = windTobeaufort
self.unit_system_wind = ' Bft. '
if wind_unit == 'metric' and wind_unit_speed_config == 'km':
self.wind_km_bool = True
wind_speed = '{0:.1f}'.format(float(wind_speed) * 3.6)
self.unit_system_wind = QCoreApplication.translate(
'''Unit displayed after the wind speed value and before
the wind description (keep the spaces before and after)''',
' km/h ',
'Weather Infos panel'
)
else:
self.wind_km_bool = False
try:
self.windLabelDescr.setText(
'<font color=>{0} {1}° <br/>{2}{3}{4}</font>'.format(
self.weatherDataDico['Wind'][4],
self.weatherDataDico['Wind'][2],
wind_speed,
self.unit_system_wind,
self.weatherDataDico['Wind'][1]
)
)
self.windLabelDescr.setToolTip(
self.beaufort_sea_land[windTobeaufort]
)
except:
logging.error(
'Cannot find wind informations:\n{}'.format(
self.weatherDataDico['Wind']
)
)
self.wind_icon_direction()
# Clouds
self.clouds_name.setText(
f'<font color=>{self.weatherDataDico["Clouds"]}</font>'
)
# Pressure
if self.hPaTrend == 0:
hpa = "→"
elif self.hPaTrend < 0:
hpa = "↘"
elif self.hPaTrend > 0:
hpa = "↗"
self.pressure_value.setText(
'<font color=>{0} {1} {2}</font>'.format(
str(float(self.weatherDataDico['Pressure'][0])),
self.weatherDataDico['Pressure'][1],
hpa
)
)
self.pressure_value.setToolTip(self.hpa_indications['hpa'])
# Humidity
self.humidity_value.setText(
'<font color=>{0} {1}</font>'.format(
self.weatherDataDico['Humidity'][0],
self.weatherDataDico['Humidity'][1]
)
)
# Precipitation
rain_mode = (
self.precipitation[self.weatherDataDico['Precipitation'][0]]
)
rain_value = self.weatherDataDico['Precipitation'][1]
rain_unit = ' mm '
if rain_value == '':
rain_unit = ''
else:
if wind_unit == 'imperial':
rain_unit = 'inch'
rain_value = str(float(rain_value) / 25.4)
rain_value = "{0:.4f}".format(float(rain_value))
else:
rain_value = "{0:.2f}".format(float(rain_value))
self.precipitation_value.setText(
'<font color=>{0} {1} {2}</font>'.format(
rain_mode,
rain_value,
rain_unit
)
)
# Sunrise Sunset Daylight
try:
rise_str = self.utc('Sunrise', 'weatherdata')
set_str = self.utc('Sunset', 'weatherdata')
except (AttributeError, ValueError):
logging.error('Cannot find sunrise, sunset time info')
# if value is None
rise_str = '00:00:00'
set_str = '00:00:00'
self.sunrise_value.setText(
f'<font color=>{rise_str[:-3]}</font>'
)
self.sunset_value.setText(
f'<font color=>{set_str[:-3]}</font>'
)
daylight_value = self.daylight_delta(rise_str[:-3], set_str[:-3])
self.daylight_value_label.setText(
f'<font color=>{daylight_value}</font>'
)
# --UV---
fetching_text = (
'<font color=>{}</font>'.format(
QCoreApplication.translate(
'Ultraviolet index waiting text label',
'Fetching...',
'Weather info dialogue'
)
)
)
self.uv_value_label.setText(fetching_text)
# Ozone
self.ozone_value_label.setText(fetching_text)
if self.forcast6daysBool:
self.forecast6data()
else:
self.forecastdata()
self.iconfetch()
logging.debug('Fetched 6 days forecast icons')
self.dayforecastdata()
logging.debug('Fetched day forecast data')
self.dayiconfetch()
logging.debug('Fetched day forcast icons')
self.uv_fetch()
logging.debug('Fetched uv index')
self.ozone_fetch()
logging.debug('Fetched ozone data')
self.restoreGeometry(
self.settings.value(
"MainWindow/Geometry",
QByteArray()
)
)
# Option to start with the panel closed, true by defaut
# starting with the panel open can be useful for users who don't have plasma
# installed (to set keyboard shortcuts or other default window behaviours)
start_minimized = self.settings.value('StartMinimized') or 'True'
if start_minimized == 'False':
self.showpanel()
def daylight_delta(self, s1, s2):
FMT = '%H:%M'
tdelta = (
datetime.datetime.strptime(s2, FMT)
- datetime.datetime.strptime(s1, FMT)
)
m, s = divmod(tdelta.seconds, 60)
h, m = divmod(m, 60)
if len(str(m)) == 1:
m = f'0{str(m)}'
daylight_in_hours = f'{str(h)}:{str(m)}'
return daylight_in_hours
def utc(self, rise_set, what):
''' Convert sun rise/set from UTC to local time
'rise_set' is 'Sunrise' or 'Sunset' when it is for weatherdata
or the index of hour in day forecast when dayforecast'''
listtotime = ''
# Create a list ['h', 'm', 's'] and pass it to QTime
if what == 'weatherdata':
listtotime = (
self.weatherDataDico[rise_set].split('T')[1].split(':')
)
elif what == 'dayforecast':
if not self.json_data_bool:
listtotime = (
self.dayforecast_data[4][rise_set].get('from')
.split('T')[1].split(':')
)
else:
listtotime = (
self.dayforecast_data['list'][rise_set]['dt_txt'][10:]
.split(':')
)
suntime = QTime(int(listtotime[0]), int(listtotime[1]), int(
listtotime[2]))
# add the diff UTC-local in seconds
utc_time = suntime.addSecs(time.localtime().tm_gmtoff)
utc_time_str = utc_time.toString()
return utc_time_str
def convertToBeaufort(self, speed):
speed = float(speed)
if self.unit_system.strip() == 'm/s':
if speed <= 0.2:
return 0
elif speed <= 1.5:
return 1
elif speed <= 3.3:
return 2
elif speed <= 5.4:
return 3
elif speed <= 7.9:
return 4
elif speed <= 10.7:
return 5
elif speed <= 13.8:
return 6
elif speed <= 17.1:
return 7
elif speed <= 20.7:
return 8
elif speed <= 24.4:
return 9
elif speed <= 28.4:
return 10
elif speed <= 32.4:
return 11
elif speed <= 36.9:
return 12
elif self.unit_system.strip() == 'mph':
if speed < 1:
return 0
elif speed < 4:
return 1
elif speed < 8:
return 2
elif speed < 13:
return 3
elif speed < 18:
return 4
elif speed < 25:
return 5
elif speed < 32:
return 6
elif speed < 39:
return 7
elif speed < 47:
return 8
elif speed < 55:
return 9
elif speed < 64:
return 10
elif speed < 73:
return 11
elif speed <= 82:
return 12
def wind_icon_direction(self):
angle = self.weatherDataDico['Wind'][2]
if angle == '':
if self.wind_icon_label.isVisible is True:
self.wind_icon_label.hide()
return
else:
if self.wind_icon_label.isVisible is False:
self.wind_icon_label.show()
transf = QTransform()
logging.debug(f'Wind degrees direction: {angle}')
transf.rotate(int(float(angle)))
rotated = self.wind_icon.transformed(
transf, mode=Qt.SmoothTransformation
)
self.wind_icon_label.setPixmap(rotated)
def ozone_du(self, du):
if du <= 125:
return '#060106' # black
elif du <= 150:
return '#340634' # magenta
elif du <= 175:
return '#590b59' # fuccia
elif du <= 200:
return '#421e85' # violet
elif du <= 225:
return '#121e99' # blue
elif du <= 250:
return '#125696' # blue sea
elif du <= 275:
return '#198586' # raf
elif du <= 300:
return '#21b1b1' # cyan
elif du <= 325:
return '#64b341' # light green
elif du <= 350:
return '#1cac1c' # green
elif du <= 375:
return '#93a92c' # green oil
elif du <= 400:
return '#baba2b' # yellow
elif du <= 425:
return '#af771f' # orange
elif du <= 450:
return '#842910' # brown
elif du <= 475:
return '#501516' # brown dark
elif du > 475:
return '#210909' # darker brown
def uv_color(self, uv):
try:
uv = float(uv)
except:
return ('grey', 'None')
if uv <= 2.99:
return ('green', 'Low')
elif uv <= 5.99:
return ('yellow', 'Moderate')
elif uv <= 7.99:
return ('orange', 'High')
elif uv <= 10.99:
return ('red', 'Very high')
elif uv >= 11:
return ('purple', 'Extreme')
def winddir_json_code(self, deg):
deg = float(deg)
if deg < 22.5 or deg > 337.5:
return 'N'
elif deg < 45:
return 'NNE'
elif deg < 67.5:
return 'NE'
elif deg < 90:
return 'ENE'
elif deg < 112.5:
return 'E'
elif deg < 135:
return 'ESE'
elif deg < 157.5:
return 'SE'
elif deg < 180:
return 'SSE'
elif deg < 202.5:
return 'S'
elif deg < 225:
return 'SSW'
elif deg < 247.5:
return 'SW'
elif deg < 270:
return 'WSW'
elif deg < 292.5:
return 'W'
elif deg < 315:
return 'WNW'
elif deg <= 337.5:
return 'NNW'
def find_min_max(self, fetched_file_periods):
''' Collate the temperature of each forecast time
to find the min max T° of the forecast
of the day in the 4 days forecast '''
self.date_temp_forecast = {}
for element in self.dayforecast_data.iter():
if element.tag == 'time':
date_list = element.get('from').split('-')
date_list_time = date_list[2].split('T')
date_list[2] = date_list_time[0]
if element.tag == 'temperature':
if not date_list[2] in self.date_temp_forecast:
self.date_temp_forecast[date_list[2]] = []
self.date_temp_forecast[date_list[2]].append(
float(element.get('max')))
def forecast6data(self):
'''Forecast for the next 6 days'''
# Some times server sends less data
self.clearLayout(self.forecast_minmax_layout)
self.clearLayout(self.forecast_days_layout)
periods = 7
fetched_file_periods = (len(self.forecast6_data.xpath('//time')))
if fetched_file_periods < periods:
periods = fetched_file_periods
logging.warning(
'Reduce forecast for the next 6 days to {0}'.format(
periods - 1
)
)
counter_day = 0
forecast_data = False
for element in self.forecast6_data.iter():
if element.tag == 'time':
forecast_data = True
if forecast_data is False:
continue
if element.tag == 'time':
counter_day += 1
if counter_day == periods:
break
weather_end = False
date_list = element.get('day').split('-')
day_of_week = str(datetime.date(
int(date_list[0]), int(date_list[1]),
int(date_list[2])).weekday()
)
label = QLabel(f'{self.days_dico[day_of_week]}')
label.setToolTip(element.get('day'))
label.setAlignment(Qt.AlignHCenter)
self.forecast_days_layout.addWidget(label)
if element.tag == 'temperature':
mlabel = QLabel(
'<font color=>{0}°<br/>{1}°</font>'.format(
'{0:.0f}'.format(float(element.get('min'))),
'{0:.0f}'.format(float(element.get('max')))
)
)
mlabel.setAlignment(Qt.AlignHCenter)
mlabel.setToolTip(self.tr('Min Max Temperature of the day'))
self.forecast_minmax_layout.addWidget(mlabel)
if element.tag == 'symbol':
# icon
self.icon_list.append(element.get('var'))
weather_cond = element.get('name')
try:
weather_cond = (
self.conditions[element.get('number')]
)
except KeyError:
logging.warning(
f'Cannot find localisation string for: {weather_cond}'
)
pass
if element.tag == 'precipitation':
try:
# Take the label translated text and remove the html tags
self.doc.setHtml(self.precipitation_label.text())
precipitation_label = f'{self.doc.toPlainText()}: '
precipitation_type = element.get('type')
precipitation_type = (
f'{self.precipitation[precipitation_type]} '
)
precipitation_value = element.get('value')
rain_unit = ' mm'
if self.unit_system == ' mph ':
rain_unit = ' inch'
precipitation_value = (
f'{str(float(precipitation_value) / 25.4)} '
)
precipitation_value = (
"{0:.2f}".format(float(precipitation_value))
)
else:
precipitation_value = (
"{0:.1f}".format(float(precipitation_value))
)
weather_cond += (
'\n{0}{1}{2}{3}'.format(
precipitation_label,
precipitation_type,
precipitation_value,
rain_unit
)
)
except:
pass
if element.tag == 'windDirection':
self.doc.setHtml(self.wind_label.text())
wind = f'{self.doc.toPlainText()}: '
try:
wind_direction = (
self.wind_direction[element.get('code')]
)
except KeyError:
wind_direction = ''
if element.tag == 'windSpeed':
wind_speed = (
'{0:.1f}'.format(float(element.get('mps')))
)
if self.bft_bool:
wind_speed = str(self.convertToBeaufort(wind_speed))
if self.wind_km_bool:
wind_speed = '{0:.1f}'.format(float(wind_speed) * 3.6)
weather_cond += (
'\n{0}{1}{2}{3}'.format(
wind,
wind_speed,
self.unit_system_wind,
wind_direction
)
)
if element.tag == 'pressure':
self.doc.setHtml(self.pressure_label.text())
pressure_label = f'{self.doc.toPlainText()}: '
pressure = (
'{0:.1f}'.format(
float(element.get('value'))
)
)
weather_cond += f'\n{pressure_label}{pressure} hPa'
if element.tag == 'humidity':
humidity = element.get('value')
self.doc.setHtml(self.humidity_label.text())
humidity_label = f'{self.doc.toPlainText()}: '
weather_cond += f'\n{humidity_label}{humidity} %'
if element.tag == 'clouds':
clouds = element.get('all')
self.doc.setHtml(self.clouds_label.text())
clouds_label = f'{self.doc.toPlainText()}: '
weather_cond += f'\n{clouds_label}{clouds} %'
weather_end = True
if weather_end is True:
self.forecast_weather_list.append(weather_cond)
weather_end = False
def forecastdata(self):
'''Forecast for the next 4 days'''
# Some times server sends less data
self.clearLayout(self.forecast_minmax_layout)
self.clearLayout(self.forecast_days_layout)
fetched_file_periods = (len(self.dayforecast_data.xpath('//time')))
self.find_min_max(fetched_file_periods)
weather_end = False
collate_info = False
for element in self.dayforecast_data.iter():
# Find the day for the forecast (today+1) at 12:00:00
if element.tag == 'time':
date_list = element.get('from').split('-')
date_list_time = date_list[2].split('T')
date_list[2] = date_list_time[0]
date_list.append(date_list_time[1])
if (
datetime.datetime.now().day == int(date_list[2])
or date_list[3] != '12:00:00'
):
collate_info = False
continue
else:
collate_info = True
day_of_week = str(
datetime.date(
int(date_list[0]),
int(date_list[1]),
int(date_list[2])
).weekday()
)
label = QLabel(f'{self.days_dico[day_of_week]}')
label.setToolTip('-'.join(i for i in date_list[:3]))
label.setAlignment(Qt.AlignHCenter)
self.forecast_days_layout.addWidget(label)
temp_min = min(self.date_temp_forecast[date_list[2]])
temp_max = max(self.date_temp_forecast[date_list[2]])
mlabel = QLabel(
'<font color=>{0}°<br/>{1}°</font>'.format(
'{0:.0f}'.format(temp_min),
'{0:.0f}'.format(temp_max)
)
)
mlabel.setAlignment(Qt.AlignHCenter)
mlabel.setToolTip(self.tr('Min Max Temperature of the day'))
self.forecast_minmax_layout.addWidget(mlabel)
if element.tag == 'symbol' and collate_info:
# icon
self.icon_list.append(element.get('var'))
weather_cond = element.get('name')
try:
weather_cond = (
self.conditions[
element.get('number')
]
)
except:
logging.warning(
f'Cannot find localisation string for: {weather_cond}'
)
pass
if element.tag == 'precipitation' and collate_info:
try:
# Take the label translated text and remove the html tags
self.doc.setHtml(self.precipitation_label.text())
precipitation_label = f'{self.doc.toPlainText()}: '
precipitation_type = element.get('type')
precipitation_type = (
f'{self.precipitation[precipitation_type]} '
)
precipitation_value = (
element.get('value')
)
rain_unit = ' mm'
if self.unit_system == ' mph ':
rain_unit = ' inch'
precipitation_value = (
f'{str(float(precipitation_value) / 25.4)} '
)
precipitation_value = (
"{0:.2f}".format(float(precipitation_value))
)
else:
precipitation_value = (
"{0:.1f}".format(float(precipitation_value))
)
weather_cond += (
'\n{0}{1}{2}{3}'.format(
precipitation_label,
precipitation_type,
precipitation_value,
rain_unit
)
)
except:
pass
self.doc.setHtml(self.wind_label.text())
wind = f'{self.doc.toPlainText()}: '
if element.tag == 'windDirection' and collate_info:
try:
wind_direction = (
self.wind_direction[
element.get('code')
]
)
except:
wind_direction = ''
if element.tag == 'windSpeed' and collate_info:
wind_speed = (
'{0:.1f}'.format(
float(element.get('mps'))
)
)
if self.bft_bool:
wind_speed = str(self.convertToBeaufort(wind_speed))
if self.wind_km_bool:
wind_speed = '{0:.1f}'.format(float(wind_speed) * 3.6)
weather_cond += (
'\n{0}{1}{2}{3}'.format(
wind,
wind_speed,
self.unit_system_wind,
wind_direction
)
)
if element.tag == 'feels_like' and collate_info:
feels_like_label = QCoreApplication.translate(
'Tooltip on weather icon on 4 days forecast',
'Feels like',
'Weather information window'
)
feels_like_value = element.get('value')
feels_like_unit = element.get('unit')
if feels_like_unit == 'celsius':
feels_like_unit = '°C'
if feels_like_unit == 'fahrenheit':
feels_like_unit = '°F'
weather_cond += f'\n{feels_like_label} : {feels_like_value} {feels_like_unit}'
if element.tag == 'pressure' and collate_info:
self.doc.setHtml(self.pressure_label.text())
pressure_label = f'{self.doc.toPlainText()}: '
pressure = (
'{0:.1f}'.format(
float(
element.get('value')
)
)
)
weather_cond += f'\n{pressure_label}{pressure} hPa'
if element.tag == 'humidity' and collate_info:
humidity = element.get('value')
self.doc.setHtml(self.humidity_label.text())
humidity_label = f'{self.doc.toPlainText()}: '
weather_cond += f'\n{humidity_label}{humidity} %'
if element.tag == 'clouds' and collate_info:
clouds = element.get('all')
self.doc.setHtml(self.clouds_label.text())
clouds_label = f'{self.doc.toPlainText()}: '
weather_cond += f'\n{clouds_label}{clouds} %'
weather_end = True
if weather_end is True:
self.forecast_weather_list.append(weather_cond)
weather_end = False
def iconfetch(self):
self.clearLayout(self.forecast_icons_layout)
logging.debug('Download forecast icons...')
self.download_thread = (
IconDownload(self.forecast_icon_url, self.icon_list)
)
self.download_thread.wimage['PyQt_PyObject'].connect(self.iconwidget)
self.download_thread.url_error_signal['QString'].connect(self.errorIconFetch)
self.download_thread.start()
def clearLayout(self, layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.clearLayout(item.layout())
def iconwidget(self, icon):
'''Next days forecast icons'''
image = QImage()
image.loadFromData(icon)
iconlabel = QLabel()
iconlabel.setAlignment(Qt.AlignHCenter)
iconpixmap = QPixmap(image)
iconlabel.setPixmap(iconpixmap)
try:
iconlabel.setToolTip(self.forecast_weather_list.pop(0))
self.forecast_icons_layout.addWidget(iconlabel)
except IndexError as error:
logging.error(f'{str(error)} forecast_weather_list')
return
def dayforecastdata(self):
'''Fetch forecast for the day'''
self.clearLayout(self.dayforecast_temp_layout)
periods = 6
start = 0
if not self.json_data_bool:
start = 1
periods = 7
fetched_file_periods = (len(self.dayforecast_data.xpath('//time')))
if fetched_file_periods < periods:
# Some times server sends less data
periods = fetched_file_periods
logging.warning(
'Reduce forecast of the day to {0}'.format(periods - 1)
)
for d in range(start, periods):
clouds_translated = ''
wind = ''
timeofday = self.utc(d, 'dayforecast')
if not self.json_data_bool:
weather_cond = self.conditions[
self.dayforecast_data[4][d][0].get('number')
]
self.dayforecast_icon_list.append(
self.dayforecast_data[4][d][0].get('var')
)
temperature_at_hour = float(
self.dayforecast_data[4][d][4].get('value')
)
feels_like_value = self.dayforecast_data[4][d][5].get('value')
feels_like_unit_dic = {'celsius': '°C', 'fahrenheit': '°F'}
feels_like_unit = feels_like_unit_dic[self.dayforecast_data[4][d][5].get('unit')]
precipitation = str(
self.dayforecast_data[4][d][1].get('value')
)
precipitation_type = str(
self.dayforecast_data[4][d][1].get('type')
)
windspeed = self.dayforecast_data[4][d][3].get('mps')
winddircode = self.dayforecast_data[4][d][2].get('code')
wind_name = self.dayforecast_data[4][d][3].get('name')
try:
wind_name_translated = (
f'{self.conditions[self.wind_name_dic[wind_name.lower()]]}<br/>'
)
wind += wind_name_translated
except KeyError:
logging.warning(f'Cannot find wind name: {str(wind_name)}')
logging.info('Set wind name to None')
wind = ''
finally:
if wind == '':
wind += '<br/>'
pressure = self.dayforecast_data[4][d][6].get('value')
humidity = self.dayforecast_data[4][d][7].get('value')
clouds = self.dayforecast_data[4][d][8].get('value')
cloudspercent = self.dayforecast_data[4][d][8].get('all')
else:
weather_cond = self.conditions[
str(self.dayforecast_data['list'][d]['weather'][0]['id'])
]
self.dayforecast_icon_list.append(
self.dayforecast_data['list'][d]['weather'][0]['icon']
)
temperature_at_hour = float(
self.dayforecast_data['list'][d]['main']['temp']
)
precipitation_orig = self.dayforecast_data['list'][d]
precipitation_rain = precipitation_orig.get('rain')
precipitation_snow = precipitation_orig.get('snow')
if (
precipitation_rain is not None
and len(precipitation_rain) > 0
):
precipitation_type = 'rain'
precipitation = precipitation_rain['3h']
elif (
precipitation_snow is not None
and len(precipitation_snow) > 0
):
precipitation_type = 'snow'
precipitation_snow['3h']
else:
precipitation = 'None'
windspeed = self.dayforecast_data['list'][d]['wind']['speed']
winddircode = (
self.winddir_json_code(
self.dayforecast_data['list'][d]['wind'].get('deg')
)
)
clouds = (
self.dayforecast_data['list']
[d]['weather'][0]['description']
)
cloudspercent = (
self.dayforecast_data['list'][0]['clouds']['all']
)
self.dayforecast_weather_list.append(weather_cond)
daytime = QLabel(
'<font color=>{0}<br/>{1}°</font>'.format(
timeofday[:-3],
'{0:.0f}'.format(temperature_at_hour)
)
)
daytime.setAlignment(Qt.AlignHCenter)
unit = self.settings.value('Unit') or 'metric'
if unit == 'metric':
mu = 'mm'
if precipitation.count('None') == 0:
precipitation = "{0:.1f}".format(float(precipitation))
elif unit == 'imperial':
mu = 'inch'
if precipitation.count('None') == 0:
precipitation = str(float(precipitation) / 25.0)
precipitation = "{0:.2f}".format(float(precipitation))
elif unit == ' ':
mu = 'kelvin'
ttip = (
f'{QCoreApplication.translate("Tootltip forcast of the day", "Feels like", "Weather info window")} '
f'{feels_like_value} {feels_like_unit}'
'<br/>'
)
ttip_prec = (
f'{str(precipitation)} {mu} {precipitation_type}<br/>'
)
if ttip_prec.count('None') >= 1:
ttip_prec = ''
logging.warning(f'Actual day forcast n° {d} : No precipitation info provided')
else:
ttip_prec = ttip_prec.replace('snow', self.tr('snow'))
ttip_prec = ttip_prec.replace('rain', self.tr('rain'))
ttip += ttip_prec
if self.bft_bool is True:
windspeed = self.convertToBeaufort(windspeed)
if self.wind_km_bool:
windspeed = '{0:.1f}'.format(float(windspeed) * 3.6)
ttip += f'{str(windspeed)} {self.unit_system_wind}'
if winddircode != '':
wind = f'{self.wind_direction[winddircode]} '
else:
logging.warning(
'Wind direction code is missing: {}'.format(
str(winddircode)
)
)
if clouds != '':
try:
# In JSON there is no clouds description
clouds_translated = (
self.conditions[self.clouds_name_dic[clouds.lower()]]
)
except KeyError:
logging.warning(
'The clouding description in json is not relevant'
)
clouds_translated = ''
else:
logging.warning(f'Clouding name is missing: {str(clouds)}')
clouds_cond = f'{clouds_translated} {str(cloudspercent)}%'
ttip += f'{wind}<br/>{clouds_cond}<br/>'
pressure_local = QCoreApplication.translate(
'Tootltip forcast of the day',
'Pressure',
'Weather info window'
)
humidity_local = QCoreApplication.translate(
'Tootltip forcast of the day',
'Humidity',
'Weather info window'
)
ttip += f'{pressure_local} {pressure} hPa<br/>'
ttip += f'{humidity_local} {humidity} %'
daytime.setToolTip(ttip)
self.dayforecast_temp_layout.addWidget(daytime)
def ozone_fetch(self):
logging.debug('Download ozone info...')
if hasattr(self, 'ozone_thread'):
if self.ozone_thread.isRunning():
logging.debug('Terminate running ozone thread...')
self.ozone_thread.terminate()
self.ozone_thread.wait()
self.ozone_thread = Ozone(self.uv_coord)
self.ozone_thread.o3_signal['PyQt_PyObject'].connect(self.ozone_index)
self.ozone_thread.start()
def ozone_index(self, index):
logging.debug(f'Ozone index : {index}')
try:
du = int(index)
o3_color = self.ozone_du(du)
factor = f'{str(du)[:1]}.{str(du)[1:2]}'
gauge = '◼' * round(float(factor))
logging.debug(f'Ozone gauge: {gauge}')
except:
du = '-'
o3_color = None
du_unit = QCoreApplication.translate(
'Dobson Units',
'DU',
'Ozone value label'
)
if o3_color is not None:
self.ozone_value_label.setText(
'<font color=>{0} {1}</font><font color={2}> {3}</font>'.format(
str(du),
du_unit,
o3_color,
gauge
)
)
self.ozone_value_label.setToolTip(
QCoreApplication.translate(
'Ozone value tooltip',
'''The average amount of ozone in the <br/> atmosphere is
roughly 300 Dobson Units. What scientists call the
Antarctic Ozone “Hole” is an area where the ozone
concentration drops to an average of about 100 Dobson
Units.''',
'http://ozonewatch.gsfc.nasa.gov/facts/dobson_SH.html'
)
)
else:
self.ozone_value_label.setText(
f'<font color=>{str(du)}</font>'
)
if du != '-':
self.over_grid.addWidget(self.ozone_label, 9, 0)
self.over_grid.addWidget(self.ozone_value_label, 9, 1)
def uv_fetch(self):
logging.debug('Download uv info...')
self.uv_thread = Uv(self.uv_coord)
self.uv_thread.uv_signal['PyQt_PyObject'].connect(self.uv_index)
self.uv_thread.start()
def uv_index(self, index):
uv_gauge = '-'
uv_color = self.uv_color(index)
if uv_color[1] != 'None':
uv_gauge = '◼' * int(round(float(index)))
if uv_gauge == '':
uv_gauge = '◼'
self.uv_value_label.setText(
'<font color=>{0} {1}</font><br/><font color={2}><b>{3}</b></font>'.format(
'{0:.1f}'.format(float(index)),
self.uv_risk[uv_color[1]],
uv_color[0],
uv_gauge
)
)
else:
self.uv_value_label.setText(f'<font color=>{uv_gauge}</font>')
logging.debug(f'UV gauge ◼: {uv_gauge}')
self.uv_value_label.setToolTip(self.uv_recommend[uv_color[1]])
if uv_gauge == '-':
self.uv_label.hide()
self.uv_value_label.hide()
else:
self.uv_label.show()
self.uv_value_label.show()
def dayiconfetch(self):
'''Icons for the forecast of the day'''
self.clearLayout(self.dayforecast_layout)
logging.debug('Download forecast icons for the day...')
self.day_download_thread = IconDownload(
self.forecast_icon_url, self.dayforecast_icon_list
)
self.day_download_thread.wimage['PyQt_PyObject'].connect(self.dayiconwidget)
self.day_download_thread.url_error_signal['QString'].connect(self.errorIconFetch)
self.day_download_thread.start()
def dayiconwidget(self, icon):
'''Forecast icons of the day'''
image = QImage()
image.loadFromData(icon)
iconlabel = QLabel()
iconlabel.setAlignment(Qt.AlignHCenter)
iconpixmap = QPixmap(image)
iconlabel.setPixmap(iconpixmap)
try:
iconlabel.setToolTip(self.dayforecast_weather_list.pop(0))
self.dayforecast_layout.addWidget(iconlabel)
except IndexError as error:
logging.error(f'{str(error)} dayforecast_weather_list')
def moveEvent(self, event):
self.settings.setValue("MainWindow/Geometry", self.saveGeometry())
def resizeEvent(self, event):
self.settings.setValue("MainWindow/Geometry", self.saveGeometry())
def hideEvent(self, event):
self.settings.setValue("MainWindow/Geometry", self.saveGeometry())
def errorIconFetch(self, error):
logging.error(f'error in download of forecast icon:\n{error}')
def icon_loading(self):
self.gif_loading = QMovie(":/loading")
self.gif_loading.frameChanged.connect(self.update_gif)
self.gif_loading.start()
def update_gif(self):
gif_frame = self.gif_loading.currentPixmap()
self.systray.setIcon(QIcon(gif_frame))
def icon_city_loading(self):
self.city_label.setText('▉')
self.cityChangeTimer.start(20)
def update_city_gif(self):
current = self.city_label.text()
current += '▌'
if len(current) > 35:
current = '▉'
self.city_label.setText(current)
def manual_refresh(self):
self.tentatives = 0
self.refresh()
def wheelEvent(self, event):
if (
self.day_download_thread.isRunning()
or self.download_thread.isRunning()
):
logging.debug(
'WheelEvent: Downloading icons - remaining thread...'
)
return
self.icon_city_loading()
cities = eval(self.settings.value('CityList') or [])
if len(cities) == 0:
return
cities_trans = self.settings.value('CitiesTranslation') or '{}'
cities_trans_dict = eval(cities_trans)
direction = event.angleDelta().y()
actual_city = self.current_city_display
for key, value in cities_trans_dict.items():
if self.current_city_display == key:
actual_city = key
if actual_city not in cities:
cities.append(actual_city)
current_city_index = cities.index(actual_city)
if direction > 0:
current_city_index += 1
if current_city_index >= len(cities):
current_city_index = 0
else:
current_city_index -= 1
if current_city_index < 0:
current_city_index = len(cities) - 1
self.current_city_display = cities[current_city_index]
self.city, self.country, self.id_ = self.current_city_display.split('_')
self.timer.singleShot(500, self.refresh)
def cities_menu(self):
self.citiesMenu.clear()
cities = self.settings.value('CityList') or []
cities_trans = self.settings.value('CitiesTranslation') or '{}'
cities_trans_dict = eval(cities_trans)
if type(cities) is str:
cities = eval(cities)
# If we delete all cities it results to a '__'
if (
cities is not None
and cities != ''
and cities != '[]'
and cities != ['__']
):
if type(cities) is not list:
# FIXME sometimes the list of cities is read as a string (?)
# eval to a list
cities = eval(cities)
# Create the cities list menu
for city in cities:
if city in cities_trans_dict:
city = cities_trans_dict[city]
action = QAction(city, self)
action.triggered.connect(partial(self.changecity, city))
self.citiesMenu.addAction(action)
else:
self.empty_cities_list()
@pyqtSlot(str)
def changecity(self, city):
if hasattr(self, 'city_label'):
self.icon_city_loading()
cities_list = self.settings.value('CityList')
cities_trans = self.settings.value('CitiesTranslation') or '{}'
self.cities_trans_dict = eval(cities_trans)
logging.debug(f'Cities {str(cities_list)}')
if cities_list is None:
self.empty_cities_list()
if type(cities_list) is not list:
# FIXME some times is read as string (?)
cities_list = eval(cities_list)
for town in cities_list:
if town == self.find_city_key(city):
ind = cities_list.index(town)
self.current_city_display = cities_list[ind]
self.refresh()
def find_city_key(self, city):
for key, value in self.cities_trans_dict.items():
if value == city:
return key
return city
def empty_cities_list(self):
self.citiesMenu.addAction(self.tr('Empty list'))
def refresh(self):
if (
hasattr(self, 'overviewcitydlg')
and not self.cityChangeTimer.isActive()
):
self.icon_city_loading()
self.inerror = False
self.systray.setIcon(QIcon(':/noicon'))
self.systray.setToolTip(self.tr('Fetching weather data...'))
if self.id_ == '':
# Clear the menu, no cities configured
self.citiesMenu.clear()
self.empty_cities_list()
self.timer.singleShot(2000, self.firsttime)
self.id_ = ''
self.systray.setToolTip(self.tr('No city configured'))
return
self.city, self.country, self.id_ = self.current_city_display.split('_')
self.unit = self.settings.value('Unit') or 'metric'
self.wind_unit_speed = self.settings.value('Wind_unit') or 'df'
self.suffix = f'&mode=xml&units={self.unit}{self.appid}'
self.interval = int(self.settings.value('Interval') or 30) * 60 * 1000
self.timer.start(self.interval)
self.update()
def firsttime(self):
self.temp = ''
self.wIcon = QPixmap(':/noicon')
self.systray.showMessage(
'meteo-qt:\n',
'{0}\n{1}'.format(
self.tr('No city has been configured yet.'),
self.tr('Right click on the icon and click on Settings.')
)
)
def update(self):
if hasattr(self, 'downloadThread'):
if self.downloadThread.isRunning():
logging.debug('remaining thread...')
return
logging.debug('Update...')
self.icon_loading()
self.wIcon = QPixmap(':/noicon')
self.downloadThread = Download(
self.wIconUrl, self.baseurl, self.day_forecast_url,
self.forecast6_url, self.id_, self.suffix
)
self.downloadThread.wimage['PyQt_PyObject'].connect(self.makeicon)
self.downloadThread.finished.connect(self.tray)
self.downloadThread.xmlpage['PyQt_PyObject'].connect(self.weatherdata)
self.downloadThread.day_forecast_rawpage.connect(self.dayforecast)
self.forcast6daysBool = False
self.downloadThread.forecast6_rawpage.connect(self.forecast6)
self.downloadThread.uv_signal.connect(self.uv)
self.downloadThread.error.connect(self.error)
self.downloadThread.done.connect(self.done)
self.downloadThread.start()
def uv(self, value):
self.uv_coord = value
def forecast6(self, data):
self.forcast6daysBool = True
self.forecast6_data = data
def dayforecast(self, data):
if type(data) == dict:
self.json_data_bool = True
else:
self.json_data_bool = False
self.dayforecast_data = data
def done(self, done):
self.cityChangeTimer.stop()
if done == 0:
self.inerror = False
elif done == 1:
self.inerror = True
logging.debug('Trying to retrieve data...')
self.timer.singleShot(10000, self.try_again)
return
if hasattr(self, 'dayforecast_data'):
self.overviewcity()
return
else:
self.try_again()
def try_again(self):
self.nodata_message()
logging.debug(f'Attempts: {str(self.tentatives)}')
self.tentatives += 1
self.timer.singleShot(5000, self.refresh)
def nodata_message(self):
nodata = QCoreApplication.translate(
"Tray icon",
"Searching for weather data...",
"Tooltip (when mouse over the icon"
)
self.systray.setToolTip(nodata)
self.notification = nodata
def error(self, error):
logging.error(f'Error:\n{str(error)}')
self.nodata_message()
self.timer.start(self.interval)
self.inerror = True
def makeicon(self, data):
image = QImage()
image.loadFromData(data)
self.wIcon = QPixmap(image)
def weatherdata(self, tree):
if self.inerror:
return
for element in tree.iter():
if element.tag == 'sun':
self.weatherDataDico['Sunrise'] = element.get('rise')
self.weatherDataDico['Sunset'] = element.get('set')
if element.tag == 'temperature':
self.tempFloat = element.get('value')
self.temp = f' {str(round(float(self.tempFloat)))}°'
self.temp_decimal = (
'{}°'.format(
'{0:.1f}'.format(float(self.tempFloat))
)
)
if element.tag == 'weather':
self.meteo = element.get('value')
meteo_condition = element.get('number')
try:
self.meteo = self.conditions[meteo_condition]
except KeyError:
logging.debug(
'Cannot find localisation string for'
' meteo_condition:'
f'{str(meteo_condition)}'
)
pass
if element.tag == 'clouds':
clouds = element.get('name')
clouds_percent = element.get('value') + '%'
try:
clouds = self.clouds[clouds]
clouds = self.conditions[clouds]
except KeyError:
logging.debug(
f'Cannot find localisation string for clouds: {str(clouds)}'
)
pass
if element.tag == 'speed':
wind_value = element.get('value')
wind = element.get('name').lower()
try:
wind = self.wind[wind]
wind = self.conditions[wind]
except KeyError:
logging.debug(
f'Cannot find localisation string for wind:{str(wind)}'
)
pass
if element.tag == 'direction':
wind_codes_english = element.get('code')
wind_dir_value = element.get('value')
wind_dir = element.get('name')
try:
wind_dir_value = str(int(float(wind_dir_value)))
except TypeError:
wind_dir_value = ''
try:
wind_codes = self.wind_codes[wind_codes_english]
except (KeyError, UnboundLocalError):
logging.debug(
f'Cannot find localisation string for wind_codes: {str(wind_codes_english)}'
)
wind_codes = wind_codes_english
if wind_codes is None:
wind_codes = ''
try:
wind_dir = self.wind_dir[wind_codes_english]
except KeyError:
logging.debug(
f'Cannot find localisation string for wind_dir: {str(wind_dir)}'
)
if wind_dir is None:
wind_dir = ''
if element.tag == 'humidity':
self.weatherDataDico['Humidity'] = (
element.get('value'), element.get('unit')
)
if element.tag == 'pressure':
self.weatherDataDico['Pressure'] = (
element.get('value'), element.get('unit')
)
if element.tag == 'precipitation':
rain_mode = element.get('mode')
rain_value = element.get('value')
if rain_value is None:
rain_value = ''
self.weatherDataDico['Precipitation'] = (
rain_mode, rain_value
)
if element.tag == 'feels_like':
t_unit = {'celsius': '°C', 'fahrenheit': '°F'}
self.weatherDataDico['Feels_like'] = [element.get('value'), t_unit[element.get('unit')]]
self.city_weather_info = (
'{0} {1} {2} {3}'.format(
self.city,
self.country,
self.temp_decimal,
self.meteo
)
)
self.tooltip_weather()
self.notification = self.city_weather_info
self.weatherDataDico['Id'] = self.id_
self.weatherDataDico['City'] = self.city
self.weatherDataDico['Country'] = self.country
self.weatherDataDico['Temp'] = f'{self.tempFloat}°'
self.weatherDataDico['Meteo'] = self.meteo
self.weatherDataDico['Wind'] = (
wind_value,
wind,
wind_dir_value,
wind_codes,
wind_dir
)
self.weatherDataDico['Clouds'] = (f'{clouds_percent} {clouds}')
if self.id_ not in self.trendCities_dic:
# dict {'id': 'hPa', , '', 'T°', 'temp_trend', 'weather changedBool'}
self.trendCities_dic[self.id_] = [''] * 5
# hPa trend
pressure = float(self.weatherDataDico['Pressure'][0])
if (
self.id_ in self.trendCities_dic
and self.trendCities_dic[self.id_][0] != ''
):
self.hPaTrend = pressure - float(self.trendCities_dic[self.id_][0])
else:
self.hPaTrend = 0
self.trendCities_dic[self.id_][0] = pressure
# Temperature trend
self.notifier()
def notifier(self):
''' The notification is being shown:
On a city change or first launch or if the temperature changes
The notification is not shown if is turned off from the settings.
The tray tooltip is set here '''
temp = float(self.tempFloat)
if (
self.id_ in self.trendCities_dic
and self.trendCities_dic[self.id_][2] != ''
):
if temp > float(self.trendCities_dic[self.id_][2]):
self.temp_trend = " ↗"
self.trendCities_dic[self.id_][3] = self.temp_trend
elif temp < float(self.trendCities_dic[self.id_][2]):
self.temp_trend = " ↘"
self.trendCities_dic[self.id_][3] = self.temp_trend
else:
self.temp_trend = self.trendCities_dic[self.id_][3]
if temp == self.trendCities_dic[self.id_][2]:
self.trendCities_dic[self.id_][4] = False
else:
self.trendCities_dic[self.id_][4] = True
self.trendCities_dic[self.id_][2] = temp
self.systray.setToolTip(self.city_weather_info + self.temp_trend)
def tooltip_weather(self):
# Creation of the tray tootltip
trans_cities = self.settings.value('CitiesTranslation') or '{}'
trans_cities_dict = eval(trans_cities)
city = f'{self.city}_{self.country}_{self.id_}'
feels_like = (
'{0} {1}'.format(
QCoreApplication.translate(
'Temperature info',
'Feels like',
'SystemTrayIcon ToolTip'
),
' '.join(fl for fl in self.weatherDataDico['Feels_like'])
)
)
if city in trans_cities_dict:
self.city_weather_info = (
'{0} {1}<br/>{2}<br/>{3}'.format(
trans_cities_dict[city],
self.temp_decimal,
feels_like,
self.meteo
)
)
else:
self.city_weather_info = (
'{0} {1} {2}<br/>{3}<br/>{4}'.format(
self.city,
self.country,
self.temp_decimal,
feels_like,
self.meteo
)
)
def tray(self):
temp_decimal = eval(self.settings.value('Decimal') or 'False')
try:
if temp_decimal:
temp_tray = self.temp_decimal
else:
temp_tray = self.temp
except:
# First time launch
return
if self.inerror or not hasattr(self, 'temp'):
logging.critical('Cannot paint icon!')
return
try:
self.gif_loading.stop()
except:
# In first time run the gif is not animated
pass
logging.debug('Paint tray icon...')
# Place empty.png here to initialize the icon
# don't paint the T° over the old value
icon = QPixmap(':/empty')
self.traycolor = self.settings.value('TrayColor') or ''
self.fontsize = self.settings.value('FontSize') or '18'
self.tray_type = self.settings.value('TrayType') or 'icon&temp'
if self.tray_type == 'feels_like_temp' or self.tray_type == 'icon&feels_like':
temp_tray = '{0:.0f}'.format(float(self.weatherDataDico['Feels_like'][0]))
if temp_decimal:
temp_tray = '{0:.1f}'.format(float(self.weatherDataDico['Feels_like'][0]))
temp_tray += '°'
pt = QPainter()
pt.begin(icon)
if self.tray_type != 'temp' and self.tray_type != 'feels_like_temp':
pt.drawPixmap(0, -12, 64, 64, self.wIcon)
self.bold_set = self.settings.value('Bold') or 'False'
if self.bold_set == 'True':
br = QFont.Bold
else:
br = QFont.Normal
pt.setFont(QFont('sans-sertif', int(self.fontsize), weight=br))
pt.setPen(QColor(self.traycolor))
if self.tray_type == 'icon&temp' or self.tray_type == 'icon&feels_like':
pt.drawText(
icon.rect(),
Qt.AlignBottom | Qt.AlignCenter,
str(temp_tray)
)
if self.tray_type == 'temp' or self.tray_type == 'feels_like_temp':
pt.drawText(icon.rect(), Qt.AlignCenter, str(temp_tray))
pt.end()
if self.tray_type == 'icon':
self.systray.setIcon(QIcon(self.wIcon))
else:
self.systray.setIcon(QIcon(icon))
if self.notifier_settings():
try:
if (
self.temp_trend != ''
or self.trendCities_dic[self.id_][1] == ''
or self.id_ != self.notifier_id
):
if not self.isVisible():
# Don't show the notification when window is open
# Show only if the temperature has changed
if (
self.trendCities_dic[self.id_][4] is
True or self.trendCities_dic[self.id_][4] == ''
):
self.systray.showMessage(
'meteo-qt', f'{self.notification}{self.temp_trend}'
)
return
except KeyError:
return
self.notifier_id = self.id_ # To always notify when city changes
if self.temporary_city_status:
self.restore_city()
self.tentatives = 0
self.tooltip_weather()
logging.info(f'Actual weather status for: {self.notification}')
def notifier_settings(self):
notifier = self.settings.value('Notifications') or 'True'
notifier = eval(notifier)
if notifier:
return True
else:
return False
def restore_city(self):
self.city = self.settings.value('City') or ''
self.country = self.settings.value('Country') or ''
self.id_ = self.settings.value('ID') or ''
self.current_city_display = f'{self.city}_{self.country}_{self.id_}'
self.temporary_city_status = False
def showpanel(self):
self.activate(3)
def activate(self, reason):
# Option to start with the panel closed, true by defaut
# starting with the panel open can be useful for users who don't have plasma
# installed (to set keyboard shortcuts or other default window behaviours)
start_minimized = self.settings.value('StartMinimized') or 'True'
if reason == 3:
if self.inerror or self.id_ is None or self.id_ == '':
return
if self.isVisible() and start_minimized == 'True':
self.hide()
else:
self.show()
elif reason == 1:
self.menu.popup(QCursor.pos())
def closeEvent(self, event):
event.ignore()
self.hide()
self.settings.setValue("MainWindow/State", self.saveState())
def overview(self):
if self.inerror or len(self.weatherDataDico) == 0:
return
self.show()
def config_save(self):
logging.debug('Config saving...')
city = self.settings.value('City')
id_ = self.settings.value('ID')
country = self.settings.value('Country')
unit = self.settings.value('Unit')
wind_unit_speed = self.settings.value('Wind_unit')
traycolor = self.settings.value('TrayColor')
tray_type = self.settings.value('TrayType')
fontsize = self.settings.value('FontSize')
bold_set = self.settings.value('Bold')
language = self.settings.value('Language')
decimal = self.settings.value('Decimal')
self.appid = f'&APPID={self.settings.value("APPID")}' or ''
if language != self.language and language is not None:
self.systray.showMessage(
'meteo-qt:',
QCoreApplication.translate(
"System tray notification",
"The application has to be restarted to apply the language setting",
''
)
)
self.language = language
# Check if update is needed
if traycolor is None:
traycolor = ''
if (
self.traycolor != traycolor
or self.tray_type != tray_type
or self.fontsize != fontsize or self.bold_set != bold_set
or decimal != self.temp_decimal
):
self.tray()
if (
city == self.city
and id_ == self.id_
and country == self.country
and unit == self.unit
and wind_unit_speed == self.wind_unit_speed
):
return
else:
logging.debug('Apply changes from settings...')
self.city, self.country, self.id_ = city, country, id_
self.current_city_display = f'{city}_{country}_{id_}'
self.refresh()
def config(self):
dialog = settings.MeteoSettings(self.accurate_url, self.appid, self)
dialog.applied_signal.connect(self.config_save)
if dialog.exec_() == 1:
self.config_save()
logging.debug('Update Cities menu...')
self.cities_menu()
def tempcity(self):
dialog = searchcity.SearchCity(self.accurate_url, self.appid, self)
dialog.id_signal[tuple].connect(self.citydata)
dialog.city_signal[tuple].connect(self.citydata)
dialog.country_signal[tuple].connect(self.citydata)
if dialog.exec_():
self.temporary_city_status = True
self.current_city_display = f'{self.city}_{self.country}_{self.id_}'
self.systray.setToolTip(self.tr('Fetching weather data...'))
self.refresh()
def citydata(self, what):
if what[0] == 'City':
self.city = what[1]
elif what[0] == 'Country':
self.country = what[1]
elif what[0] == 'ID':
self.id_ = what[1]
def about(self):
title = self.tr(
"""<b>meteo-qt</b> v{0}
<br/>License: GPLv3
<br/>Python {1} - Qt {2} - PyQt {3} on {4}"""
).format(
__version__, platform.python_version(),
QT_VERSION_STR, PYQT_VERSION_STR, platform.system()
)
image = ':/logo'
text = self.tr(
"""<p>Author: Dimitrios Glentadakis
<a href="mailto:dglent@free.fr">dglent@free.fr</a>
<p>A simple application showing the weather status
information on the system tray.
<p>Website: <a href="https://github.com/dglent/meteo-qt">
https://github.com/dglent/meteo-qt</a>
<br/>Data source: <a href="http://openweathermap.org/">
OpenWeatherMap</a>.
<br/>This software uses icons from the
<a href="http://www.kde.org/">Oxygen Project</a>.
<p>To translate meteo-qt in your language or contribute to
current translations, you can use the
<a href="https://www.transifex.com/projects/p/meteo-qt/">
Transifex</a> platform.
<p>If you want to report a dysfunction or a suggestion,
feel free to open an issue in
<a href="https://github.com/dglent/meteo-qt/issues">
github</a>."""
)
dialog = about_dlg.AboutDialog(title, text, image, self)
dialog.exec_()
class Download(QThread):
wimage = pyqtSignal(['PyQt_PyObject'])
xmlpage = pyqtSignal(['PyQt_PyObject'])
forecast6_rawpage = pyqtSignal(['PyQt_PyObject'])
day_forecast_rawpage = pyqtSignal(['PyQt_PyObject'])
uv_signal = pyqtSignal(['PyQt_PyObject'])
error = pyqtSignal(['QString'])
done = pyqtSignal([int])
def __init__(self, iconurl, baseurl, day_forecast_url, forecast6_url, id_,
suffix, parent=None):
QThread.__init__(self, parent)
self.wIconUrl = iconurl
self.baseurl = baseurl
self.day_forecast_url = day_forecast_url
self.forecast6_url = forecast6_url
self.id_ = id_
self.suffix = suffix
self.tentatives = 0
self.settings = QSettings()
def run(self):
use_json_day_forecast = False
use_proxy = self.settings.value('Proxy') or 'False'
use_proxy = eval(use_proxy)
proxy_auth = (
self.settings.value('Use_proxy_authentification')
or 'False'
)
proxy_auth = eval(proxy_auth)
if use_proxy:
proxy_url = self.settings.value('Proxy_url')
proxy_port = self.settings.value('Proxy_port')
proxy_tot = f'http://:{proxy_port}'
if proxy_auth:
proxy_user = self.settings.value('Proxy_user')
proxy_password = self.settings.value('Proxy_pass')
proxy_tot = (
f'http://{proxy_user}:{proxy_password}@{proxy_url}:{proxy_port}'
)
proxy = urllib.request.ProxyHandler(
{"http": proxy_tot}
)
auth = urllib.request.HTTPBasicAuthHandler()
opener = urllib.request.build_opener(
proxy, auth, urllib.request.HTTPHandler
)
urllib.request.install_opener(opener)
else:
proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
done = 0
logging.debug(
f'Fetching url for 6 days: {self.forecast6_url}{self.id_}{self.suffix}&cnt=7'
)
reqforecast6 = (
f'{self.forecast6_url}{self.id_}{self.suffix}&cnt=7'
)
try:
reqforecast6 = urllib.request.urlopen(
f'{self.forecast6_url}{self.id_}{self.suffix}&cnt=7',
timeout=5
)
pageforecast6 = reqforecast6.read()
if str(pageforecast6).count('ClientError') > 0:
raise TypeError
treeforecast6 = etree.fromstring(pageforecast6)
forcast6days = True
except (
timeout,
urllib.error.HTTPError,
urllib.error.URLError,
etree.XMLSyntaxError,
TypeError
) as e:
forcast6days = False
logging.error(f'Url of 6 days forcast not available: {str(reqforecast6)}')
logging.error(f'6 days forcast not available: {str(e)}')
try:
logging.debug(
f'Fetching url for actual weather: {self.baseurl}{self.id_}{self.suffix}'
)
req = urllib.request.urlopen(
f'{self.baseurl}{self.id_}{self.suffix}',
timeout=5
)
logging.debug(
'Fetching url for forecast of the day + 4: {0}{1}{2}'.format(
self.day_forecast_url,
self.id_,
self.suffix
)
)
reqdayforecast = urllib.request.urlopen(
f'{self.day_forecast_url}{self.id_}{self.suffix}',
timeout=5
)
page = req.read()
pagedayforecast = reqdayforecast.read()
if self.html404(page, 'city'):
raise urllib.error.HTTPError
elif self.html404(pagedayforecast, 'day_forecast'):
# Try with json
logging.debug(
'Fetching json url for forecast of the day: '
f'{self.day_forecast_url}{self.id_}'
f'{self.suffix.replace("xml", "json")}'
)
reqdayforecast = urllib.request.urlopen(
f'{self.day_forecast_url}{self.id_}{self.suffix.replace("xml", "json")}',
timeout=5
)
pagedayforecast = reqdayforecast.read().decode('utf-8')
if self.html404(pagedayforecast, 'day_forecast'):
raise urllib.error.HTTPError
else:
treedayforecast = json.loads(pagedayforecast)
use_json_day_forecast = True
logging.debug(
'Found json page for the forecast of the day'
)
try:
tree = etree.fromstring(page)
lat = tree[0][0].get('lat')
lon = tree[0][0].get('lon')
weather_icon = tree[9].get('icon')
for var_ in [lat, lon, weather_icon]:
if isinstance(var_, type(None)):
raise TypeError
except TypeError:
logging.debug(
f'Error, use JSON page for the actual weather info {str(traceback.print_exc())}'
)
req = urllib.request.urlopen(
f'{self.baseurl}{self.id_}{self.suffix.replace("xml", "json")}',
timeout=5
)
page = req.read().decode('utf-8').replace("'", '"')
actual_weather_dic = json.loads(page)
lat = str(actual_weather_dic["coord"]["lat"])
lon = str(actual_weather_dic["coord"]["lon"])
weather_icon = actual_weather_dic["weather"][0]["icon"]
uv_ind = (lat, lon)
url = f'{self.wIconUrl}{weather_icon}.png'
self.uv_signal['PyQt_PyObject'].emit(uv_ind)
if not use_json_day_forecast:
treedayforecast = etree.fromstring(pagedayforecast)
logging.debug(f'Icon url: {url}')
data = urllib.request.urlopen(url).read()
if self.html404(data, 'icon'):
raise urllib.error.HTTPError
self.xmlpage['PyQt_PyObject'].emit(tree)
self.wimage['PyQt_PyObject'].emit(data)
if forcast6days:
self.forecast6_rawpage['PyQt_PyObject'].emit(treeforecast6)
self.day_forecast_rawpage['PyQt_PyObject'].emit(treedayforecast)
self.done.emit(int(done))
except (
ConnectionResetError,
urllib.error.HTTPError,
urllib.error.URLError
) as error:
if self.tentatives >= 10:
done = 1
try:
m_error = (
f'{self.tr("Error:")}\n{str(error.code)} {str(error.reason)}'
)
except:
m_error = str(error)
logging.error(m_error)
self.error['QString'].emit(m_error)
self.done.emit(int(done))
return
else:
self.tentatives += 1
logging.warning(f'Error: {str(error)}')
logging.info(f'Try again...{str(self.tentatives)}')
self.run()
except timeout:
if self.tentatives >= 10:
done = 1
logging.error('Timeout error, abandon...')
self.done.emit(int(done))
return
else:
self.tentatives += 1
logging.warning(
f'5 secondes timeout, new tentative: {str(self.tentatives)}'
)
self.run()
except (etree.XMLSyntaxError) as error:
logging.critical(f'Error: {str(error)}')
done = 1
self.done.emit(int(done))
logging.debug('Download thread done')
def html404(self, page, what):
try:
dico = eval(page.decode('utf-8'))
code = dico['cod']
message = dico['message']
self.error_message = f'{code} {message}@{what}'
logging.debug(str(self.error_message))
return True
except:
return False
class Ozone(QThread):
o3_signal = pyqtSignal(['PyQt_PyObject'])
def __init__(self, coord, parent=None):
QThread.__init__(self, parent)
self.coord = coord
self.settings = QSettings()
self.appid = self.settings.value('APPID') or ''
def run(self):
use_proxy = self.settings.value('Proxy') or 'False'
use_proxy = eval(use_proxy)
proxy_auth = (
self.settings.value('Use_proxy_authentification') or 'False'
)
proxy_auth = eval(proxy_auth)
if use_proxy:
proxy_url = self.settings.value('Proxy_url')
proxy_port = self.settings.value('Proxy_port')
proxy_tot = f'http://:{proxy_port}'
if proxy_auth:
proxy_user = self.settings.value('Proxy_user')
proxy_password = self.settings.value('Proxy_pass')
proxy_tot = (
f'http://{proxy_user}:{proxy_password}@{proxy_url}:{proxy_port}'
)
proxy = urllib.request.ProxyHandler({"http": proxy_tot})
auth = urllib.request.HTTPBasicAuthHandler()
opener = urllib.request.build_opener(
proxy, auth, urllib.request.HTTPHandler
)
urllib.request.install_opener(opener)
else:
proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
try:
lat = self.coord[0]
lon = self.coord[1]
url = (
'http://api.openweathermap.org/pollution/v1/o3/{0},{1}/current.json?appid={2}'.format(
lat,
lon,
self.appid
)
)
logging.debug(f'Fetching url for ozone index: {str(url)}')
req = urllib.request.urlopen(url, timeout=5)
page = req.read()
dico_value = eval(page)
o3_ind = dico_value['data']
logging.debug(f'Ozone index: {str(o3_ind)}')
except:
o3_ind = '-'
logging.error('Cannot find Ozone index')
self.o3_signal['PyQt_PyObject'].emit(o3_ind)
class Uv(QThread):
uv_signal = pyqtSignal(['PyQt_PyObject'])
def __init__(self, uv_coord, parent=None):
QThread.__init__(self, parent)
self.uv_coord = uv_coord
self.settings = QSettings()
self.appid = self.settings.value('APPID') or ''
def run(self):
use_proxy = self.settings.value('Proxy') or 'False'
use_proxy = eval(use_proxy)
proxy_auth = (
self.settings.value('Use_proxy_authentification') or 'False'
)
proxy_auth = eval(proxy_auth)
if use_proxy:
proxy_url = self.settings.value('Proxy_url')
proxy_port = self.settings.value('Proxy_port')
proxy_tot = f'http://:{proxy_port}'
if proxy_auth:
proxy_user = self.settings.value('Proxy_user')
proxy_password = self.settings.value('Proxy_pass')
proxy_tot = (
f'http://{proxy_user}:{proxy_password}@{proxy_url}:{proxy_port}'
)
proxy = urllib.request.ProxyHandler({"http": proxy_tot})
auth = urllib.request.HTTPBasicAuthHandler()
opener = urllib.request.build_opener(
proxy, auth, urllib.request.HTTPHandler
)
urllib.request.install_opener(opener)
else:
proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
try:
lat = self.uv_coord[0]
lon = self.uv_coord[1]
url = (
f'http://api.openweathermap.org/data/2.5/uvi?lat={lat}&lon={lon}&appid={self.appid}'
)
logging.debug(f'Fetching url for uv index: {str(url)}')
req = urllib.request.urlopen(url, timeout=5)
page = req.read().decode('utf-8')
dicUV = json.loads(page)
uv_ind = dicUV['value']
logging.debug(f'UV index: {str(uv_ind)}')
except:
uv_ind = '-'
logging.error('Cannot find UV index')
self.uv_signal['PyQt_PyObject'].emit(uv_ind)
class IconDownload(QThread):
url_error_signal = pyqtSignal(['QString'])
wimage = pyqtSignal(['PyQt_PyObject'])
def __init__(self, icon_url, icon, parent=None):
QThread.__init__(self, parent)
self.icon_url = icon_url
self.icon = icon
self.tentatives = 0
# Some times server sends less data
self.periods = 6
periods = len(self.icon)
if periods < 6:
self.periods = periods
self.settings = QSettings()
def run(self):
use_proxy = self.settings.value('Proxy') or 'False'
use_proxy = eval(use_proxy)
proxy_auth = (
self.settings.value('Use_proxy_authentification') or 'False'
)
proxy_auth = eval(proxy_auth)
if use_proxy:
proxy_url = self.settings.value('Proxy_url')
proxy_port = self.settings.value('Proxy_port')
proxy_tot = 'http://' + ':' + proxy_port
if proxy_auth:
proxy_user = self.settings.value('Proxy_user')
proxy_password = self.settings.value('Proxy_pass')
proxy_tot = (
f'http://{proxy_user}:{proxy_password}@{proxy_url}:{proxy_port}'
)
proxy = urllib.request.ProxyHandler({"http": proxy_tot})
auth = urllib.request.HTTPBasicAuthHandler()
opener = urllib.request.build_opener(
proxy, auth, urllib.request.HTTPHandler
)
urllib.request.install_opener(opener)
else:
proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
try:
for i in range(self.periods):
url = f'{self.icon_url}{self.icon[i]}.png'
logging.debug(f'Icon downloading: {url}')
data = urllib.request.urlopen(url, timeout=5).read()
if self.html404(data, 'icon'):
self.url_error_signal['QString'].emit(self.error_message)
return
self.wimage['PyQt_PyObject'].emit(data)
except (urllib.error.HTTPError, urllib.error.URLError) as error:
try:
url_error = (
f'Error: {str(error.code)}: {str(error.reason)}'
)
except:
url_error = error
logging.error(str(url_error))
self.url_error_signal['QString'].emit(url_error)
except timeout:
if self.tentatives >= 10:
logging.error('Timeout error, abandon...')
return
else:
self.tentatives += 1
logging.info(
'5 secondes timeout, new tentative: '
+ str(self.tentatives)
)
self.run()
logging.debug('Download forecast icons thread done')
def html404(self, page, what):
try:
dico = eval(page.decode('utf-8'))
code = dico['cod']
message = dico['message']
self.error_message = f'{code} {message}@{what}'
logging.error(self.error_message)
return True
except:
return False
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
app.setOrganizationName('meteo-qt')
app.setOrganizationDomain('meteo-qt')
app.setApplicationName('meteo-qt')
app.setWindowIcon(QIcon(':/logo'))
filePath = os.path.dirname(os.path.realpath(__file__))
settings = QSettings()
locale = settings.value('Language')
if locale is None or locale == '':
locale = QLocale.system().name()
appTranslator = QTranslator()
if os.path.exists(f'{filePath}/translations/'):
appTranslator.load(
filePath + f'/translations/meteo-qt_{locale}'
)
else:
appTranslator.load(
f'/usr/share/meteo_qt/translations/meteo-qt_{locale}'
)
app.installTranslator(appTranslator)
qtTranslator = QTranslator()
qtTranslator.load(
f'qt_{locale}',
QLibraryInfo.location(
QLibraryInfo.TranslationsPath
)
)
app.installTranslator(qtTranslator)
logLevel = settings.value('Logging/Level')
if logLevel == '' or logLevel is None:
logLevel = 'INFO'
settings.setValue('Logging/Level', 'INFO')
logPath = os.path.dirname(settings.fileName())
logFile = f'{logPath}/meteo-qt.log'
if not os.path.exists(logPath):
os.makedirs(logPath)
if os.path.isfile(logFile):
fsize = os.stat(logFile).st_size
if fsize > 10240000:
with open(logFile, 'rb') as rFile:
rFile.seek(102400)
logData = rFile.read()
with open(logFile, 'wb') as wFile:
wFile.write(logData)
del logData
logging.basicConfig(
format='%(asctime)s %(levelname)s: %(message)s'
'- %(lineno)s: %(module)s',
datefmt='%Y/%m/%d %H:%M:%S',
filename=logFile, level=logLevel
)
logger = logging.getLogger('meteo-qt')
logger.setLevel(logLevel)
loggerStream = logging.getLogger()
handlerStream = logging.StreamHandler()
loggerStreamFormatter = logging.Formatter(
'%(levelname)s: %(message)s - %(lineno)s: %(module)s'
)
handlerStream.setFormatter(loggerStreamFormatter)
loggerStream.addHandler(handlerStream)
m = SystemTrayIcon()
app.exec_()
def excepthook(exc_type, exc_value, tracebackobj):
"""
Global function to catch unhandled exceptions.
Parameters
----------
exc_type : str
exception type
exc_value : int
exception value
tracebackobj : traceback
traceback object
"""
separator = '-' * 80
now = f'{datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")} CRASH:'
info = StringIO()
traceback.print_tb(tracebackobj, None, info)
info.seek(0)
info = info.read()
errmsg = '{}\t \n{}'.format(exc_type, exc_value)
sections = [now, separator, errmsg, separator, info]
msg = '\n'.join(sections)
print(msg)
settings = QSettings()
logPath = os.path.dirname(settings.fileName())
logFile = f'{logPath}/meteo-qt.log'
with open(logFile, 'a') as logfile:
logfile.write(msg)
sys.excepthook = excepthook
if __name__ == '__main__':
main()