Skip to content

Commit c5c3cf0

Browse files
committed
Get basic temperature, conditions, location working with scaled icon
1 parent 453f4e0 commit c5c3cf0

File tree

7 files changed

+509
-42
lines changed

7 files changed

+509
-42
lines changed

src/util/location.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from dataclasses import dataclass
2+
from typing import NamedTuple, Optional
3+
4+
@dataclass
5+
class LocationData:
6+
lat: float
7+
lon: float
8+
city: Optional[str] = None
9+
country: Optional[str] = None
10+
timeZone: Optional[str] = None
11+
entryText: Optional[str] = None

src/util/meson.build

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ app_py = [
44
'fader.py',
55
'focusNavigator.py',
66
'keybindings.py',
7+
'location.py',
8+
'openweathermap.py',
79
'settings.py',
810
'trackers.py',
9-
'utils.py'
11+
'utils.py',
12+
'weather.py',
13+
'weather_types.py'
1014
]
1115

1216
install_data(app_py, install_dir: join_paths(pkgdatadir, 'util'))

src/util/openweathermap.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import json
2+
3+
import requests
4+
from types import SimpleNamespace
5+
from gi.repository import Pango
6+
7+
from util import settings
8+
from util.weather_types import APIUniqueField, BuiltinIcons, CustomIcons, Condition, Location, LocationData, WeatherData, WeatherProvider, Wind
9+
10+
OWM_URL = "https://api.openweathermap.org/data/2.5/weather"
11+
OWM_SUPPORTED_LANGS = [
12+
"af", "al", "ar", "az", "bg", "ca", "cz", "da", "de", "el", "en", "eu", "fa", "fi",
13+
"fr", "gl", "he", "hi", "hr", "hu", "id", "it", "ja", "kr", "la", "lt", "mk", "no", "nl", "pl",
14+
"pt", "pt_br", "ro", "ru", "se", "sk", "sl", "sp", "es", "sr", "th", "tr", "ua", "uk", "vi", "zh_cn", "zh_tw", "zu"
15+
]
16+
17+
18+
class OWMWeatherProvider(WeatherProvider):
19+
"""
20+
WeatherProvider implementation for OpenWeatherMap.org
21+
"""
22+
23+
def __init__(self):
24+
self.needsApiKey = False
25+
self.prettyName = _("OpenWeatherMap")
26+
self.name = "OpenWeatherMap_Open"
27+
self.maxForecastSupport = 7
28+
self.maxHourlyForecastSupport = 0
29+
self.website = "https://openweathermap.org/"
30+
self.remainingCalls = None
31+
self.supportHourlyPrecipChance = False
32+
self.supportHourlyPrecipVolume = False
33+
34+
# NOT SUPPORTED in cinnamon-spices-applets/weather
35+
# but it saves us implementing conversion functions until we need them
36+
# (which would be once we have more than just the one WeatherProvider)
37+
self.units = settings.get_weather_units()
38+
39+
def GetWeather(self, loc: LocationData):
40+
lang = self.locale_to_owm_lang(
41+
Pango.language_get_default().to_string())
42+
pref = Pango.language_get_preferred()
43+
if lang not in OWM_SUPPORTED_LANGS:
44+
for locale in pref:
45+
if self.locale_to_owm_lang(locale) in OWM_SUPPORTED_LANGS:
46+
lang = self.locale_to_owm_lang(locale)
47+
break
48+
# if we still have not found a supported language...
49+
if lang not in OWM_SUPPORTED_LANGS:
50+
lang = "en"
51+
52+
response = requests.get(OWM_URL,
53+
{
54+
"lat": loc.lat,
55+
"lon": loc.lon,
56+
"units": self.units,
57+
"appid": settings.get_weather_api_key(),
58+
"lang": lang})
59+
60+
# actual object structure: https://github.com/linuxmint/cinnamon-spices-applets/weather@mockturtl/src/3_8/providers/openweathermap/payload/weather.ts
61+
data = json.loads(
62+
response.text, object_hook=lambda d: SimpleNamespace(**d))
63+
print(data.name)
64+
return self.owm_data_to_weather_data(data)
65+
66+
@staticmethod
67+
def locale_to_owm_lang(locale_string):
68+
if locale_string is None:
69+
return "en"
70+
71+
# Dialect? support by OWM
72+
if locale_string == "zh-cn" or locale_string == "zh-cn" or locale_string == "pt-br":
73+
return locale_string
74+
75+
lang = locale_string.split("-")[0]
76+
# OWM uses different language code for Swedish, Czech, Korean, Latvian, Norwegian
77+
if lang == "sv":
78+
return "se"
79+
elif lang == "cs":
80+
return "cz"
81+
elif lang == "ko":
82+
return "kr"
83+
elif lang == "lv":
84+
return "la"
85+
elif lang == "nn" or lang == "nb":
86+
return "no"
87+
return lang
88+
89+
def owm_data_to_weather_data(self, owm_data) -> WeatherData:
90+
"""
91+
Returns as much of a complete WeatherData object as we can
92+
"""
93+
return WeatherData(**dict(
94+
date=owm_data.dt,
95+
sunrise=owm_data.sys.sunrise,
96+
sunset=owm_data.sys.sunset,
97+
coord=owm_data.coord,
98+
location=Location(**dict(
99+
city=owm_data.name,
100+
country=owm_data.sys.country,
101+
url="https://openweathermap.org/city/%s" % owm_data.id)),
102+
condition=Condition(**dict(
103+
main=owm_data.weather[0].main,
104+
description=owm_data.weather[0].description,
105+
icons=self.owm_icon_to_builtin_icons(owm_data.weather[0].icon),
106+
customIcon=self.owm_icon_to_custom_icon(
107+
owm_data.weather[0].icon)
108+
)),
109+
wind=Wind(**dict(
110+
speed=owm_data.wind.speed,
111+
degree=owm_data.wind.deg
112+
)),
113+
temperature=owm_data.main.temp,
114+
pressure=owm_data.main.pressure,
115+
humidity=owm_data.main.humidity,
116+
dewPoint=None,
117+
extra_field=APIUniqueField(**dict(
118+
type="temperature",
119+
name=_("Feels Like"),
120+
value=owm_data.main.feels_like
121+
))
122+
))
123+
124+
def owm_icon_to_builtin_icons(self, icon) -> list[BuiltinIcons]:
125+
# https://openweathermap.org/weather-conditions
126+
# fallback icons are: weather-clear-night
127+
# weather-clear weather-few-clouds-night weather-few-clouds
128+
# weather-fog weather-overcast weather-severe-alert weather-showers
129+
# weather-showers-scattered weather-snow weather-storm
130+
match icon:
131+
case "10d":
132+
# rain day */
133+
return ["weather-rain", "weather-showers-scattered", "weather-freezing-rain"]
134+
case "10n":
135+
# rain night */
136+
return ["weather-rain", "weather-showers-scattered", "weather-freezing-rain"]
137+
case "09n":
138+
# showers night*/
139+
return ["weather-showers"]
140+
case "09d":
141+
# showers day */
142+
return ["weather-showers"]
143+
case "13d":
144+
# snow day*/
145+
return ["weather-snow"]
146+
case "13n":
147+
# snow night */
148+
return ["weather-snow"]
149+
case "50d":
150+
# mist day */
151+
return ["weather-fog"]
152+
case "50n":
153+
# mist night */
154+
return ["weather-fog"]
155+
case "04d":
156+
# broken clouds day */
157+
return ["weather-overcast", "weather-clouds", "weather-few-clouds"]
158+
case "04n":
159+
# broken clouds night */
160+
return ["weather-overcast", "weather-clouds-night", "weather-few-clouds-night"]
161+
case "03n":
162+
# mostly cloudy (night) */
163+
return ['weather-clouds-night', "weather-few-clouds-night"]
164+
case "03d":
165+
# mostly cloudy (day) */
166+
return ["weather-clouds", "weather-few-clouds", "weather-overcast"]
167+
case "02n":
168+
# partly cloudy (night) */
169+
return ["weather-few-clouds-night"]
170+
case "02d":
171+
# partly cloudy (day) */
172+
return ["weather-few-clouds"]
173+
case "01n":
174+
# clear (night) */
175+
return ["weather-clear-night"]
176+
case "01d":
177+
# sunny */
178+
return ["weather-clear"]
179+
case "11d":
180+
# storm day */
181+
return ["weather-storm"]
182+
case "11n":
183+
# storm night */
184+
return ["weather-storm"]
185+
case _:
186+
return ["weather-severe-alert"]
187+
188+
@staticmethod
189+
def owm_icon_to_custom_icon(icon) -> CustomIcons:
190+
return None # TODO

src/util/settings.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
OSK_SIZE = "keyboard-size"
5252
OSK_ACTIVATION = "activation-mode"
5353

54-
a11y_settings = Gio.Settings(schema_id="org.cinnamon.desktop.a11y.applications")
54+
a11y_settings = Gio.Settings(
55+
schema_id="org.cinnamon.desktop.a11y.applications")
5556
OSK_A11Y_ENABLED = "screen-keyboard-enabled"
5657

5758
# Every setting has a getter (and setter, rarely). This is mainly for
@@ -60,119 +61,153 @@
6061
# "settings.ss_settings.get_string(settings.DEFAULT_MESSAGE_KEY)" or keeping
6162
# instances of GioSettings wherever we need them.
6263

64+
6365
def _check_string(string):
6466
if string and string != "":
6567
return string
6668

6769
return ""
6870

71+
6972
def get_default_away_message():
7073
msg = ss_settings.get_string(DEFAULT_MESSAGE_KEY)
7174

7275
return _check_string(msg)
7376

77+
7478
def get_custom_screensaver():
7579
cmd = ss_settings.get_string(CUSTOM_SCREENSAVER_KEY)
7680

7781
return _check_string(cmd)
7882

83+
7984
def get_user_switch_enabled():
8085
return ss_settings.get_boolean(USER_SWITCH_ENABLED_KEY)
8186

87+
8288
def get_idle_activate():
8389
return ss_settings.get_boolean(IDLE_ACTIVATE_KEY)
8490

91+
8592
def get_idle_lock_enabled():
8693
return ss_settings.get_boolean(LOCK_ENABLED_KEY)
8794

95+
8896
def get_idle_lock_delay():
8997
return ss_settings.get_uint(LOCK_DELAY_KEY)
9098

99+
91100
def get_use_custom_format():
92101
return ss_settings.get_boolean(USE_CUSTOM_FORMAT_KEY)
93102

103+
94104
def get_custom_date_format():
95105
date_format = ss_settings.get_string(DATE_FORMAT_KEY)
96106

97107
return _check_string(date_format)
98108

109+
99110
def get_custom_time_format():
100111
time_format = ss_settings.get_string(TIME_FORMAT_KEY)
101112

102113
return _check_string(time_format)
103114

115+
104116
def get_date_font():
105117
date_font = ss_settings.get_string(FONT_DATE_KEY)
106118

107119
return _check_string(date_font)
108120

121+
109122
def get_message_font():
110123
message_font = ss_settings.get_string(FONT_MESSAGE_KEY)
111124

112125
return _check_string(message_font)
113126

127+
114128
def get_time_font():
115129
time_font = ss_settings.get_string(FONT_TIME_KEY)
116130

117131
return _check_string(time_font)
118132

133+
119134
def get_show_flags():
120135
return if_settings.get_boolean(KBD_LAYOUT_SHOW_FLAGS)
121136

137+
122138
def get_show_upper_case_layout():
123139
return if_settings.get_boolean(KBD_LAYOUT_USE_CAPS)
124140

141+
125142
def get_use_layout_variant_names():
126143
return if_settings.get_boolean(KBD_LAYOUT_PREFER_VARIANT)
127144

145+
128146
def get_kb_group():
129147
return ss_settings.get_int(KB_LAYOUT_KEY)
130148

149+
131150
def set_kb_group(group):
132151
return ss_settings.set_int(KB_LAYOUT_KEY, group)
133152

153+
134154
def get_show_clock():
135155
return ss_settings.get_boolean(SHOW_CLOCK_KEY)
136156

157+
137158
def get_show_albumart():
138159
return ss_settings.get_boolean(SHOW_ALBUMART)
139160

161+
140162
def get_show_weather():
141163
# return ss_settings.get_boolean(SHOW_WEATHER)
142164
return True
143165

166+
144167
def get_weather_api_key():
145168
# return ss_settings.get_string(WEATHER_API_KEY)
146-
return ""
169+
# this is the OpenWeatherMap API key used by linux-mint/cinnamon-spices-applets/weather@mockturtl
170+
# presumably belongs to the org?
171+
return "1c73f8259a86c6fd43c7163b543c8640"
172+
147173

148174
def get_weather_location():
149-
# return ss_settings.get_string(WEAThER_LOCATION) # string LAT,LON (eventually)
150-
return "chicago,il,us"
175+
# location_string = ss_settings.get_string(WEATHER_LOCATION) # string LAT,LON (eventually)
176+
# return _check_string(location_string)
177+
return "41.85,-87.65" # chicago,il,us
178+
151179

152180
def get_weather_units():
153181
# return ss_settings.get_string(WEATHER_UNITS) # metric || imperial
154182
return "imperial"
155183

184+
156185
def get_weather_font():
157186
# reusing the Clock widget Time font for now (it's big)
158187
time_font = ss_settings.get_string(FONT_TIME_KEY)
159188

160189
return _check_string(time_font)
161190

191+
162192
def get_allow_shortcuts():
163193
return ss_settings.get_boolean(ALLOW_SHORTCUTS)
164194

195+
165196
def get_allow_media_control():
166197
return ss_settings.get_boolean(ALLOW_MEDIA_CONTROL)
167198

199+
168200
def get_show_info_panel():
169201
return ss_settings.get_boolean(SHOW_INFO_PANEL)
170202

203+
171204
def get_allow_floating():
172205
return ss_settings.get_boolean(FLOATING_WIDGETS)
173206

207+
174208
def get_osk_type():
175209
return osk_settings.get_string(OSK_TYPE)
176210

211+
177212
def get_osk_a11y_active():
178-
return a11y_settings.get_boolean(OSK_A11Y_ENABLED) and osk_settings.get_string(OSK_ACTIVATION) == 'accessible'
213+
return a11y_settings.get_boolean(OSK_A11Y_ENABLED) and osk_settings.get_string(OSK_ACTIVATION) == 'accessible'

src/util/weather.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def k_to_f(k: float) -> int:
2+
return round(k - 273.15)
3+
4+
5+
def k_to_c(k: float) -> float:
6+
return round((9 / 5 * (k - 273.15) + 32), 1)

0 commit comments

Comments
 (0)