Skip to content

Commit 8af2e13

Browse files
committed
Merge remote-tracking branch 'origin/main' into dev/492-systemuptime
2 parents 3cf76cc + 7388dae commit 8af2e13

19 files changed

+785
-244
lines changed

config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ config:
2626
ETH: "" # Ethernet Card
2727
WLO: "" # Wi-Fi Card
2828

29+
# CPU fan
30+
# For Linux/MacOS platforms, the CPU fan is amongst all fan sensors gathered from the motherboard chipset
31+
# If value is AUTO the system monitor will try to auto-select the CPU fan
32+
# If auto-detection fails, it might be necessary to manually indicate which fan is the CPU fan
33+
# Value must be 'controller/fan' e.g. 'nct6798/fan2'. Use configuration wizard for help in selection
34+
CPU_FAN: AUTO
35+
2936
display:
3037
# Display revision:
3138
# - A for Turing 3.5" and UsbPCMonitor 3.5"/5"

configure.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import sv_ttk
5252
from PIL import Image
5353
from serial.tools.list_ports import comports
54+
from tktooltip import ToolTip
5455
except:
5556
print(
5657
"[ERROR] Python dependencies not installed. Please follow start guide: https://github.com/mathoudebine/turing-smart-screen-python/wiki/System-monitor-:-how-to-start")
@@ -59,6 +60,8 @@
5960
except:
6061
os._exit(0)
6162

63+
from library.sensors.sensors_python import sensors_fans, is_cpu_fan
64+
6265
TURING_MODEL = "Turing Smart Screen"
6366
USBPCMONITOR_MODEL = "UsbPCMonitor"
6467
XUANFANG_MODEL = "XuanFang rev. B & flagship"
@@ -141,14 +144,28 @@ def get_net_if():
141144
return if_list
142145

143146

147+
def get_fans():
148+
fan_list = list()
149+
auto_detected_cpu_fan = "None"
150+
for name, entries in sensors_fans().items():
151+
for entry in entries:
152+
fan_list.append("%s/%s (%d%% - %d RPM)" % (name, entry.label, entry.percent, entry.current))
153+
if (is_cpu_fan(entry.label) or is_cpu_fan(name)) and auto_detected_cpu_fan == "None":
154+
auto_detected_cpu_fan = "Auto-detected: %s/%s" % (name, entry.label)
155+
156+
fan_list.insert(0, auto_detected_cpu_fan) # Add manual entry on top if auto-detection succeeded
157+
return fan_list
158+
159+
144160
class TuringConfigWindow:
145161
def __init__(self):
146162
self.window = Tk()
147163
self.window.title('Turing System Monitor configuration')
148-
self.window.geometry("770x550")
164+
self.window.geometry("770x570")
149165
self.window.iconphoto(True, PhotoImage(file="res/icons/monitor-icon-17865/64.png"))
150166
# When window gets focus again, reload theme preview in case it has been updated by theme editor
151167
self.window.bind("<FocusIn>", self.on_theme_change)
168+
self.window.after(0, self.on_fan_speed_update)
152169

153170
# Make TK look better with Sun Valley ttk theme
154171
sv_ttk.set_theme("light")
@@ -224,18 +241,29 @@ def __init__(self):
224241
self.wl_cb = ttk.Combobox(self.window, values=get_net_if(), state='readonly')
225242
self.wl_cb.place(x=500, y=415, width=250)
226243

244+
# For Windows platform only
227245
self.lhm_admin_warning = ttk.Label(self.window,
228246
text="❌ Restart as admin. or select another Hardware monitoring",
229247
foreground='#f00')
248+
# For platform != Windows
249+
self.cpu_fan_label = ttk.Label(self.window, text='CPU fan (?)')
250+
self.cpu_fan_label.config(foreground="#a3a3ff", cursor="hand2")
251+
self.cpu_fan_cb = ttk.Combobox(self.window, values=get_fans(), state='readonly')
252+
253+
self.tooltip = ToolTip(self.cpu_fan_label,
254+
msg="If \"None\" is selected, CPU fan was not auto-detected.\n"
255+
"Manually select your CPU fan from the list.\n\n"
256+
"Fans missing from the list? Install lm-sensors package\n"
257+
"and run 'sudo sensors-detect' command, then reboot.")
230258

231259
self.edit_theme_btn = ttk.Button(self.window, text="Edit theme", command=lambda: self.on_theme_editor_click())
232-
self.edit_theme_btn.place(x=310, y=490, height=50, width=130)
260+
self.edit_theme_btn.place(x=310, y=510, height=50, width=130)
233261

234262
self.save_btn = ttk.Button(self.window, text="Save settings", command=lambda: self.on_save_click())
235-
self.save_btn.place(x=450, y=490, height=50, width=130)
263+
self.save_btn.place(x=450, y=510, height=50, width=130)
236264

237265
self.save_run_btn = ttk.Button(self.window, text="Save and run", command=lambda: self.on_saverun_click())
238-
self.save_run_btn.place(x=590, y=490, height=50, width=130)
266+
self.save_run_btn.place(x=590, y=510, height=50, width=130)
239267

240268
self.config = None
241269
self.load_config_values()
@@ -261,7 +289,8 @@ def load_theme_preview(self):
261289
self.theme_author.config(text="Author: " + author_name)
262290
if author_name.startswith("@"):
263291
self.theme_author.config(foreground="#a3a3ff", cursor="hand2")
264-
self.theme_author.bind("<Button-1>", lambda e: webbrowser.open_new_tab("https://github.com/" + author_name[1:]))
292+
self.theme_author.bind("<Button-1>",
293+
lambda e: webbrowser.open_new_tab("https://github.com/" + author_name[1:]))
265294
else:
266295
self.theme_author.config(foreground="#a3a3a3", cursor="")
267296
self.theme_author.unbind("<Button-1>")
@@ -271,11 +300,16 @@ def load_config_values(self):
271300
with open("config.yaml", "rt", encoding='utf8') as stream:
272301
self.config, ind, bsi = ruamel.yaml.util.load_yaml_guess_indent(stream)
273302

303+
# Check if theme is valid
304+
if get_theme_data(self.config['config']['THEME']) is None:
305+
# Theme from config.yaml is not valid: use first theme available default size 3.5"
306+
self.config['config']['THEME'] = get_themes(SIZE_3_5_INCH)[0]
307+
274308
try:
275309
self.theme_cb.set(self.config['config']['THEME'])
276310
except:
277-
self.theme_cb.current(0)
278-
# self.load_theme_size()
311+
self.theme_cb.set("")
312+
279313
self.load_theme_preview()
280314

281315
try:
@@ -331,6 +365,14 @@ def load_config_values(self):
331365
except:
332366
self.brightness_slider.set(50)
333367

368+
try:
369+
if self.config['config']['CPU_FAN'] == "AUTO":
370+
self.cpu_fan_cb.current(0)
371+
else:
372+
self.cpu_fan_cb.set(self.config['config']['CPU_FAN'])
373+
except:
374+
self.cpu_fan_cb.current(0)
375+
334376
# Reload content on screen
335377
self.on_model_change()
336378
self.on_size_change()
@@ -353,6 +395,10 @@ def save_config_values(self):
353395
self.config['config']['COM_PORT'] = "AUTO"
354396
else:
355397
self.config['config']['COM_PORT'] = self.com_cb.get()
398+
if self.cpu_fan_cb.current() == 0:
399+
self.config['config']['CPU_FAN'] = "AUTO"
400+
else:
401+
self.config['config']['CPU_FAN'] = self.cpu_fan_cb.get().split(' ')[0]
356402
self.config['display']['REVISION'] = model_and_size_to_revision_map[(self.model_cb.get(), self.size_cb.get())]
357403
self.config['display']['DISPLAY_REVERSE'] = [k for k, v in reverse_map.items() if v == self.orient_cb.get()][0]
358404
self.config['display']['BRIGHTNESS'] = int(self.brightness_slider.get())
@@ -361,7 +407,6 @@ def save_config_values(self):
361407
ruamel.yaml.YAML().dump(self.config, file)
362408

363409
def on_theme_change(self, e=None):
364-
# self.load_theme_size()
365410
self.load_theme_preview()
366411

367412
def on_theme_editor_click(self):
@@ -417,11 +462,18 @@ def on_hwlib_change(self, e=None):
417462
import ctypes
418463
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
419464
if (hwlib == "LHM" or hwlib == "AUTO") and not is_admin:
420-
self.lhm_admin_warning.place(x=320, y=455)
465+
self.lhm_admin_warning.place(x=320, y=460)
421466
self.save_run_btn.state(["disabled"])
422467
else:
423468
self.lhm_admin_warning.place_forget()
424469
self.save_run_btn.state(["!disabled"])
470+
else:
471+
if hwlib == "PYTHON" or hwlib == "AUTO":
472+
self.cpu_fan_label.place(x=320, y=460)
473+
self.cpu_fan_cb.place(x=500, y=455, width=250)
474+
else:
475+
self.cpu_fan_label.place_forget()
476+
self.cpu_fan_cb.place_forget()
425477

426478
def show_hide_brightness_warning(self, e=None):
427479
if int(self.brightness_slider.get()) > 50 and self.model_cb.get() == TURING_MODEL and self.size_cb.get() == SIZE_3_5_INCH:
@@ -430,6 +482,14 @@ def show_hide_brightness_warning(self, e=None):
430482
else:
431483
self.brightness_warning_label.place_forget()
432484

485+
def on_fan_speed_update(self):
486+
# Update fan speed periodically
487+
prev_value = self.cpu_fan_cb.current() # Save currently selected index
488+
self.cpu_fan_cb.config(values=get_fans())
489+
if prev_value != -1:
490+
self.cpu_fan_cb.current(prev_value) # Force select same index to refresh displayed value
491+
self.window.after(500, self.on_fan_speed_update)
492+
433493

434494
if __name__ == "__main__":
435495
configurator = TuringConfigWindow()

library/display.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,17 @@ def display_static_text(self):
126126
text=config.THEME_DATA['static_text'][text].get("TEXT"),
127127
x=config.THEME_DATA['static_text'][text].get("X", 0),
128128
y=config.THEME_DATA['static_text'][text].get("Y", 0),
129+
width=config.THEME_DATA['static_text'][text].get("WIDTH", 0),
130+
height=config.THEME_DATA['static_text'][text].get("HEIGHT", 0),
129131
font=config.THEME_DATA['static_text'][text].get("FONT", "roboto-mono/RobotoMono-Regular.ttf"),
130132
font_size=config.THEME_DATA['static_text'][text].get("FONT_SIZE", 10),
131133
font_color=config.THEME_DATA['static_text'][text].get("FONT_COLOR", (0, 0, 0)),
132134
background_color=config.THEME_DATA['static_text'][text].get("BACKGROUND_COLOR", (255, 255, 255)),
133135
background_image=_get_full_path(config.THEME_DATA['PATH'],
134136
config.THEME_DATA['static_text'][text].get("BACKGROUND_IMAGE",
135137
None)),
136-
anchor="lt"
138+
align=config.THEME_DATA['static_text'][text].get("ALIGN", "left"),
139+
anchor=config.THEME_DATA['static_text'][text].get("ANCHOR", "lt"),
137140
)
138141

139142

library/lcd/lcd_comm.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ def DisplayText(
209209
text: str,
210210
x: int = 0,
211211
y: int = 0,
212+
width: int = 0,
213+
height: int = 0,
212214
font: str = "roboto-mono/RobotoMono-Regular.ttf",
213215
font_size: int = 20,
214216
font_color: Tuple[int, int, int] = (0, 0, 0),
@@ -249,12 +251,30 @@ def DisplayText(
249251
self.font_cache[(font, font_size)] = ImageFont.truetype("./res/fonts/" + font, font_size)
250252
font = self.font_cache[(font, font_size)]
251253
d = ImageDraw.Draw(text_image)
252-
left, top, right, bottom = d.textbbox((x, y), text, font=font, align=align, anchor=anchor)
253254

254-
# textbbox may return float values, which is not good for the bitmap operations below.
255-
# Let's extend the bounding box to the next whole pixel in all directions
256-
left, top = math.floor(left), math.floor(top)
257-
right, bottom = math.ceil(right), math.ceil(bottom)
255+
if width == 0 or height == 0:
256+
left, top, right, bottom = d.textbbox((x, y), text, font=font, align=align, anchor=anchor)
257+
258+
# textbbox may return float values, which is not good for the bitmap operations below.
259+
# Let's extend the bounding box to the next whole pixel in all directions
260+
left, top = math.floor(left), math.floor(top)
261+
right, bottom = math.ceil(right), math.ceil(bottom)
262+
else:
263+
left, top, right, bottom = x, y, x + width, y + height
264+
265+
if anchor.startswith("m"):
266+
x = (right + left) / 2
267+
elif anchor.startswith("r"):
268+
x = right
269+
else:
270+
x = left
271+
272+
if anchor.endswith("m"):
273+
y = (bottom + top) / 2
274+
elif anchor.endswith("b"):
275+
y = bottom
276+
else:
277+
y = top
258278

259279
# Draw text onto the background image with specified color & font
260280
d.text((x, y), text, font=font, fill=font_color, align=align, anchor=anchor)
@@ -327,6 +347,7 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int,
327347
max_value: int = 100,
328348
autoscale: bool = False,
329349
line_color: Tuple[int, int, int] = (0, 0, 0),
350+
line_width: int = 2,
330351
graph_axis: bool = True,
331352
axis_color: Tuple[int, int, int] = (0, 0, 0),
332353
background_color: Tuple[int, int, int] = (255, 255, 255),
@@ -397,7 +418,7 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int,
397418

398419
# Draw plot graph
399420
draw = ImageDraw.Draw(graph_image)
400-
draw.line(list(zip(plotsX, plotsY)), fill=line_color, width=2)
421+
draw.line(list(zip(plotsX, plotsY)), fill=line_color, width=line_width)
401422

402423
if graph_axis:
403424
# Draw axis

library/scheduler.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def wrap(
6969
**kwargs
7070
):
7171
""" Wrapper to create our schedule and run it at the appropriate time """
72+
if interval == 0:
73+
return
7274
scheduler = sched.scheduler(time.time, time.sleep)
7375
periodic(scheduler, interval, func)
7476
scheduler.run()
@@ -79,88 +81,88 @@ def wrap(
7981

8082

8183
@async_job("CPU_Percentage")
82-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['PERCENTAGE'].get("INTERVAL", None)).total_seconds())
84+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['PERCENTAGE'].get("INTERVAL", 0)).total_seconds())
8385
def CPUPercentage():
8486
""" Refresh the CPU Percentage """
8587
# logger.debug("Refresh CPU Percentage")
8688
stats.CPU.percentage()
8789

8890

8991
@async_job("CPU_Frequency")
90-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['FREQUENCY'].get("INTERVAL", None)).total_seconds())
92+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['FREQUENCY'].get("INTERVAL", 0)).total_seconds())
9193
def CPUFrequency():
9294
""" Refresh the CPU Frequency """
9395
# logger.debug("Refresh CPU Frequency")
9496
stats.CPU.frequency()
9597

9698

9799
@async_job("CPU_Load")
98-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['LOAD'].get("INTERVAL", None)).total_seconds())
100+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['LOAD'].get("INTERVAL", 0)).total_seconds())
99101
def CPULoad():
100102
""" Refresh the CPU Load """
101103
# logger.debug("Refresh CPU Load")
102104
stats.CPU.load()
103105

104106

105107
@async_job("CPU_Load")
106-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['TEMPERATURE'].get("INTERVAL", None)).total_seconds())
108+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['TEMPERATURE'].get("INTERVAL", 0)).total_seconds())
107109
def CPUTemperature():
108110
""" Refresh the CPU Temperature """
109111
# logger.debug("Refresh CPU Temperature")
110112
stats.CPU.temperature()
111113

112114

113115
@async_job("CPU_FanSpeed")
114-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['FAN_SPEED'].get("INTERVAL", None)).total_seconds())
116+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CPU']['FAN_SPEED'].get("INTERVAL", 0)).total_seconds())
115117
def CPUFanSpeed():
116118
""" Refresh the CPU Fan Speed """
117119
# logger.debug("Refresh CPU Fan Speed")
118120
stats.CPU.fan_speed()
119121

120122

121123
@async_job("GPU_Stats")
122-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['GPU'].get("INTERVAL", None)).total_seconds())
124+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['GPU'].get("INTERVAL", 0)).total_seconds())
123125
def GpuStats():
124126
""" Refresh the GPU Stats """
125127
# logger.debug("Refresh GPU Stats")
126128
stats.Gpu.stats()
127129

128130

129131
@async_job("Memory_Stats")
130-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['MEMORY'].get("INTERVAL", None)).total_seconds())
132+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['MEMORY'].get("INTERVAL", 0)).total_seconds())
131133
def MemoryStats():
132134
# logger.debug("Refresh memory stats")
133135
stats.Memory.stats()
134136

135137

136138
@async_job("Disk_Stats")
137-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DISK'].get("INTERVAL", None)).total_seconds())
139+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DISK'].get("INTERVAL", 0)).total_seconds())
138140
def DiskStats():
139141
# logger.debug("Refresh disk stats")
140142
stats.Disk.stats()
141143

142144

143145
@async_job("Net_Stats")
144-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['NET'].get("INTERVAL", None)).total_seconds())
146+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['NET'].get("INTERVAL", 0)).total_seconds())
145147
def NetStats():
146148
# logger.debug("Refresh net stats")
147149
stats.Net.stats()
148150

149151

150152
@async_job("Date_Stats")
151-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DATE'].get("INTERVAL", None)).total_seconds())
153+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['DATE'].get("INTERVAL", 0)).total_seconds())
152154
def DateStats():
153155
# logger.debug("Refresh date stats")
154156
stats.Date.stats()
155157

156158
@async_job("SystemUptime_Stats")
157-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['UPTIME'].get("INTERVAL", None)).total_seconds())
159+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['UPTIME'].get("INTERVAL", 0)).total_seconds())
158160
def SystemUptimeStats():
159161
# logger.debug("Refresh system uptime stats")
160162
stats.SystemUptime.stats()
161163

162164
@async_job("Custom_Stats")
163-
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", None)).total_seconds())
165+
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", 0)).total_seconds())
164166
def CustomStats():
165167
# print("Refresh custom stats")
166168
stats.Custom.stats()

0 commit comments

Comments
 (0)