Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Add] Ping and Weather custom sensor + ColoredFlat Theme #620

Merged
merged 18 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
17 changes: 16 additions & 1 deletion 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

# IP address used for ping sensor. Can be external (e.g. 8.8.8.8) or internal (e.g. 192.168.x.x)
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 All @@ -42,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: A
REVISION: C

# Display Brightness
# Set this as the desired %, 0 being completely dark and 100 being max brightness
Expand Down
6 changes: 6 additions & 0 deletions library/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ def CustomStats():
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("Queue_Handler")
@schedule(timedelta(milliseconds=1).total_seconds())
def QueueHandler():
Expand Down
37 changes: 36 additions & 1 deletion library/sensors/sensors_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@

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
Expand All @@ -47,6 +51,37 @@ 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):
Expand Down Expand Up @@ -95,4 +130,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
pass
75 changes: 75 additions & 0 deletions library/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import babel.dates
from psutil._common import bytes2human
from uptime import uptime
import requests

import library.config as config
from library.display import display
Expand Down Expand Up @@ -824,3 +825,77 @@
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:
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:
try:
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}"
except Exception as e:
logger.error(str(e))
desc = "Error fetching weather"
else:
print(f"Failed to fetch weather data. Status code: {response.status_code}")
print(f"Response content: {response.content}")
Fixed Show fixed Hide fixed
logger.error(response.text)
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
desc = response.json().get('message')
except:
desc = "Connection error"

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
display_themed_value(theme_data=wdescription_theme_data, value=desc)
1 change: 1 addition & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def on_win32_wm_event(hWnd, msg, wParam, lParam):
scheduler.DateStats()
scheduler.SystemUptimeStats()
scheduler.CustomStats()
scheduler.WeatherStats()
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