터치 패널 기록 그래프 재구성

This commit is contained in:
seo committed 2026-08-01 11:48:23 +09:00
1 parent 5f1cc4cc7f
commit 11e29d9954
2 files changed
+65 -96

No files matched your search

+63 -94
View File
@@ -14,7 +14,6 @@ from PyQt5.QtWidgets import (
QHBoxLayout,
QLabel,
QMainWindow,
QSlider,
QVBoxLayout,
QWidget,
)
@@ -172,22 +171,6 @@ def cpu_temp():
return round(raw / 1000, 1) if raw > 0 else 0.0
def memory_percent():
rows = {}
for line in read_text("/proc/meminfo").splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)
parts = value.strip().split()
if parts and parts[0].isdigit():
rows[key] = int(parts[0])
total = rows.get("MemTotal", 0)
available = rows.get("MemAvailable", 0)
if total <= 0:
return 0.0
return round((total - available) / total * 100, 1)
def fan_paths():
for pwm in Path("/sys/devices/platform/cooling_fan/hwmon").glob("hwmon*/pwm1"):
base = pwm.parent
@@ -471,24 +454,6 @@ class PcWakeButton(StatusTile):
self.error_dot.tick()
class MetricCard(QFrame):
def __init__(self, title):
super().__init__()
self.setObjectName("metricCard")
self.title = QLabel(title)
self.title.setObjectName("metricTitle")
self.value = QLabel("-")
self.value.setObjectName("metricValue")
layout = QVBoxLayout(self)
layout.setContentsMargins(10, 8, 10, 8)
layout.setSpacing(2)
layout.addWidget(self.title)
layout.addWidget(self.value)
def set_value(self, text):
self.value.setText(text)
class Sparkline(QFrame):
def __init__(self, title, color="#38bdf8", second_color=None, y_unit="", right_unit="", auto_byte_unit=False, max_points=120):
super().__init__()
@@ -592,13 +557,13 @@ class TouchPanel(QMainWindow):
def __init__(self):
super().__init__()
self.rows = {}
self.metrics = {}
self.charts = {}
self.fan = fan_paths()
self.wan_iface = default_iface()
NET_GROUPS["wan"] = [self.wan_iface]
self.last_net = {}
self.last_synology = {}
self.last_synology_net_rate = {}
self.synology_buffer = ""
self.synology_values = {}
self.synology_disk_temps = {}
@@ -608,17 +573,20 @@ class TouchPanel(QMainWindow):
self.last_sample = None
self.last_graph = None
self.current_rates = {"wan": {"rx": 0.0, "tx": 0.0}}
self.current_synology_rates = {"wan": {"rx": 0.0, "tx": 0.0}}
self.disk_temp = None
self.current_pwm = read_int(self.fan["pwm"])
self.latest_temp = 0.0
self.latest_rpm = 0
self.latest_pwm = self.current_pwm
self.latest_percent = 0.0
self.temp_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.rpm_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.net_history = {"wan": {"rx": deque(maxlen=WAN_GRAPH_POINTS), "tx": deque(maxlen=WAN_GRAPH_POINTS)}}
self.rpi_temp_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.rpi_rpm_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.synology_temp_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.synology_rpm_history = deque(maxlen=TEMP_GRAPH_POINTS)
self.net_history = {
"wan": {"rx": deque(maxlen=WAN_GRAPH_POINTS), "tx": deque(maxlen=WAN_GRAPH_POINTS)},
"synology_wan": {"rx": deque(maxlen=WAN_GRAPH_POINTS), "tx": deque(maxlen=WAN_GRAPH_POINTS)},
}
self.setWindowTitle("Standalone Touch Dashboard")
self.setWindowTitle("Standalone Touch Records")
self.setCursor(Qt.BlankCursor)
self.setMinimumSize(800, 480)
self._build_ui()
@@ -751,7 +719,7 @@ class TouchPanel(QMainWindow):
right_layout.setSpacing(8)
header = QHBoxLayout()
heading = QLabel("Dashboard")
heading = QLabel("기록")
heading.setObjectName("sectionTitle")
self.clock = QLabel("--:--:--")
self.clock.setObjectName("clock")
@@ -763,30 +731,14 @@ class TouchPanel(QMainWindow):
header.addWidget(self.clock)
right_layout.addLayout(header)
metric_grid = QGridLayout()
metric_grid.setHorizontalSpacing(8)
metric_grid.setVerticalSpacing(8)
for index, (key, title) in enumerate([("rpm", "팬 RPM"), ("temp", "CPU 온도"), ("pwm", "PWM"), ("mem", "메모리")]):
card = MetricCard(title)
self.metrics[key] = card
metric_grid.addWidget(card, 0, index)
right_layout.addLayout(metric_grid)
slider_wrap = QHBoxLayout()
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 255)
self.slider.setValue(self.current_pwm)
self.slider.setFocusPolicy(Qt.NoFocus)
self.slider.setAttribute(Qt.WA_TransparentForMouseEvents, True)
slider_wrap.addWidget(self.slider, 1)
right_layout.addLayout(slider_wrap)
chart_grid = QGridLayout()
chart_grid.setHorizontalSpacing(8)
chart_grid.setVerticalSpacing(8)
for index, (key, chart) in enumerate([
("temp", Sparkline("온도 / RPM", "#f97316", "#38bdf8", "C", "rpm", max_points=TEMP_GRAPH_POINTS)),
("wan", Sparkline("WAN 트래픽", "#f97316", "#22c55e", auto_byte_unit=True, max_points=WAN_GRAPH_POINTS)),
("rpi_temp", Sparkline("라즈베리파이 온도 / RPM", "#f97316", "#38bdf8", "C", "rpm", max_points=TEMP_GRAPH_POINTS)),
("rpi_wan", Sparkline("라즈베리파이 UPLINK 트래픽", "#f97316", "#22c55e", auto_byte_unit=True, max_points=WAN_GRAPH_POINTS)),
("synology_temp", Sparkline("시놀로지 온도 / RPM", "#f97316", "#38bdf8", "C", "rpm", max_points=TEMP_GRAPH_POINTS)),
("synology_wan", Sparkline("시놀로지 UPLINK 트래픽", "#f97316", "#22c55e", auto_byte_unit=True, max_points=WAN_GRAPH_POINTS)),
]):
self.charts[key] = chart
chart_grid.addWidget(chart, index, 0)
@@ -862,32 +814,16 @@ class TouchPanel(QMainWindow):
def update_fan_status(self):
rpm = read_int(self.fan["rpm"])
pwm = read_int(self.fan["pwm"])
percent = pwm / 255 * 100 if pwm > 0 else 0
self.render_rpi_fan(rpm)
self.latest_rpm = rpm
self.latest_pwm = pwm
self.latest_percent = percent
def update_dashboard(self):
rpm = getattr(self, "latest_rpm", read_int(self.fan["rpm"]))
temp = getattr(self, "latest_temp", 0.0)
pwm = getattr(self, "latest_pwm", read_int(self.fan["pwm"]))
percent = getattr(self, "latest_percent", pwm / 255 * 100 if pwm > 0 else 0)
mem_percent = memory_percent()
self.metrics["rpm"].set_value(f"{rpm:,}")
self.metrics["temp"].set_value(f"{temp:.1f} C")
self.metrics["pwm"].set_value(f"{percent:.1f}%")
self.metrics["mem"].set_value(f"{mem_percent:.1f}%")
self.slider.blockSignals(True)
self.slider.setValue(pwm)
self.slider.blockSignals(False)
self.clock.setText(time.strftime("%H:%M:%S"))
self.update_temp_graph()
self.update_temp_graphs(temp, rpm)
def ping_pc(self):
if self.pc_ping.state() != QProcess.NotRunning:
@@ -1167,24 +1103,62 @@ class TouchPanel(QMainWindow):
write_activity = previous is not None and rx > previous[0]
error_activity = previous is not None and (errors > previous[2] or drops > previous[3])
self.rows[key].set_state(self.drop_detail(drops, detail), error_activity, read_activity, write_activity)
if key == "synology_wan":
self.update_synology_wan_rate(rx, tx)
def update_temp_graph(self):
temp = getattr(self, "latest_temp", 0.0)
rpm = getattr(self, "latest_rpm", read_int(self.fan["rpm"]))
self.temp_history.append(temp)
self.rpm_history.append(rpm)
self.charts["temp"].set_values(self.temp_history, format_temp_rpm_caption(temp, rpm), self.rpm_history)
def update_synology_wan_rate(self, rx, tx):
now = time.monotonic()
previous = self.last_synology_net_rate.get("wan")
if previous is not None:
prev_rx, prev_tx, prev_time = previous
dt = max(0.001, now - prev_time)
self.current_synology_rates["wan"] = {
"rx": max(0.0, (rx - prev_rx) / dt),
"tx": max(0.0, (tx - prev_tx) / dt),
}
self.last_synology_net_rate["wan"] = (rx, tx, now)
self.update_synology_wan_graph()
def update_temp_graphs(self, rpi_temp, rpi_rpm):
synology_temp = getattr(self, "synology_cpu_temp", 0.0)
synology_rpm = getattr(self, "synology_fan_rpm", 0)
self.rpi_temp_history.append(rpi_temp)
self.rpi_rpm_history.append(rpi_rpm)
self.charts["rpi_temp"].set_values(
self.rpi_temp_history,
format_temp_rpm_caption(rpi_temp, rpi_rpm),
self.rpi_rpm_history,
)
self.synology_temp_history.append(synology_temp)
self.synology_rpm_history.append(synology_rpm)
self.charts["synology_temp"].set_values(
self.synology_temp_history,
format_temp_rpm_caption(synology_temp, synology_rpm),
self.synology_rpm_history,
)
def update_wan_graph(self):
wan = self.current_rates.get("wan", {"rx": 0.0, "tx": 0.0})
self.net_history["wan"]["rx"].append(wan["rx"])
self.net_history["wan"]["tx"].append(wan["tx"])
self.charts["wan"].set_values(
self.charts["rpi_wan"].set_values(
self.net_history["wan"]["rx"],
format_wan_caption(wan["rx"], wan["tx"]),
self.net_history["wan"]["tx"],
)
def update_synology_wan_graph(self):
wan = self.current_synology_rates.get("wan", {"rx": 0.0, "tx": 0.0})
self.net_history["synology_wan"]["rx"].append(wan["rx"])
self.net_history["synology_wan"]["tx"].append(wan["tx"])
self.charts["synology_wan"].set_values(
self.net_history["synology_wan"]["rx"],
format_wan_caption(wan["rx"], wan["tx"]),
self.net_history["synology_wan"]["tx"],
)
def tick_pulses(self):
for row in self.rows.values():
row.tick()
@@ -1220,16 +1194,11 @@ def main():
#statusDetail { font-size: 9px; color: #475569; background: transparent; }
#pcWake { background: transparent; border: 0; }
#pcLabel { color: #dce8f8; font-size: 13px; font-weight: 700; background: transparent; }
#metricCard, #chartCard { background: #111a2e; border: 1px solid #27364f; border-radius: 12px; }
#metricTitle { color: #8fa7c5; font-size: 13px; background: transparent; }
#metricValue { color: #f8fafc; font-size: 24px; font-weight: 800; background: transparent; }
#chartCard { background: #111a2e; border: 1px solid #27364f; border-radius: 12px; }
#chartTitle { color: #dce8f8; font-size: 14px; font-weight: 700; background: transparent; }
#chartCaption { color: #93a4bd; font-size: 11px; background: transparent; font-family: "DejaVu Sans Mono", monospace; }
#clock { font-size: 18px; color: #8fb3ff; font-weight: 700; }
#statusText { color: #93a4bd; font-size: 13px; background: transparent; }
QSlider::groove:horizontal { height: 10px; background: #263650; border-radius: 5px; }
QSlider::sub-page:horizontal { background: #22c55e; border-radius: 5px; }
QSlider::handle:horizontal { width: 28px; margin: -10px 0; border-radius: 14px; background: #f8fafc; }
"""
)
win = TouchPanel()