Die Überschrift ist vielleicht nicht so gut, mir fiel aber (noch) nichts besseres ein...
Ähnlich wie mein "RPi_Info - Windows Gadget", um System-Informationen des RaspberryPi's auf dem Windows-Desktop anzuzeigen, habe ich versucht sowas auch andersherum umzusetzen: System-Informationen eines Windows-PC's auf dem RaspberryPi-Desktop anzeigen.
Die Entwicklung bzw genaue Details findet ihr im Thread von derveganemetzger: Ressourcenmonitor
Ich hab mich dazu entschieden Windows Management Instrumentation (WMI) zu verwenden. Auf dem Windows-PC müssen 2 Programme laufen:
- OpenHardwareMonitor ... was die nötigen Sensoren ausliest und ins WMI stellt.
- wmi_server.py ... ein Python Script was die Informationen aus dem WMI ausließt und via Socket verschicken kann.
Installation Windows:
Open Hardware Monitor installieren & starten. "Run On Windows Startup" und die obersten beiden, in den Options einstellen
Ich empfehle folgendes Python für Windows zu installieren: python-3.5.3-amd64.exe ... Achtet darauf "Add Python to PATH" anzuwählen (ich empfehle "Customize installation"). Es geht natürlich auch eine 32bit Version von Python.
Anschließend "cmd" als Administrator ausführen (Start => Alle Programme => Zubehör => rechtsklick auf Eingabeaufforderung und "Als Administrator ausführen") und folgende Python Module installieren:
(Die Eingabeaufforderung erst noch offen lassen, da kommen ggf noch ein paar Befehle rein ;))
Nun käme der schwierige Teil beziehungsweise gibt es mehrere Möglichkeiten... Nämlich um das Python Script ebenfalls "Run On Windows Startup" auszuführen. Welche Möglichkeit ihr nehmt ist relativ egal, manche sind umständlicher andere nicht - ich leg mich hier nicht auf eine fest, entscheidet selbst.
1.Möglichkeit => Klassisches Autostart:
Spoiler anzeigen
Hierbei legt man ein sog. Shortcut also eine Verknüpfung in das "Autostart" Verzeichnis von Windows an und lässt das Script mithilfe von "pythonw.exe" ausführen.
Bei Windows 7 ist das "Autostart" Verzeichnis versteckt:
USER mit eurem Benutzernamen ersetzen
Ihr könnt diese Adresse/Pfad in den Windows-Explorer oben rein pasten ohne die Ordneroptionen ändern zu müssen (Versteckte Dateien und Ordner)
Beispielsweise habt ihr das Python Script im Verzeichnis "Eigene Dokumente" abgelegt - Wo die Datei liegt ist aber letztlich egal.
Dann macht ihr auf die Datei ein Rechtsklick und wählt "Verknüpfung erstellen" aus.
Dann benenne ich oftmals die Datei um und schneide das " - Verknüpfung" raus... Sieht besser aus.
Anschließend macht ihr auf die Verknüpfung einen Rechtsklick und wählt "Eigenschaften" aus.
Im Feld "Ziel" fügt ihr jetzt an den Anfang "pythonw.exe " ein, mit Leerzeichen. In dem Feld müsste dann also zB folgendes stehen:
Sind Leerzeichen im Pfad enthalten dann mit " umschließen.
Mit OK bestätigen.
Die Verknüpfung verschiebt ihr dann in besagtes "Autostart" Verzeichnis. Fertig.
Nachteil bei dieser Methode:
Im 'Task Manager' taucht nur ein "pythonw.exe" Prozess auf. Habt ihr mehrere Python Scripts über pythonw am laufen könnt ihr die nicht auseinander halten.
2.Möglichkeit => Run As Service:
Spoiler anzeigen
Hierfür bedient man sich eines zusätzlichen Programms: https://sourceforge.net/projects/runasservice/
WICHTIG: "RunAsSvc.exe" wird hierfür permanent benötigt. Legt euch das Tool also in ein sicheres Verzeichnis wo es überdauern kann. Wird es gelöscht funktioniert diese Methode nicht mehr!
Zunächst muss das Python Script aber erst in eine Executable (*.exe) umgewandelt werden, beziehungsweise werden alle nötigen Sachen wie Module usw in die exe mit rein gepackt sodass die exe auch auf Systemen ausgeführt werden kann wo kein Python installiert ist.
Um das zu erreichen bevorzuge ich pyinstaller:
Anschließend in der Eingabeaufforderung folgendes ausführen:
(zuvor ins entsprechende Verzeichnis wechseln)
War das erfolgreich wurde eine wmi_server.exe im Unterverzeichnis "dist" erstellt. Das Arbeitsverzeichnis "build" kann man gefahrlos löschen.
Datei eine Ebene höher verschieben und dann kann auch "dist" gelöscht werden.
Nun kommt das eingangs erwähnte Tool "RunAsService" zum Einsatz... Rechtsklick auf "RunAsSvc.exe", als Administrator ausführen und die Felder wie folgt ausfüllen:
- Dienstname: wmi_python_server
- Beschreibung: wmi_python_server
- Pfad zur Exe-Datei: ... navigiert zu eurer wmi_server.exe ...
- Parameter:
- Arbeitsverzeichnis: ... navigiert in das Verzeichnis eurer wmi_server.exe ...
Anschließend mit OK bestätigen.
Wer das verifizieren will kann das in der Verwaltung überprüfen: Start => Systemsteuerung => Verwaltung => Dienste. Ganz unten müsste dann der Dienstname gelistet werden.
Wer den Dienst wieder löschen will gibt in der Eingabeaufforderung das ein:
Alternativ den Dienst aus der Registry löschen und anschließend rebooten, aber das ist mit Vorsicht zu genießen!
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services ... einer der {....} Verzeichnisse, müsst ihr selber rausfinden.
Die beiden Möglichkeiten können auch vermischt werden (exe in "Startup" ablegen). Ich bin mir sicher das es auch noch weitere Möglichkeiten gibt
wmi_server.py => https://slexy.org/view/s2fWHkifxo
Spoiler anzeigen
#
# Creator: meigrafd
# Copyright (C) 2017 by meiraspi@gmail.com published under the Creative Commons License (BY-NC-SA)
#
# On Windows install OpenHardwareMonitor, Python >= 3.4 and:
# pip install WMI win32core pypiwin32
#
import wmi
import json
import struct
import threading
import pythoncom
import socketserver
def hw_tree(hwmon):
hardwares = hwmon.Hardware()
# No sensor data (empty list) indicates OpenHWMon is not running
if not hardwares:
return False
hardware_nodes = dict()
for hardware in hardwares:
if hardware.Parent == "":
hardware_nodes[hardware.Identifier] = [hardware.Identifier]
for hardware in hardwares:
if hardware.Parent != "":
hardware_nodes[hardware.Parent].append(hardware.Identifier)
sensors = hwmon.Sensor()
data = dict()
for key, nodelist in hardware_nodes.items():
for index, node in enumerate(nodelist):
for hardware in hardwares:
if hardware.Identifier == nodelist[index]:
node_Name = hardware.Name
data[node_Name] = dict()
for sensor in sensors:
if sensor.Parent == nodelist[index]:
data[node_Name][sensor.Identifier] = dict()
data[node_Name][sensor.Identifier].update(Name=sensor.Name)
data[node_Name][sensor.Identifier].update(Type=sensor.SensorType)
data[node_Name][sensor.Identifier].update(Max=sensor.Max)
data[node_Name][sensor.Identifier].update(Min=sensor.Min)
data[node_Name][sensor.Identifier].update(Value=sensor.Value)
return data
#socket server
class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
def handle(self):
try:
pythoncom.CoInitialize() # need this for multithreading COM/WMI
hwmon = wmi.WMI(namespace="root\OpenHardwareMonitor")
data = hw_tree(hwmon)
if data:
data = json.dumps(data, sort_keys=True).encode()
self.wfile.write(struct.pack('<L', len(data)))
self.wfile.flush()
self.wfile.write(data)
self.wfile.flush()
except Exception as error:
print("Error in ThreadedTCPRequestHandler: " + str(error))
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def main(SocketHost="0.0.0.0", SocketPort=7060):
try:
socket_server = ThreadedTCPServer((SocketHost, SocketPort), ThreadedTCPRequestHandler)
# Start a thread with the server - that thread will then start one more thread for each request
socket_server_thread = threading.Thread(target=socket_server.serve_forever)
socket_server_thread.start()
except Exception as error:
print("Error...: " + str(error))
except (KeyboardInterrupt, SystemExit):
print("\nQuit\n")
if __name__ == '__main__':
main()
#EOF
[/php]
[/spoiler]
[b]wmi_server.exe[/b] => http://raspberrypi.roxxs.org/wmi_server.zip
[b]Installation Raspbian[/b]:
Hier wird nicht viel benötigt:
[code]apt-get install python3-tk
Alles anzeigen
So weit so gut... Nun folgt der Teil an dem ich noch zu knabbern habe und Grund ist wieso ich diesen Thread in "laufende Projekte" erstellt habe: die GUI
Für die GUI verwende ich Tkinter. Zur Darstellung möchte ich mich am "Open Hardware Monitor" orientieren und nutze daher ttk.Treeview()
Was bisher problemlos funktioniert ist der Verbindungsaufbau und das Abrufen der Daten - ist ja auch eigentlich nicht weiter schwer...
Allerdings stehe ich aktuell ein wenig auf dem Schlauch wie ich die Staffelung/Gruppierung der Daten verarbeite sodass zum Beispiel alle "Load" unter diesem node aufgelistet werden, aber nicht mehrere nodes "Load" eingefügt werden...
Auch diese Baumansicht sieht noch längst nicht so aus wie ich mir das vorgestellt habe, derzeit steht einfach alles untereinander...
Es werden auch bei jedem 'update_interval' nur neue Daten hinzugefügt, keine vorhanden gelöscht oder schlicht nur updated.
Und zu guter letzt will die Scrollbar nicht so wie ich
Vielleicht kann mir hier jemand auf die Sprünge helfen?
Achja: Durch Doppelklick auf zB eure CPU klappt dieser Teil auf und so sieht das ganze derzeit aus:
GUI => https://slexy.org/view/s2290gd9nO
Spoiler anzeigen
[code=php]
#!/usr/bin/python3
#
# Creator: meigrafd
# Copyright (C) 2017 by meiraspi@gmail.com published under the Creative Commons License (BY-NC-SA)
#
import sys
import json
import socket
import select
import struct
import tkinter as tk
import tkinter.ttk as ttk
class GUI(object):
def __init__(self, resolution="640x480", host='127.0.0.1', port=7060, timeout=5, interval=5000):
self.host = host
self.port = port
self.gui_resolution = resolution
self.timeout = timeout
self.running = False
self.update_interval = interval #ms
self.master = tk.Tk()
#self.master.geometry(self.gui_resolution)
self.master.protocol("WM_DELETE_WINDOW", self.quit)
self.main()
# https://stackoverflow.com/q/32051780/2641799
def main(self):
frame = ttk.Frame(self.master)
frame.pack()
header = ['Sensor', 'Value', 'Min', 'Max']
self.tree = ttk.Treeview(frame, columns=header, show='headings')
# setup column headings
self.tree.heading('#1', text='Sensor', anchor=tk.W)
self.tree.heading('Value', text='Value', anchor=tk.W)
self.tree.column('Value', stretch=0, width=70)
self.tree.heading('Min', text='Min', anchor=tk.W)
self.tree.column('Min', stretch=0, width=70)
self.tree.heading('Max', text='Max', anchor=tk.W)
self.tree.column('Max', stretch=0, width=70)
#for col in self.header:
# self.tree.heading(col, text=col.title())
style = ttk.Style()
style.configure('.', font=('Helvetica', 8), foreground='white')
style.configure('Treeview', foreground='red')
style.configure('Treeview.Heading', foreground='green')
# create scrollbars
ysb = ttk.Scrollbar(self.tree, orient=tk.VERTICAL, command=self.tree.yview)
#xsb = ttk.Scrollbar(self.tree, orient=tk.HORIZONTAL, command=self.tree.xview)
self.tree['yscroll'] = ysb.set
#self.tree['xscroll'] = xsb.set
# add tree and scrollbars to frame
#ysb.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.pack()
# set frame resizing priorities
#frame.rowconfigure(0, weight=1)
#frame.columnconfigure(0, weight=1)
self.connect()
self.get_data()
def run(self):
self.master.mainloop()
def connect(self):
self.client_socket = socket.socket()
self.client_socket.settimeout(self.timeout)
try:
self.client_socket.connect((self.host, self.port))
except (socket.timeout, ConnectionRefusedError) as Error:
print(Error)
self.stop()
return
self.client_socket.settimeout(None)
# Make a file-like object out of the connection
self.connection = self.client_socket.makefile('rb')
self.running = True
def get_data(self):
if self.running:
try:
calcsize = self.connection.read(struct.calcsize('<L'))
if not calcsize:
self.stop()
self.connect()
self.get_data()
return
data_len = struct.unpack('<L', calcsize)[0]
except struct.error as Error:
print(Error)
self.stop()
self.connect()
self.get_data()
return
if data_len:
data = self.connection.read(data_len).decode()
data = json.loads(data)
self.populate_root(data)
data=None
self.master.after(self.update_interval, self.get_data)
def populate_root(self, data):
for node, _dic in data.items():
#print(node)
parent = self.tree.insert('', 'end', text=node, values=[node], tags=('node'), open=False)
if isinstance(_dic, dict):
self.populate_tree(parent, _dic)
def populate_tree(self, parent, children):
# parent - id of node acting as parent
# children - list of sensors belonging to the 'parent' node
for k, v in children.items():
#print("{} => {}".format(k, v))
for index, type in enumerate(v):
#print(type)
if type == "Type":
self.tree.insert(parent, 'end', text=v["Type"], values=[v["Type"]])
self.tree.insert(parent, 'end', text=v["Name"], values=[v["Name"], v['Value'], v['Min'], v['Max']])
def stop(self):
self.running = False
try: self.connection.close()
except: pass
try: self.client_socket.close()
except: pass
self.client_socket = None
def quit(self):
self.running = False
self.master.destroy()
print('\nQuit\n')
def main():
screen_width = 400
screen_height = 500
try:
tkinter_app = GUI(host='192.168.0.10', port=7060, resolution="{0}x{1}".format(screen_width, screen_height))
tkinter_app.run()
except (KeyboardInterrupt, SystemExit):
print('\nQuit\n')
tkinter_app.quit()
except Exception as error:
print('Error: ' + str(error))
sys.exit()
if __name__ == '__main__':
main()
#EOF
[/php]