diff --git a/config.yaml b/config.yaml index db3a529b..755e5910 100644 --- a/config.yaml +++ b/config.yaml @@ -33,7 +33,7 @@ config: # Value must be 'controller/fan' e.g. 'nct6798/fan2'. Use configuration wizard for help in selection CPU_FAN: AUTO - # IP address used for ping sensor. Can be external (e.g. 8.8.8.8) or internal (e.g. 192.168.x.x) + # 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 @@ -57,7 +57,7 @@ display: # - SIMU for 3.5" simulated LCD (image written in screencap.png) # - SIMU5 for 5" simulated LCD # To identify your smart screen: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions - REVISION: C + REVISION: A # Display Brightness # Set this as the desired %, 0 being completely dark and 100 being max brightness diff --git a/library/scheduler.py b/library/scheduler.py index f1a6c738..175aa6e8 100644 --- a/library/scheduler.py +++ b/library/scheduler.py @@ -121,7 +121,7 @@ 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") @@ -129,40 +129,42 @@ def GpuStats(): @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() @@ -174,6 +176,14 @@ 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(): diff --git a/library/sensors/sensors_custom.py b/library/sensors/sensors_custom.py index 972fe233..0605f802 100644 --- a/library/sensors/sensors_custom.py +++ b/library/sensors/sensors_custom.py @@ -21,12 +21,8 @@ import math import platform -import requests from abc import ABC, abstractmethod from typing import List -from ping3 import ping, verbose_ping -from datetime import datetime -import library.config as config # Custom data classes must be implemented in this file, inherit the CustomDataSource and implement its 2 methods @@ -51,37 +47,6 @@ def last_values(self) -> List[float]: # If you do not want to draw a line graph or if your custom data has no numeric values, keep this function empty pass -# Custom data class to measure ping to google.fr -class Ping(CustomDataSource): - # This list is used to store the last 500 values to display a line graph - last_val = [math.nan] * 500 # By default, it is filed with math.nan values to indicate there is no data stored - - def as_numeric(self) -> float: - # Measure the ping - try: - result = ping(config.CONFIG_DATA['config'].get('PING', "8.8.8.8"))*1000 - if result is not None: - # Store the value to the history list that will be used for line graph - self.last_val.append(result) - # Also remove the oldest value from history list - self.last_val.pop(0) - return result - else: - self.last_val.append(9999) - self.last_val.pop(0) - return 9999 # Return 0 if ping fails - except Exception as e: - self.last_val.append(9999) - self.last_val.pop(0) - return 9999 - - def as_string(self) -> str: - # Format the ping result as a string - return f'{self.as_numeric():4.0f} ms' - - def last_values(self) -> List[float]: - # Since there is no historical data for ping, return an empty list - return self.last_val # Example for a custom data class that has numeric and text values class ExampleCustomNumericData(CustomDataSource): @@ -130,4 +95,4 @@ def as_string(self) -> str: def last_values(self) -> List[float]: # If a custom data class only has text values, it won't be possible to display line graph - pass \ No newline at end of file + pass diff --git a/library/stats.py b/library/stats.py index 254d6a86..9aa3136d 100644 --- a/library/stats.py +++ b/library/stats.py @@ -29,9 +29,10 @@ from typing import List import babel.dates +import requests +from ping3 import ping from psutil._common import bytes2human from uptime import uptime -import requests import library.config as config from library.display import display @@ -43,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': @@ -826,6 +828,7 @@ def stats(): 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(): @@ -843,9 +846,16 @@ def stats(): 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 - + 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 + desc = None + time = None + humidity = None if HW_SENSORS in ["STATIC", "STUB"]: temp = "17.5°C" feel = "(17.2°C)" @@ -853,7 +863,7 @@ def stats(): time = "@15:33" humidity = "45%" if wdescription_theme_data['CENTER_LENGTH']: - desc = "x"*center_description_length + desc = "x" * center_description_length else: # API Parameters lat = config.CONFIG_DATA['config'].get('WEATHER_LATITUDE', "") @@ -892,10 +902,39 @@ def stats(): # 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_themed_value(theme_data=wfelt_theme_data, value=feel) # Display Update Time - display_themed_value(theme_data=wupdatetime_theme_data, value=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 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) diff --git a/main.py b/main.py index e494624f..fbe0cf0f 100755 --- a/main.py +++ b/main.py @@ -215,6 +215,7 @@ def on_win32_wm_event(hWnd, msg, wParam, lParam): scheduler.SystemUptimeStats() scheduler.CustomStats() scheduler.WeatherStats() + scheduler.PingStats() scheduler.QueueHandler() if tray_icon and platform.system() == "Darwin": # macOS-specific diff --git a/res/themes/ColoredFlat/theme.yaml b/res/themes/ColoredFlat/theme.yaml index b066fcc7..3f199233 100644 --- a/res/themes/ColoredFlat/theme.yaml +++ b/res/themes/ColoredFlat/theme.yaml @@ -540,30 +540,28 @@ STATS: FONT_SIZE: 18 FONT_COLOR: 247, 227, 227 BACKGROUND_IMAGE: background.png - CUSTOM: - # For now the refresh interval (in seconds) is the same for all custom data classes + PING: INTERVAL: 1 - Ping: - TEXT: - SHOW: True - X: 325 - Y: 495 - FONT: jetbrains-mono/JetBrainsMono-Bold.ttf - FONT_SIZE: 25 - FONT_COLOR: 247, 227, 227 - BACKGROUND_IMAGE: background.png - ALIGN: right - LINE_GRAPH: - SHOW: true - X: 30 - Y: 680 - WIDTH: 415 - HEIGHT: 70 - MIN_VALUE: 0 - MAX_VALUE: 250 - HISTORY_SIZE: 120 - AUTOSCALE: false - LINE_COLOR: 247, 227, 227 - AXIS: True - AXIS_COLOR: 247, 227, 227 - BACKGROUND_IMAGE: background.png + TEXT: + SHOW: True + X: 325 + Y: 495 + FONT: jetbrains-mono/JetBrainsMono-Bold.ttf + FONT_SIZE: 25 + FONT_COLOR: 247, 227, 227 + BACKGROUND_IMAGE: background.png + ALIGN: right + LINE_GRAPH: + SHOW: true + X: 30 + Y: 680 + WIDTH: 415 + HEIGHT: 70 + MIN_VALUE: 0 + MAX_VALUE: 250 + HISTORY_SIZE: 120 + AUTOSCALE: false + LINE_COLOR: 247, 227, 227 + AXIS: True + AXIS_COLOR: 247, 227, 227 + BACKGROUND_IMAGE: background.png diff --git a/res/themes/default.yaml b/res/themes/default.yaml index d2c5cc6d..a915bcb6 100644 --- a/res/themes/default.yaml +++ b/res/themes/default.yaml @@ -217,5 +217,32 @@ STATS: FORMATTED: TEXT: SHOW: False + WEATHER: + INTERVAL: 0 + TEMPERATURE: + TEXT: + SHOW: False + TEMPERATURE_FELT: + TEXT: + SHOW: False + UPDATE_TIME: + TEXT: + SHOW: False + HUMIDITY: + TEXT: + SHOW: False + WEATHER_DESCRIPTION: + TEXT: + SHOW: False + PING: + INTERVAL: 0 + GRAPH: + SHOW: False + RADIAL: + SHOW: False + LINE_GRAPH: + SHOW: False + TEXT: + SHOW: False CUSTOM: INTERVAL: 0 diff --git a/res/themes/theme_example.yaml b/res/themes/theme_example.yaml index c068b802..72b141d4 100644 --- a/res/themes/theme_example.yaml +++ b/res/themes/theme_example.yaml @@ -1516,3 +1516,72 @@ STATS: FONT_SIZE: 18 FONT_COLOR: 200, 200, 200 BACKGROUND_IMAGE: background.png + PING: + INTERVAL: 10 + GRAPH: + SHOW: False + X: 115 + Y: 357 + WIDTH: 178 + HEIGHT: 13 + MIN_VALUE: 0 + MAX_VALUE: 100 + BAR_COLOR: 255, 0, 0 + BAR_OUTLINE: False + # BACKGROUND_COLOR: 0, 0, 0 + BACKGROUND_IMAGE: background.png + RADIAL: + SHOW: False + X: 141 + Y: 275 + RADIUS: 28 + WIDTH: 8 + MIN_VALUE: 0 + MAX_VALUE: 100 + ANGLE_START: 110 + ANGLE_END: 70 + ANGLE_STEPS: 1 + ANGLE_SEP: 25 + CLOCKWISE: True + BAR_COLOR: 255, 0, 0 + SHOW_TEXT: True + SHOW_UNIT: True + FONT: roboto-mono/RobotoMono-Bold.ttf + FONT_SIZE: 13 + FONT_COLOR: 200, 200, 200 + # BACKGROUND_COLOR: 0, 0, 0 + BACKGROUND_IMAGE: background.png + LINE_GRAPH: + SHOW: False + X: 300 + Y: 220 + WIDTH: 133 + HEIGHT: 70 + MIN_VALUE: 30 + MAX_VALUE: 120 + HISTORY_SIZE: 10 + AUTOSCALE: True + LINE_COLOR: 61, 184, 225 + LINE_WIDTH: 2 + AXIS: True + AXIS_COLOR: 255, 135, 0 + # BACKGROUND_COLOR: 0, 0, 0 + BACKGROUND_IMAGE: background.png + TEXT: + SHOW: False + SHOW_UNIT: True + X: 204 + Y: 405 + # Text sensors may vary in size and create "ghosting" effects where old value stay displayed under the new one. + # To avoid this use one of these 2 methods (or both): + # - either use a monospaced font (fonts with "mono" in name, see res/fonts/ for available fonts) + # - or force a static width/height for the text field. Be sure to have enough space for the longest value that can be displayed (e.g. "100%" for a percentage) + # WIDTH: 200 # Uncomment to force a static width + # HEIGHT: 50 # Uncomment to force static height + FONT: jetbrains-mono/JetBrainsMono-Bold.ttf + FONT_SIZE: 23 + FONT_COLOR: 255, 255, 255 + # BACKGROUND_COLOR: 132, 154, 165 + BACKGROUND_IMAGE: background.png + ALIGN: left # left / center / right + ANCHOR: lt # Check https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html diff --git a/theme-editor.py b/theme-editor.py index 3e1bed53..5ca59443 100755 --- a/theme-editor.py +++ b/theme-editor.py @@ -47,7 +47,6 @@ except: os._exit(0) - if len(sys.argv) != 2: print("Usage :") print(" theme-editor.py theme-name") @@ -127,6 +126,8 @@ def refresh_theme(): stats.Custom.stats() if config.THEME_DATA['STATS']['WEATHER'].get("INTERVAL", 0) > 0: stats.Weather.stats() + if config.THEME_DATA['STATS']['PING'].get("INTERVAL", 0) > 0: + stats.Ping.stats() if __name__ == "__main__":