Skip to content

Commit e686767

Browse files
authored
Merge pull request #620 from mathoudebine/dev/pull/510
2 parents 37280a5 + 4c7366f commit e686767

15 files changed

+866
-9
lines changed

config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ config:
3333
# Value must be 'controller/fan' e.g. 'nct6798/fan2'. Use configuration wizard for help in selection
3434
CPU_FAN: AUTO
3535

36+
# Address used for ping sensor. Can be internal/external IP (e.g. 8.8.8.8 or 192.168.0.1) or hostname (google.com)
37+
PING: 8.8.8.8
38+
39+
# Weather data with OpenWeatherMap API. Only useful if you want to use a theme that displays it
40+
# Location from which to display the weather. Use for example https://www.latlong.net/ to get latitude/longitude
41+
WEATHER_LATITUDE: 45.75
42+
WEATHER_LONGITUDE: 4.85
43+
# OpenWeatherMap API KEY. Can be obtained by creating a free account on https://home.openweathermap.org/users/sign_up.
44+
# You need to subscribe to the 3.0 OneCallAPI that has 1000 free daily calls
45+
WEATHER_API_KEY: ""
46+
# Units used to display temperatures (metric - °C, imperial - °F, standard - °K)
47+
WEATHER_UNITS: metric
48+
# Language is used by the API. Find more here https://openweathermap.org/api/one-call-3#multi
49+
WEATHER_LANGUAGE: en
50+
3651
display:
3752
# Display revision:
3853
# - A for Turing 3.5" and UsbPCMonitor 3.5"/5"

library/lcd/lcd_comm.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ def DisplayText(
235235
assert len(text) > 0, 'Text must not be empty'
236236
assert font_size > 0, "Font size must be > 0"
237237

238+
# If only width is specified, assume height based on font size (one-line text)
239+
if width > 0 and height == 0:
240+
height = font_size
241+
238242
if background_image is None:
239243
# A text bitmap is created with max width/height by default : text with solid background
240244
text_image = Image.new(

library/scheduler.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,53 +121,69 @@ def CPUFanSpeed():
121121

122122

123123
@async_job("GPU_Stats")
124-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['GPU'].get("INTERVAL", 0)).total_seconds())
124+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('GPU', {}).get("INTERVAL", 0)).total_seconds())
125125
def GpuStats():
126126
""" Refresh the GPU Stats """
127127
# logger.debug("Refresh GPU Stats")
128128
stats.Gpu.stats()
129129

130130

131131
@async_job("Memory_Stats")
132-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['MEMORY'].get("INTERVAL", 0)).total_seconds())
132+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('MEMORY', {}).get("INTERVAL", 0)).total_seconds())
133133
def MemoryStats():
134134
# logger.debug("Refresh memory stats")
135135
stats.Memory.stats()
136136

137137

138138
@async_job("Disk_Stats")
139-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DISK'].get("INTERVAL", 0)).total_seconds())
139+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('DISK', {}).get("INTERVAL", 0)).total_seconds())
140140
def DiskStats():
141141
# logger.debug("Refresh disk stats")
142142
stats.Disk.stats()
143143

144144

145145
@async_job("Net_Stats")
146-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['NET'].get("INTERVAL", 0)).total_seconds())
146+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('NET', {}).get("INTERVAL", 0)).total_seconds())
147147
def NetStats():
148148
# logger.debug("Refresh net stats")
149149
stats.Net.stats()
150150

151151

152152
@async_job("Date_Stats")
153-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DATE'].get("INTERVAL", 0)).total_seconds())
153+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('DATE', {}).get("INTERVAL", 0)).total_seconds())
154154
def DateStats():
155155
# logger.debug("Refresh date stats")
156156
stats.Date.stats()
157157

158+
158159
@async_job("SystemUptime_Stats")
159-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['UPTIME'].get("INTERVAL", 0)).total_seconds())
160+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('UPTIME', {}).get("INTERVAL", 0)).total_seconds())
160161
def SystemUptimeStats():
161162
# logger.debug("Refresh system uptime stats")
162163
stats.SystemUptime.stats()
163164

165+
164166
@async_job("Custom_Stats")
165-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", 0)).total_seconds())
167+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('CUSTOM', {}).get("INTERVAL", 0)).total_seconds())
166168
def CustomStats():
167169
# print("Refresh custom stats")
168170
stats.Custom.stats()
169171

170172

173+
@async_job("Weather_Stats")
174+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('WEATHER', {}).get("INTERVAL", 0)).total_seconds())
175+
def WeatherStats():
176+
# logger.debug("Refresh Weather data")
177+
stats.Weather.stats()
178+
179+
180+
@async_job("Ping_Stats")
181+
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('PING', {}).get("INTERVAL", 0)).total_seconds())
182+
def PingStats():
183+
# logger.debug("Refresh Ping data")
184+
stats.Ping.stats()
185+
186+
171187
@async_job("Queue_Handler")
172188
@schedule(timedelta(milliseconds=1).total_seconds())
173189
def QueueHandler():

library/stats.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
from typing import List
3030

3131
import babel.dates
32+
import requests
33+
from ping3 import ping
3234
from psutil._common import bytes2human
3335
from uptime import uptime
3436

@@ -42,6 +44,7 @@
4244
WLO_CARD = config.CONFIG_DATA["config"].get("WLO", "")
4345
HW_SENSORS = config.CONFIG_DATA["config"].get("HW_SENSORS", "AUTO")
4446
CPU_FAN = config.CONFIG_DATA["config"].get("CPU_FAN", "AUTO")
47+
PING_DEST = config.CONFIG_DATA["config"].get("PING", "127.0.0.1")
4548

4649
if HW_SENSORS == "PYTHON":
4750
if platform.system() == 'Windows':
@@ -824,3 +827,104 @@ def stats():
824827
theme_data = config.THEME_DATA['STATS']['CUSTOM'][custom_stat].get("LINE_GRAPH", None)
825828
if theme_data is not None and last_values is not None:
826829
display_themed_line_graph(theme_data=theme_data, values=last_values)
830+
831+
832+
class Weather:
833+
@staticmethod
834+
def stats():
835+
WEATHER_UNITS = {'metric': '°C', 'imperial': '°F', 'standard': '°K'}
836+
837+
weather_theme_data = config.THEME_DATA['STATS'].get('WEATHER', {})
838+
wtemperature_theme_data = weather_theme_data.get('TEMPERATURE', {}).get('TEXT', {})
839+
wfelt_theme_data = weather_theme_data.get('TEMPERATURE_FELT', {}).get('TEXT', {})
840+
wupdatetime_theme_data = weather_theme_data.get('UPDATE_TIME', {}).get('TEXT', {})
841+
wdescription_theme_data = weather_theme_data.get('WEATHER_DESCRIPTION', {}).get('TEXT', {})
842+
whumidity_theme_data = weather_theme_data.get('HUMIDITY', {}).get('TEXT', {})
843+
844+
activate = True if wtemperature_theme_data.get("SHOW") or wfelt_theme_data.get(
845+
"SHOW") or wupdatetime_theme_data.get("SHOW") or wdescription_theme_data.get(
846+
"SHOW") or whumidity_theme_data.get("SHOW") else False
847+
848+
if activate:
849+
temp = None
850+
feel = None
851+
time = None
852+
humidity = None
853+
if HW_SENSORS in ["STATIC", "STUB"]:
854+
temp = "17.5°C"
855+
feel = "(17.2°C)"
856+
desc = "Cloudy"
857+
time = "@15:33"
858+
humidity = "45%"
859+
else:
860+
# API Parameters
861+
lat = config.CONFIG_DATA['config'].get('WEATHER_LATITUDE', "")
862+
lon = config.CONFIG_DATA['config'].get('WEATHER_LONGITUDE', "")
863+
api_key = config.CONFIG_DATA['config'].get('WEATHER_API_KEY', "")
864+
units = config.CONFIG_DATA['config'].get('WEATHER_UNITS', "metric")
865+
lang = config.CONFIG_DATA['config'].get('WEATHER_LANGUAGE', "en")
866+
deg = WEATHER_UNITS.get(units, '°?')
867+
if api_key:
868+
url = f'https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude=minutely,hourly,daily,alerts&appid={api_key}&units={units}&lang={lang}'
869+
try:
870+
response = requests.get(url)
871+
if response.status_code == 200:
872+
data = response.json()
873+
temp = f"{data['current']['temp']:.1f}{deg}"
874+
feel = f"({data['current']['feels_like']:.1f}{deg})"
875+
desc = data['current']['weather'][0]['description'].capitalize()
876+
humidity = f"{data['current']['humidity']:.0f}%"
877+
now = datetime.datetime.now()
878+
time = f"@{now.hour:02d}:{now.minute:02d}"
879+
else:
880+
logger.error(f"Error {response.status_code} fetching OpenWeatherMap API:")
881+
# logger.error(f"Response content: {response.content}")
882+
# logger.error(response.text)
883+
desc = response.json().get('message')
884+
except Exception as e:
885+
logger.error(f"Error fetching OpenWeatherMap API: {str(e)}")
886+
desc = "Error fetching OpenWeatherMap API"
887+
else:
888+
logger.warning("No OpenWeatherMap API key provided in config.yaml")
889+
desc = "No OpenWeatherMap API key"
890+
891+
if activate:
892+
# Display Temperature
893+
display_themed_value(theme_data=wtemperature_theme_data, value=temp)
894+
# Display Temperature Felt
895+
display_themed_value(theme_data=wfelt_theme_data, value=feel)
896+
# Display Update Time
897+
display_themed_value(theme_data=wupdatetime_theme_data, value=time)
898+
# Display Humidity
899+
display_themed_value(theme_data=whumidity_theme_data, value=humidity)
900+
# Display Weather Description (or error message)
901+
display_themed_value(theme_data=wdescription_theme_data, value=desc)
902+
903+
904+
class Ping:
905+
last_values_ping = []
906+
907+
@classmethod
908+
def stats(cls):
909+
theme_data = config.THEME_DATA['STATS']['PING']
910+
911+
delay = ping(dest_addr=PING_DEST, unit="ms")
912+
913+
save_last_value(delay, cls.last_values_ping,
914+
theme_data['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE))
915+
# logger.debug(f"Ping delay: {delay}ms")
916+
917+
display_themed_progress_bar(theme_data['GRAPH'], delay)
918+
display_themed_radial_bar(
919+
theme_data=theme_data['RADIAL'],
920+
value=int(delay),
921+
unit="ms",
922+
min_size=6
923+
)
924+
display_themed_value(
925+
theme_data=theme_data['TEXT'],
926+
value=int(delay),
927+
unit="ms",
928+
min_size=6
929+
)
930+
display_themed_line_graph(theme_data['LINE_GRAPH'], cls.last_values_ping)

main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ def on_win32_wm_event(hWnd, msg, wParam, lParam):
214214
scheduler.DateStats()
215215
scheduler.SystemUptimeStats()
216216
scheduler.CustomStats()
217+
scheduler.WeatherStats()
218+
scheduler.PingStats()
217219
scheduler.QueueHandler()
218220

219221
if tray_icon and platform.system() == "Darwin": # macOS-specific

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ ruamel.yaml~=0.18.6 # For configuration editor
88
sv-ttk~=2.6.0 # Tk Sun Valley theme for configuration editor
99
tkinter-tooltip~=3.1.2 # Tooltips for configuration editor
1010
uptime~=3.0.1 # For System Uptime
11+
requests~=2.32.3 # HTTP library
12+
ping3~=4.0.8 # ICMP ping implementation using raw socket
1113

1214
# Image generation
1315
Pillow~=10.4.0; python_version < "3.9" # For Python 3.8 max.

res/themes/ColoredFlat/background.png

209 KB
Loading

res/themes/ColoredFlat/background.xcf

675 KB
Binary file not shown.

res/themes/ColoredFlat/preview.png

238 KB
Loading
3.61 KB
Loading

0 commit comments

Comments
 (0)