基于树莓派的机箱硬件监控副屏 您所在的位置:网站首页 电脑副屏diy 基于树莓派的机箱硬件监控副屏

基于树莓派的机箱硬件监控副屏

2023-06-20 13:38| 来源: 网络整理| 查看: 265

一、简介

监控器效果如图所示:

电脑端的服务器ui:

思路就是在电脑端建立一个小服务器读取硬件数据,然后通过局域网发送给树莓派上的客户端用以显示。

二、原理

首先读取硬件数据可以使用OpenHardwareMonitor,它会自动读取硬件数据并且将其统一注册到WMI(Windows Management Instrumentation) 里面的命名空间为 root\\OpenHardwareMonitor 下的一个叫做Sensor的类里面。

电脑端上的服务器就可以通过建立WMI对象来获取这些数据,同时树莓派上的客户端则不断地通过局域网向服务器发送连接请求,获取到数据之后再显示到界面上。用局域网的好处是接线简单,只需要往机箱内拉一根树莓派的电源线即可。

三、实现 1. 客户端(树莓派)

我使用的是树莓派4b 2g,同时配置了一个带4寸触摸屏的保护壳。操作系统是官方的 Raspberry Pi OS,刷机过程在此略过。

用QtDesigner设计了一个简单的显示界面:

图标文件统一放在resource文件夹下,图标都是我自己随便画的,画得不好还请见谅 - -

用pyside2-uic把ui文件转换成python代码:

pyside2-uic monitor_ui.ui > monitor_ui.py

其自动生成的monitor_ui.py这里就不贴出来了。接下来是Monitor类的具体实现:

from monitor_ui import * import socket import datetime class InternalTimer(QThread): """ A simple timer thread that emits signal periodically """ clk_sig = Signal() def __init__(self, ms): super(InternalTimer, self).__init__() self.ms = ms def run(self): while True: self.clk_sig.emit() self.msleep(self.ms) if self.isInterruptionRequested(): break def qWait(ms): """ simple wait function without blocking UI like PyQt5.QtTest.QTest.qWait() """ end = datetime.datetime.now() + datetime.timedelta(milliseconds=ms) while datetime.datetime.now() < end: QApplication.processEvents() class SignalReceiver(QThread): """ Signal receiver thread """ sig = Signal(str) def __init__(self, host, port, ms): super(SignalReceiver, self).__init__() self.ms = ms self.host = host self.port = port def run(self): while True: data = '' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # try to establish a connection and receive data s.connect((self.host, self.port)) data = s.recv(2048).decode('utf-8') s.close() except Exception as e: pass self.sig.emit(data) self.msleep(self.ms) if self.isInterruptionRequested(): break class Monitor(Ui_monitor, QMainWindow): def __init__(self, host, port): super(Monitor, self).__init__() self.setupUi(self) self.empty_values = '--,--,--,--,--,--,--,--,--,--,--,--' self.host = host self.port = port self.weekday_abbr = ('Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.', 'Sun.') # setup timer and signal receiver self.clk = InternalTimer(50) self.clk.clk_sig.connect(self.clk_triggered) self.signal_receiver = SignalReceiver(host, port, 500) self.signal_receiver.sig.connect(self.data_received) self.clk.start() self.signal_receiver.start() self.data_received(self.empty_values) self.showFullScreen() def clk_triggered(self): # update clock now = datetime.datetime.now() current_time = now.strftime("%H:%M:%S") cur_date = now.strftime('%b %d') cur_day = now.weekday() self.clock_lbl.setText(current_time) self.datetime_lbl.setText('%s\n%s' % (self.weekday_abbr[cur_day], cur_date)) def keyPressEvent(self, e): # press q to exit if e.key() == Qt.Key_Q: self.close() e.accept() def closeEvent(self, e): self.clk.requestInterruption() self.signal_receiver.requestInterruption() self.clk.terminate() self.signal_receiver.terminate() e.accept() def data_received(self, data: str): if data == '': data = self.empty_values if len(data) > 8 and data[:8] == '[DEBUG] ': print(data) # for debugging else: # split data # these are specific sensor data of my PC cpu_temp, cpu_load, cpu_power, gpu_temp, gpu_load, gpu_power, \ gpu_fan, ram_load, hdd1_load, hdd2_load, ssd1_load, ssd1_temp = data.split(',') self.cpu_load_lbl.setText('%s%%' % cpu_load) self.cpu_temp_lbl.setText('%s\u00b0C' % cpu_temp) self.cpu_power_lbl.setText('%s W' % cpu_power) self.gpu_temp_lbl.setText('%s\u00b0C' % gpu_temp) self.gpu_load_lbl.setText('%s%%' % gpu_load) self.gpu_power_lbl.setText('%s W' % gpu_power) self.gpu_fan_lbl.setText('%s rpm' % gpu_fan) self.ram_load_lbl.setText('%s%%' % ram_load) self.hdd1_load_lbl.setText('%s%%' % hdd1_load) self.hdd2_load_lbl.setText('%s%%' % hdd2_load) self.ssd_load_lbl.setText('%s%%' % ssd1_load)

 最后是程序入口launch.py

host = '192.168.178.44' port = 4005 try: import os import sys from monitor import * def start(): app = QApplication(sys.argv) monitor = Monitor(host, port) sys.exit(app.exec_()) if __name__ == "__main__": start() except Exception as e: print(e) input('press any key to continue...\n')

开发ui我用的是pyside2,不过目前树莓派上无法直接一步到位安装pyside2,还好根据这篇帖子:https://forum.qt.io/topic/112813/installing-pyside2-on-raspberry-pi/2 可以用如下命令安装,其实就是安装所有的组件库:

sudo apt-get install python3-pyside2.qt3dcore python3-pyside2.qt3dinput python3-pyside2.qt3dlogic python3-pyside2.qt3drender python3-pyside2.qtcharts python3-pyside2.qtconcurrent python3-pyside2.qtcore python3-pyside2.qtgui python3-pyside2.qthelp python3-pyside2.qtlocation python3-pyside2.qtmultimedia python3-pyside2.qtmultimediawidgets python3-pyside2.qtnetwork python3-pyside2.qtopengl python3-pyside2.qtpositioning python3-pyside2.qtprintsupport python3-pyside2.qtqml python3-pyside2.qtquick python3-pyside2.qtquickwidgets python3-pyside2.qtscript python3-pyside2.qtscripttools python3-pyside2.qtsensors python3-pyside2.qtsql python3-pyside2.qtsvg python3-pyside2.qttest python3-pyside2.qttexttospeech python3-pyside2.qtuitools python3-pyside2.qtwebchannel python3-pyside2.qtwebsockets python3-pyside2.qtwidgets python3-pyside2.qtx11extras python3-pyside2.qtxml python3-pyside2.qtxmlpatterns python3-pyside2uic

 安装完成后客户端在树莓派上运行的效果如图:

2. 服务器端(Windows)

服务器端使用C++来实现。需要说明的是,开发Qt应用的话用QtCreator会更方便一些,因为可以自动生成编译所需的文件,还能自动生成slot和signal的代码,等等。不过我还是更习惯vscode的IDE,所以这里参考了另一位大神的教程:https://blog.csdn.net/weixin_43669941/article/details/108921714,先用Qt Creator生成一个名为Sender的项目框架,然后再在vscode里面用cmake来build。

为了获取电脑的传感器数据,需要使用WMI对象。使用C++创建WMI对象并获取数据的过程较为繁琐,这里在参考官方教程的基础上打包了一个类WMI,可以用自定义的命名空间构造,用init()初始化后可以用exec_query()执行具体的检索命令。

WMI.h:

// headers for WMI #define _WIN32_DCOM #include using namespace std; #include #include #pragma comment(lib, "wbemuuid.lib") class WMI { private: const wchar_t *ns; IWbemServices *pSvc; IWbemLocator *pLoc; IEnumWbemClassObject *pEnumerator; public: WMI(const wchar_t *ns); ~WMI(); int init(); double exec_query(const wchar_t *query); void release(); };

 然后是WMI.cpp:

#include "WMI.h" WMI::WMI(const wchar_t *ns) : ns(ns) { pSvc = NULL; pLoc = NULL; pEnumerator = NULL; } int WMI::init() { // Reference: https://docs.microsoft.com/en-us/windows/win32/wmisdk/example-creating-a-wmi-application // Some changes are made HRESULT hres; // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ hres = CoInitialize(NULL); // different from docs if (FAILED(hres)) { cout setMaxPendingConnections(1); connect(server.get(), SIGNAL(newConnection()), this, SLOT(newConnection())); wmi.reset(new WMI(L"root\\OpenHardwareMonitor")); if (wmi->init() == 0) { showMsg("WMI initialized"); //init server if (!server->listen(QHostAddress::Any, port)) { showMsg("Failed to init server"); } else { showMsg("Server initialized"); } } else { showMsg("Failed to init WMI"); } // init query list query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/intelcpu/0/temperature/8'"); //cpu temp query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/intelcpu/0/load/0'"); //cpu load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/intelcpu/0/power/0'"); //cpu power query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/nvidiagpu/0/temperature/0'"); //gpu_temp query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/nvidiagpu/0/load/4'"); //gpu_load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/nvidiagpu/0/power/0'"); //gpu power query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/nvidiagpu/0/fan/0'"); //gpu fan query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/ram/load/0'"); //ram load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/hdd/0/load/0'"); //hdd1 load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/hdd/1/load/0'"); //hdd2 load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/hdd/2/load/0'"); //ssd1 load query_list.push_back(L"SELECT * FROM Sensor WHERE Identifier = '/hdd/2/temperature/0'"); //ssd1 temp // start clk clk->start(); showMsg("Welcome"); } Sender::~Sender() { server->close(); wmi->release(); clk->requestInterruption(); clk->terminate(); tray_icon->hide(); delete ui; } void Sender::clkReceived() { // periodically set has_new_connection to false, any new connection will zero the counter new_conn_time += clk_ms; if (new_conn_time > new_conn_max_time) has_new_connection = false; if (has_new_connection) { ui->tml_status_lbl->setText("online"); ui->tml_status_lbl->setStyleSheet("QLabel{color: green;}"); } else { ui->tml_status_lbl->setText("offline"); ui->tml_status_lbl->setStyleSheet("QLabel{color: red;}"); } if (server_started) { ui->srv_status_lbl->setText("online"); ui->srv_status_lbl->setStyleSheet("QLabel{color: green;}"); } else { ui->srv_status_lbl->setText("offline"); ui->srv_status_lbl->setStyleSheet("QLabel{color: red;}"); } } void Sender::showMsg(const char *msg, int timeout) { ui->statusbar->showMessage(msg, timeout); } void Sender::open_btn_clicked() { // start server server_started = true; showMsg("Server started"); } void Sender::close_btn_clicked() { // shut down server server_started = false; showMsg("Server shut down"); } void Sender::newConnection() { has_new_connection = true; new_conn_time = 0; // zero the counter QTcpSocket *socket = server->nextPendingConnection(); if (server_started) { string msg = ""; for (size_t i = 0; i < query_list.size(); ++i) { double val = wmi->exec_query(query_list[i]); char buf[100]; if (i != 6) { if (i != query_list.size() - 1) sprintf(buf, "%.1f,", val); else sprintf(buf, "%.1f", val); } else { sprintf(buf, "%d,", val); } msg += string(buf); } socket->write(msg.c_str()); socket->flush(); // socket->waitForBytesWritten(100); } else{ socket->disconnectFromHost(); } } void Sender::run_in_bg_action_triggered() { hide(); // run in background }

由于加入了自定义的库(WMI)以及一些资源文件,此前由QtCreator自动生成的CMakeLists.txt也需要做一些改动以顺利编译:

cmake_minimum_required(VERSION 3.5) project(sender VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # QtCreator supports the following variables for Android, which are identical to qmake Android variables. # Check https://doc.qt.io/qt/deployment-android.html for more information. # They need to be set before the find_package( ...) calls below. #if(ANDROID) # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") # if (ANDROID_ABI STREQUAL "armeabi-v7a") # set(ANDROID_EXTRA_LIBS # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) # endif() #endif() find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network REQUIRED) set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/icon.rc") set(PROJECT_SOURCES main.cpp sender.cpp sender.h sender.ui resource.qrc ${APP_ICON_RESOURCE_WINDOWS} ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(sender MANUAL_FINALIZATION ${PROJECT_SOURCES} ) else() if(ANDROID) add_library(sender SHARED ${PROJECT_SOURCES} ) else() add_executable(sender ${PROJECT_SOURCES} ) endif() endif() add_library(WMI WMI.cpp) target_link_libraries(sender PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network WMI) target_link_options(sender PRIVATE -mwindows) set_target_properties(sender PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} ) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(sender) endif()

用cmake编译后,在build文件夹里找到sender.exe并运行,完成!

基于树莓派的电脑硬件监控器

服务器和客户端随时开闭互不干扰,同时服务器端可以实时读取客户端的状态(是否在线)。

四、问题总结

1. 用C++获取WMI对象比较繁琐而且容易出错,相比之下python就很方便:

import wmi w = wmi.WMI(namespace="root\OpenHardwareMonitor") infos = w.Sensor()

2. 关于qt的.qrc资源文件,需要调用rcc来转换成对应的代码才能在代码中使用。这里遇到了一个问题,我试图把客户端所用到的所有图标打包到resource.qrc里面,然后用pyside2-rcc把resource.qrc转换成resource_rc.py,最后让客户端应用从这个py文件里面载入图标。然而经过测试,在windows下应用可以正常载入里面的图标,在树莓派里面却不行,所以最后只能放弃使用qrc而是直接从resource文件夹里面载入图片。不知道这个问题是否有大神知道如何解决?

3. 服务器端的settings功能尚未实现,计划之后加入一些基本的设置选项,例如刷新频率,检测终端状态的timeout,等等。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有