PC Stats sur RPI0

Salut, encore et toujours un projet.

Cette fois, c’est pour afficher les informations de mon PC sur un Raspberry. J’ai déjà vaguement parlé dans d’autres message de cet envie, comme j’ai trouvé des projets déjà fait (comme Mod-Bros) .
L’idée est en 3 parties; source des données, rendu des donnés, connexion des appareils.

Prérequis sous Windows

  • Python 3.7+ installé en environnement système (accessible en ligne de commande normal) avec PIP
  • Selon « pipreqs », rien n’est requis spécifiquement pour la partie serveur.

Prérequis sous Raspberry (merci de m’indiquer les installation manquantes, si il y a)

sudo apt -qq install -y --no-install-recommends xserver-xorg x11-xserver-utils xinit openbox
sudo apt -qq install -y python3-dev python3-pip samba
sudo apt -qq install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 python3-numpy
pip3 install --upgrade pip setuptools wheel numpy PyGObject requests

La source des données

Bien sur, c’est à l’ordinateur des fournirs, j’ai un programme que j’utilise qui couvre tout les informations essentiels, les avantages est qu’il est gratuit et que les données sont disponibles rapidement et simplement. Les données sont enregistré dans la base de registre, alors je n’ai besoin que de lire la base du registre pour obtenir mes informations.

Après il faut les communiquer au Raspberry. J’utilise dont un principe simple, un serveur Web. Comme ça les donnés sont disponibles et le Raspberry viendra les chercher de lui-même. Accessoirement, le serveur Web peut fournir des fichiers depuis le dossier pcstats/pc/web/.

pcstats/pc/pcstats.py

#!/usr/bin/python3
import urllib.parse as urlparse
import http.server
import socketserver
import os
import subprocess
import configparser
import json
import datetime
import time
from threading import Timer
from urllib.parse import parse_qs
from os import path
from datetime import datetime as dt
from os import listdir
from os.path import isfile, join, isdir
import winreg

config = configparser.ConfigParser()
config.read('config.ini')
WEBPATH=config['system']['path']+"web"
os.chdir(WEBPATH)

class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        pathSplit = self.path.split("?")
        pathSection = pathSplit[0].split("/")
        if self.path == '/':
            self.path = '/index.html'
            print(self.path);
            return http.server.SimpleHTTPRequestHandler.do_GET(self)
        elif path.exists(WEBPATH+pathSplit[0]) is True:
            self.path = pathSplit[0]
            return http.server.SimpleHTTPRequestHandler.do_GET(self)
        
        elif pathSection[1] == "api":
            self.send_response(200)
            self.send_header("Content-type", "application/json")
            self.end_headers()
            wanted=pathSection[2]
            outputJson={"result":str(self.getRegistry(config['monitors'][wanted+"_type"],config['monitors'][wanted]))}
            return self.wfile.write(bytes(json.dumps(outputJson), "utf-8"))
        
        elif pathSection[1] == "stats":
            self.send_response(200)
            self.send_header("Content-type", "application/json")
            self.end_headers()
            outputJson={
                "pcname":str(self.getRegistry(config['monitors']["pcname_type"],config['monitors']["pcname"])),
                "cpuname":str(self.getRegistry(config['monitors']["cpuname_type"],config['monitors']["cpuname"])),
                "cpuspeed":str(self.getRegistry(config['monitors']["cpuspeed_type"],config['monitors']["cpuspeed"])),
                "cpubus":str(self.getRegistry(config['monitors']["cpubus_type"],config['monitors']["cpubus"])),
                "cpumulti":str(self.getRegistry(config['monitors']["cpumulti_type"],config['monitors']["cpumulti"])),
                "cpuload":str(self.getRegistry(config['monitors']["cpuload_type"],config['monitors']["cpuload"])),
                "cputemp":str(self.getRegistry(config['monitors']["cputemp_type"],config['monitors']["cputemp"])),
                "cpupower":str(self.getRegistry(config['monitors']["cpupower_type"],config['monitors']["cpupower"])),
                "fancpu":str(self.getRegistry(config['monitors']["fancpu_type"],config['monitors']["fancpu"])),
                "fansys1":str(self.getRegistry(config['monitors']["fansys1_type"],config['monitors']["fansys1"])),
                "fansys2":str(self.getRegistry(config['monitors']["fansys2_type"],config['monitors']["fansys2"])),
                "fangpu":str(self.getRegistry(config['monitors']["fangpu_type"],config['monitors']["fangpu"])),
                "sysname":str(self.getRegistry(config['monitors']["sysname_type"],config['monitors']["sysname"])),
                "syspower":str(self.getRegistry(config['monitors']["syspower_type"],config['monitors']["syspower"])),
                "systemp1":str(self.getRegistry(config['monitors']["systemp1_type"],config['monitors']["systemp1"])),
                "systemp2":str(self.getRegistry(config['monitors']["systemp2_type"],config['monitors']["systemp2"])),
                "ramname":str(self.getRegistry(config['monitors']["ramname_type"],config['monitors']["ramname"])),
                "ramload":str(self.getRegistry(config['monitors']["ramload_type"],config['monitors']["ramload"])),
                "ramuse":str(self.getRegistry(config['monitors']["ramuse_type"],config['monitors']["ramuse"])),
                "ramfree":str(self.getRegistry(config['monitors']["ramfree_type"],config['monitors']["ramfree"])),
                "ramtotal":str(self.getRegistry(config['monitors']["ramtotal_type"],config['monitors']["ramtotal"])),
                "gpuname":str(self.getRegistry(config['monitors']["gpuname_type"],config['monitors']["gpuname"])),
                "gpucorespeed":str(self.getRegistry(config['monitors']["gpucorespeed_type"],config['monitors']["gpucorespeed"])),
                "gpuvideospeed":str(self.getRegistry(config['monitors']["gpuvideospeed_type"],config['monitors']["gpuvideospeed"])),
                "gpupower":str(self.getRegistry(config['monitors']["gpupower_type"],config['monitors']["gpupower"])),
                "gpuload":str(self.getRegistry(config['monitors']["gpuload_type"],config['monitors']["gpuload"])),
                "gputemp":str(self.getRegistry(config['monitors']["gputemp_type"],config['monitors']["gputemp"])),
                "vramname":str(self.getRegistry(config['monitors']["vramname_type"],config['monitors']["vramname"])),
                "vramload":str(self.getRegistry(config['monitors']["vramload_type"],config['monitors']["vramload"])),
                "vramuse":str(self.getRegistry(config['monitors']["vramuse_type"],config['monitors']["vramuse"])),
                "vramfree":str(self.getRegistry(config['monitors']["vramfree_type"],config['monitors']["vramfree"])),
                "vramtotal":str(self.getRegistry(config['monitors']["vramtotal_type"],config['monitors']["vramtotal"])),
                "vramspeed":str(self.getRegistry(config['monitors']["vramspeed_type"],config['monitors']["vramspeed"]))
                }
            return self.wfile.write(bytes(json.dumps(outputJson), "utf-8"))
        
        else:
            self.send_response(404)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(bytes('Document requested is not found.', "utf-8"))
        return
        
    def getData(self,data,url):
        parsed = urlparse.urlparse("http://localhost"+url)
        try:
            return str(parse_qs(parsed.query)[data]).replace("['","").replace("']","")
        except:
            return ""
            pass

       
    def getRegistry(self,rType,rKey):
        keyPath = r"HARDWARE\\SIV\\"+rType
        try:
            registry_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, keyPath, 0, winreg.KEY_READ)
            value, regtype = winreg.QueryValueEx(registry_key, rKey)
            winreg.CloseKey(registry_key)
            return value
        except WindowsError:
            return None  
        
pcStats = MyHttpRequestHandler
pcStatsServer = socketserver.TCPServer(("0.0.0.0", int(config['ctrl']['port'])), pcStats)
pcStatsServer.serve_forever()

Il faut aussi un fichier pour la configuration.

pcstats/pc/config.ini

; PCStats Configuration file
; 

[system]
;Path where are stored PCStats files, with "\" at the end
path=D:\CODES\PYTHON\pcstats\pc\

[ctrl]
;Password for safety control, only used for reboot/poweroff system over Web UI
pass=42758

;Port for the Web access, 9000 is the default
port=9000

[monitors]
#Require SIV

pcname=Nodename
pcname_type=H

cpuname=CPU-0-NAM
cpuname_type=H
cpuspeed=CPU-0-P
cpuspeed_type=K
cpubus=CPU-0-CLK
cpubus_type=K
cpumulti=CPU-0-MLT
cpumulti_type=K
cpuload=CPU-T-L
cpuload_type=L
cputemp=CPU-0-T
cputemp_type=T
cpupower=CPU-0-PWR
cpupower_type=P

fancpu=M-F1
fancpu_type=C
fansys1=M-F2
fansys1_type=C
fansys2=M-F3
fansys2_type=C
fangpu=GPU-0-F
fangpu_type=C

sysname=Motherboard
sysname_type=H
syspower=SYSPWRUSE
syspower_type=P
systemp1=M-TA
systemp1_type=T
systemp2=M-TB
systemp2_type=T

ramname=SYSMEMSUM
ramname_type=H
ramload=SYSRAMPOT
ramload_type=H
ramuse=SYSRAMUSE
ramuse_type=H
ramfree=SYSRAMFRE
ramfree_type=H
ramtotal=SYSRAMTOT
ramtotal_type=H

gpuname=GPU-0-NAM
gpuname_type=H
gpucorespeed=GPU-0-C
gpucorespeed_type=K
gpuvideospeed=CPU-0-V
gpuvideospeed_type=K
gpupower=GPU-0-PWR
gpupower_type=P
gpuload=GPU-T-L
gpuload_type=L
gputemp=GPU-0-T
gputemp_type=T

vramname=
vramname_type=
vramload=GPU-T-P
vramload_type=L
vramuse=GPU-T-B
vramuse_type=L
vramfree=
vramfree_type=
vramtotal=GPU-T-B
vramtotal_type=L
vramspeed=GPU-0-M
vramspeed_type=K

Il faut l’exécuter, pour ce faire il y a deux fichiers. Le premier est le script de lancement en « .bat », il permet de lancer l’application, mais vu le code Python et les caprices de Windows, vous aurez une console affiché, pratique par contre pour le debug. Inutile de tenter d’utiliser « pythonw.exe »; le script ne ce lance pas.

Le second fichier est un script en « .vbs », il perme de lancer la console en arrière-plan, bref, elle sera caché.

pcstats/pc/run.bat

python.exe pcstats.py

pcstats/pc/run.vbs

Dim WinScriptHost
Set WinScriptHost = CreateObject("WScript.Shell")
WinScriptHost.Run Chr(34) & "run.bat" & Chr(34), 0
Set WinScriptHost = Nothing

Vous avez alors votre service pour afficher vos informations systèmes.

L’affichage sur le Raspberry

Sous le Raspberry il faut un bureau, python et pour mon projet un écran 3.5" 480x320. J’ai utilisé Raspbian Lite et un Raspberry Pi Zero sans le Wifi, pour les tests j’utilise un chapeaux USB+Ethernet mais le projet final ne devrais pas l’avoir (voir troisième partie du projet).

J’utilise ici Glade pour GTK à fin de m’aider a créer le GUI, j’évite l’utilisation d’un navigateur Web, qui serait très beau et pratique car il est lourd à utiliser. Le rendu directement via Python limite l’usage des ressources et augmente la rapidité d’exécution.

pcstats/pi/pcstats.py

import json, requests, configparser
import gi, re, os, subprocess, datetime, time
import numpy as np
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository.GdkPixbuf import Pixbuf, InterpType
from datetime import datetime as dt
print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Start application")


class PCStats():
    def __init__(self):
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Loading configuration")
        self.config = configparser.ConfigParser()
        self.config.read("config.ini")
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Configuration loaded")    
           
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Loading UI")
        self.root = Gtk.Builder()
        self.root.add_from_file("ui.glade")
        self.window = self.root.get_object("mainapp")
        self.window.set_default_size(480, 320)        
        self.window.set_title("PCStats")
        self.window.connect("destroy", Gtk.main_quit, "WM destroy")
        self.window.show_all()
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "UI loaded")
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Loading values attributions")
        self.data_computername = self.root.get_object("computername")
        self.data_cpuname = self.root.get_object("cpuname")
        self.data_cpuloadbar = self.root.get_object("cpuloadbar")
        self.data_cpuloadtxt = self.root.get_object("cpuload")
        self.data_cpupowertxt = self.root.get_object("cpupower")
        self.data_cpuspeedtxt = self.root.get_object("cpuspeed")
        self.data_cputemptxt = self.root.get_object("cputemp")
        self.data_sysname = self.root.get_object("sysname")
        self.data_syspowertxt = self.root.get_object("syspower")
        self.data_systemp1txt = self.root.get_object("systemp1")
        self.data_systemp2txt = self.root.get_object("systemp2")
        self.data_gpuname = self.root.get_object("gpuname")
        self.data_gpuloadbar = self.root.get_object("gpuloadbar")
        self.data_gpuloadtxt = self.root.get_object("gpuload")
        self.data_gpupowertxt = self.root.get_object("gpupower")
        self.data_gpuspeedtxt = self.root.get_object("gpuspeedcore")
        self.data_gputemptxt = self.root.get_object("gputemp")
        self.data_vramloadbar = self.root.get_object("vramloadbar")
        self.data_vramloadtxt = self.root.get_object("vramload")
        self.data_vramusedtxt = self.root.get_object("vramused")
        self.data_vramtotaltxt = self.root.get_object("vramtotal")
        self.data_vramspeedtxt = self.root.get_object("vramspeed")
        self.data_ramname = self.root.get_object("ramname")
        self.data_ramloadbar = self.root.get_object("ramloadbar")
        self.data_ramloadtxt = self.root.get_object("ramload")
        self.data_ramusedtxt = self.root.get_object("ramused")
        self.data_ramtotaltxt = self.root.get_object("ramtotal")
        print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Values attributions loaded")
        GLib.timeout_add_seconds(1, self.updates)
        Gtk.main()

    def getData(self,path):
        jsonUrl = "http://" + self.config['system']['remoteIP'] + ":" + self.config['system']['remotePort']  + "/" + path
        url = requests.get(jsonUrl)
        return json.loads(url.text)

    def updates(self):
        try:
            datas = self.getData("stats")
        except:
            datas = False
            print(dt.now().strftime("%m-%d-%y %H:%M > ") + "Remote Host Error, offline ?")    
            self.data_computername.set_text("Remote host offline")
        if(datas is not False):    
            self.data_computername.set_text(datas['pcname'])    
            self.data_cpuname.set_text(datas['cpuname'])
            self.data_cpuloadtxt.set_text(str(datas['cpuload']).split(",")[0] + " %")
            self.data_cpuloadbar.set_value(float(str(datas['cpuload']).split(",")[0]))
            self.data_cpuspeedtxt.set_text(str(datas['cpuspeed']).split(",")[0])
            self.data_cpupowertxt.set_text(str(round(float(str(datas['cpupower']).split(",")[0].replace(" W","")),1)) + " W")
            self.data_cputemptxt.set_text(str(datas['cputemp']).split(",")[0])
            
            self.data_gpuname.set_text(datas['gpuname'])
            self.data_gpuloadtxt.set_text(str(datas['gpuload']).split(",")[0] + " %")
            self.data_gpuloadbar.set_value(float(str(datas['gpuload']).split(",")[0]))
            self.data_gpuspeedtxt.set_text(str(datas['gpucorespeed']).split(",")[0])
            self.data_gpupowertxt.set_text(str(round(float(str(datas['gpupower']).split(",")[0].replace(" %","")),1)) + " %")
            self.data_gputemptxt.set_text(str(datas['gputemp']).split(",")[0])
            
            self.data_sysname.set_text(datas['sysname'])
            self.data_syspowertxt.set_text(str(datas['syspower']).split(",")[0])
            self.data_systemp1txt.set_text(str(datas['systemp1']).split(",")[0])
            self.data_systemp2txt.set_text(str(datas['systemp2']).split(",")[0])
            
            self.data_ramname.set_text(datas['ramname'])
            self.data_ramloadtxt.set_text(str(datas['ramload']).split(",")[0])
            self.data_ramloadbar.set_value(float(str(datas['ramload']).split(",")[0].replace(" %","")))
            self.data_ramusedtxt.set_text(str(datas['ramuse']).split(",")[0])
            self.data_ramtotaltxt.set_text(str(datas['ramtotal']).split(",")[0])
            
            self.data_vramloadtxt.set_text(str(datas['vramload']).split(",")[0] + " %")
            self.data_vramspeedtxt.set_text(str(datas['vramspeed']).split(",")[0])
            self.data_vramloadbar.set_value(float(str(datas['vramload']).split(",")[0].replace(" %","")))
            self.data_vramusedtxt.set_text(str(datas['vramuse']).split(",")[0])
            self.data_vramtotaltxt.set_text(str(datas['vramtotal']).split(",")[4])
        return True

app=PCStats()  

pcstats/pi/config.ini

[system]
remoteIP=192.168.0.111
remotePort=9000

Il faudra aussi un fichier pour l’arrière plan.

pcstats/pi/bg.jpg

bg
*Il n’est pas travaillé pour résolution égale

pcstats/pi/ui.glade
Trop long pour le forum, mis sur PasteBin
https://pastebin.pl/view/8fc8ee05

Il faudra aussi l’exécuter, vous avez alors quelques fichiers.

pcstats/start.sh
Sert à lancer le bureau

#!/bin/bash
DISPLAY=:0 sudo startx -- -nocursor -quiet

pcstats/pcstats.sh
Sert à lancer l’interface du moniteur.

#!/bin/bash
cd /home/pi/pcstats/pi
DISPLAY=:0 sudo python3 pcstats.py >/home/pi/pcstats/app.log 2>&1 &

pcstats/pcstats-stop.sh
Sert à arrêter l’interface, pratique pour faire du debug sur le design.

#!/bin/bash
if [ ! -z "$(pgrep -f pcstats.py)" ]
then
	let pid=$(pgrep -f pcstats.py)
	echo "piPCStats is stopped (kill)."
	sudo -E kill -9 $pid
	if [ ! -z "$(pgrep -f pcstats.py)" ]
	then
        	let pid=$(pgrep -f pcstats.py)
	        echo "piPCStats is stopped (kill)."
        	sudo -E kill -9 $pid
	fi

else
	echo "piPCStats is not running."
fi

La connexion avec l’ordinateur

Idéalement, je ne veux pas passé par le réseau, je veux que le Raspberry soit relié en USB à l’ordinateur, ici je n’ai pas encore fini de penser et réaliser la chose. Alors pour les tests c’est en réseau normal, ce qui marche très bien.

L’idée de cette partie est d’utiliser le mode OTG du Raspberry Pi Zero pour communiquer plus rapidement avec l’ordinateur et avec moins de « latency » (et de rendre utile un Raspberry sans réseau). Théoriquement je me dis qu’avec le mode « g_ether », je peux alors avoir une adresse IP entre le PC et le RPi, il faudra juste faire quelques codes pour permettre de gérer et connaitre celui du PC pour que l’affichage sache quel adresse communiquer. Le nom du PC simplement pourrait être la solution.

Pour l’alimentation, selon mes examens, j’ai 200mA~450mA en usage (plus près de 250mA en moyenne), ce qu’un port USB peut fournir, j’ai fais des tests depuis un hub alimenté, ça marche très bien pour allumer le RPi.


Vous aurez compris que l’affichage s’actualise à toutes les secondes, en USB je tenterais d’accélérer l’affichage. Mais pour les tests, c’est bon.

Le code est rendu sur GitHub - levelKro/piPCStats: Display PC Stats on a Raspberry Pi pour les intéressés.