Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ config:
# Value must be 'controller/fan' e.g. 'nct6798/fan2'. Use configuration wizard for help in selection
CPU_FAN: AUTO

# 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)
PING: 8.8.8.8

# Weather data with OpenWeatherMap API. Only useful if you want to use a theme that displays it
# Location from which to display the weather. Use for example https://www.latlong.net/ to get latitude/longitude
WEATHER_LATITUDE: 45.75
WEATHER_LONGITUDE: 4.85
# OpenWeatherMap API KEY. Can be obtained by creating a free account on https://home.openweathermap.org/users/sign_up.
# You need to subscribe to the 3.0 OneCallAPI that has 1000 free daily calls
WEATHER_API_KEY: ""
# Units used to display temperatures (metric - °C, imperial - °F, standard - °K)
WEATHER_UNITS: metric
# Language is used by the API. Find more here https://openweathermap.org/api/one-call-3#multi
WEATHER_LANGUAGE: fr

display:
# Display revision:
# - A for Turing 3.5" and UsbPCMonitor 3.5"/5"
Expand Down
30 changes: 23 additions & 7 deletions library/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,53 +121,69 @@ def CPUFanSpeed():


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


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


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


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


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


@async_job("SystemUptime_Stats")
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['UPTIME'].get("INTERVAL", 0)).total_seconds())
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('UPTIME', {}).get("INTERVAL", 0)).total_seconds())
def SystemUptimeStats():
# logger.debug("Refresh system uptime stats")
stats.SystemUptime.stats()


@async_job("Custom_Stats")
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", 0)).total_seconds())
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('CUSTOM', {}).get("INTERVAL", 0)).total_seconds())
def CustomStats():
# print("Refresh custom stats")
stats.Custom.stats()


@async_job("Weather_Stats")
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('WEATHER', {}).get("INTERVAL", 0)).total_seconds())
def WeatherStats():
# logger.debug("Refresh Weather data")
stats.Weather.stats()


@async_job("Ping_Stats")
@schedule(timedelta(seconds=config.THEME_DATA['STATS'].get('PING', {}).get("INTERVAL", 0)).total_seconds())
def PingStats():
# logger.debug("Refresh Ping data")
stats.Ping.stats()


@async_job("Queue_Handler")
@schedule(timedelta(milliseconds=1).total_seconds())
def QueueHandler():
Expand Down
113 changes: 113 additions & 0 deletions library/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from typing import List

import babel.dates
import requests
from ping3 import ping
from psutil._common import bytes2human
from uptime import uptime

Expand All @@ -42,6 +44,7 @@
WLO_CARD = config.CONFIG_DATA["config"].get("WLO", "")
HW_SENSORS = config.CONFIG_DATA["config"].get("HW_SENSORS", "AUTO")
CPU_FAN = config.CONFIG_DATA["config"].get("CPU_FAN", "AUTO")
PING_DEST = config.CONFIG_DATA["config"].get("PING", "127.0.0.1")

if HW_SENSORS == "PYTHON":
if platform.system() == 'Windows':
Expand Down Expand Up @@ -824,3 +827,113 @@
theme_data = config.THEME_DATA['STATS']['CUSTOM'][custom_stat].get("LINE_GRAPH", None)
if theme_data is not None and last_values is not None:
display_themed_line_graph(theme_data=theme_data, values=last_values)


class Weather:
@staticmethod
def stats():
WEATHER_UNITS = {'metric': '°C', 'imperial': '°F', 'standard': '°K'}

weather_theme_data = config.THEME_DATA['STATS'].get('WEATHER', {})
wtemperature_theme_data = weather_theme_data.get('TEMPERATURE', {}).get('TEXT', {})
wfelt_theme_data = weather_theme_data.get('TEMPERATURE_FELT', {}).get('TEXT', {})
wupdatetime_theme_data = weather_theme_data.get('UPDATE_TIME', {}).get('TEXT', {})
wdescription_theme_data = weather_theme_data.get('WEATHER_DESCRIPTION', {}).get('TEXT', {})
whumidity_theme_data = weather_theme_data.get('HUMIDITY', {}).get('TEXT', {})

# Retrieve information used to center description, if needed
center_description_length = 40
if 'CENTER_LENGTH' in wdescription_theme_data:
center_description_length = wdescription_theme_data['CENTER_LENGTH']

activate = True if wtemperature_theme_data.get("SHOW") or wfelt_theme_data.get(
"SHOW") or wupdatetime_theme_data.get("SHOW") or wdescription_theme_data.get(
"SHOW") or whumidity_theme_data.get("SHOW") else False

if activate:
temp = None
feel = None
time = None
humidity = None
if HW_SENSORS in ["STATIC", "STUB"]:
temp = "17.5°C"
feel = "(17.2°C)"
desc = "Cloudy"
time = "@15:33"
humidity = "45%"
if wdescription_theme_data['CENTER_LENGTH']:
desc = "x" * center_description_length
else:
# API Parameters
lat = config.CONFIG_DATA['config'].get('WEATHER_LATITUDE', "")
lon = config.CONFIG_DATA['config'].get('WEATHER_LONGITUDE', "")
api_key = config.CONFIG_DATA['config'].get('WEATHER_API_KEY', "")
units = config.CONFIG_DATA['config'].get('WEATHER_UNITS', "metric")
lang = config.CONFIG_DATA['config'].get('WEATHER_LANGUAGE', "en")
deg = WEATHER_UNITS.get(units, '°?')
if api_key:
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}'
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
temp = f"{data['current']['temp']:.1f}{deg}"
feel = f"({data['current']['feels_like']:.1f}{deg})"
desc = data['current']['weather'][0]['description'].capitalize()
if wdescription_theme_data['CENTER_LENGTH']:
desc = desc.center(center_description_length)
humidity = f"{data['current']['humidity']:.0f}%"
now = datetime.datetime.now()
time = f"@{now.hour:02d}:{now.minute:02d}"
else:
logger.error(f"Error {response.status_code} fetching OpenWeatherMap API:")
# logger.debug(f"Response content: {response.content}")
logger.error(response.text)
desc = response.json().get('message')
except Exception as e:
logger.error(f"Error fetching OpenWeatherMap API: {str(e)}")
desc = "Error fetching OpenWeatherMap API"
else:
logger.warning("No OpenWeatherMap API key provided in config.yaml")
desc = "No OpenWeatherMap API key"

if activate:
# Display Temperature
display_themed_value(theme_data=wtemperature_theme_data, value=temp)
# Display Temperature Felt
display_themed_value(theme_data=wfelt_theme_data, value=feel)
# Display Update Time
display_themed_value(theme_data=wupdatetime_theme_data, value=time)
# Display Humidity
display_themed_value(theme_data=whumidity_theme_data, value=humidity)
# Display Weather Description (or error message)
display_themed_value(theme_data=wdescription_theme_data, value=desc)


class Ping:
last_values_ping = []

@classmethod
def stats(cls):
theme_data = config.THEME_DATA['STATS']['PING']

delay = ping(dest_addr=PING_DEST, unit="ms")

save_last_value(delay, cls.last_values_ping,
theme_data['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE))
# logger.debug(f"Ping delay: {delay}ms")

display_themed_progress_bar(theme_data['GRAPH'], delay)
display_themed_radial_bar(
theme_data=theme_data['RADIAL'],
value=int(delay),
unit="ms",
min_size=6
)
display_themed_value(
theme_data=theme_data['TEXT'],
value=int(delay),
unit="ms",
min_size=6
)
display_themed_line_graph(theme_data['LINE_GRAPH'], cls.last_values_ping)
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def on_win32_wm_event(hWnd, msg, wParam, lParam):
scheduler.DateStats()
scheduler.SystemUptimeStats()
scheduler.CustomStats()
scheduler.WeatherStats()
scheduler.PingStats()
scheduler.QueueHandler()

if tray_icon and platform.system() == "Darwin": # macOS-specific
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ ruamel.yaml~=0.18.6 # For configuration editor
sv-ttk~=2.6.0 # Tk Sun Valley theme for configuration editor
tkinter-tooltip~=3.1.2 # Tooltips for configuration editor
uptime~=3.0.1 # For System Uptime
requests~=2.32.3 # HTTP library
ping3~=4.0.8 # ICMP ping implementation using raw socket

# Image generation
Pillow~=10.4.0; python_version < "3.9" # For Python 3.8 max.
Expand Down
Binary file added res/themes/ColoredFlat/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/themes/ColoredFlat/background.xcf
Binary file not shown.
Binary file added res/themes/ColoredFlat/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/themes/ColoredFlat/telecharger-arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading