Seite 1 von 1
Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 12:00
von Minzent
Seid gegrüßt,
und zwar habe ich folgenden Sachverhalt: Ich habe zwei Raspi´s, im Folgenden MessPi (welcher die Sensorik ausliest) und SteuerungsPi (ergo: welcher steuert, seine Funktion ist aber zunächst irrelevant).
In meinem ersten Projekt hatte ich die beiden Raspi´s falsch konfiguriert, heißt: Ich hatte Server und Client vertauscht. Nun versuche ich (auch unter der Prämisse der objektorientierten Programmierung) das Ganze zu tauschen und aufzuräumen. Es geht jetzt erstmal um den Server, also den MessPi. Seine Aufgabe soll sein, die ganze Zeit über (also ab Boot des Pi´s --> Cronjob, sollte ich selber hinkriegen) das Meßwertregister des PiLoggers (das ist meine Sensorik) auszulesen (Klasse: Monitor) und darauf zu warten, bis der SteuerPi den Status (also die derzeitigen Messwerte Strom und Spannung) abfragt. Dann soll der MessPi diese Messwerte an den SteuerPi senden und wieder von vorne beginnen. Er soll also immer bereit sein, abgefragt zu werden. Nun habe ich eine Main() und zwei Klassen geschrieben, habe aber Schwierigkeiten nun die einzelnen Teile miteinander zu verbinden. Ich weiß, dass ich in der Server-Klasse zu viele Funktionen definiert habe, weiß aber nicht genau, welche ich nicht brauche. Die Main() macht zur Zeit im Grunde auch nichts außer die Messwerte zu lesen und die _init_ des Servers zu starten. Hier die Programmskripte:
Main()
Code: Alles auswählen
from Messpi_Server import Server
from Monitoring_Modul import Monitor
from time import sleep
SLEEP_TIME = 3
pServer = Server();
pMonitor = Monitor();
try:
while True:
pMonitor.Monitoring()
sleep(SLEEP_TIME)
Messpi_Server
Code: Alles auswählen
import socket
class Server:
def __init__(self):
host = ''
port = 5560
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind((host, port))
self.server.listen(1)
conn, address = self.server.accept()
return conn
def connectAndTransfer(self):
conn = self.setupConnection()
self.dataTransfer(conn)
def dataTransfer(self, conn):
while True:
data = conn.recv(1024)
data = data.decode('utf-8')
dataMessage = data.split(' ', 1)
command = dataMessage[0]
if command == 'Statusabfrage':
#???
self.server.close()
break
conn.close()
def sendReceive(self, s, message):
s.send(str.encode(message))
reply = s.recv(1024)
s.close()
reply = reply.decode('utf-8')
return reply
def transmit(self, message):
s = self.setupSocket()
response = self.sendReceive(s, message)
return response
Monitoring_Modul:
Code: Alles auswählen
import smbus
class Monitor:
def __init__(self):
self.pilogger = smbus.SMBus(1)
def Monitoring(self):
volt = float(self.getVolt())
ampere = float(self.getAmp())
def getVolt(self):
volt_raw = self.pilogger.read_word_data(0x48, 0x50)
volt = volt_raw * 0.000915541
return volt
def getAmp(self):
ampere_raw = self.pilogger.read_word_data(0x48, 0x60)
if ampere_raw >= 32768:
ampere_raw = ampere_raw - 65536
ampere = ampere_raw / 2141.634
return ampere
Wie immer bin ich für eure Hilfe sehr dankbar. Und wie immer: ein großes Lob an diese tolle und hilfreiche community!
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 12:19
von Sirius3
Die Namensschreibweise von Methoden ist auch klein_mit_unterstrich. Abkürzungen vermeiden, get_ampel / get_amplitude / get_amphore?
Es ist falsch, jede Klasse in eine eigene Datei zu stecken. Das wurde Dir glaube ich auch schon mehrfach geschrieben.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene. Die ; sind falsch, weg damit.
`Monitoring`` macht nichts, außer lokale Variablen zu befüllen und diese dann sofort wegzuschmeißen.
Deine Socketprogrammierung ist kaputt, wie auch 99.999% der Beispiele, die Du im Netz findest. Statt Dir etwas eigenes zu verfinden, benutze ein bereits erprobtes Protokoll, HTTP / MQTT, etc. Da noch einen Kommentar dazu zu schreiben, wäre vergeblich. Löschen und mit den richtigen Bibliotheken von vorne anfangen.
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 12:45
von __blackjack__
@Minzent: Der Ablauf klingt irgendwie falsch. Messwert lesen und dann warten bis der irgendwann mal abgeholt wird? Normal wäre warten bis die Anfrage nach einem Messwert kommt, und dann erst den *dann aktuellen* Messwert lesen und damit antworten. Und dann wieder auf die nächste Anfrage warten.
Modulnamen schreibt man klein_mit_unterstrichen, und der Zusatz `modul` hat in einem Modulnamen nichts zu suchen.
Was soll der kryptische Präfix `p` bei `pMonitor` und `pServer`? Entweder weglassen oder ausschreiben. Und auch hier gilt wieder: Namen werden klein_mit_unterstrichen geschrieben. Ausnahemen Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).
Die Semikolons haben da nichts verloren.
Die letzten beiden Zeilen von `Server.__init__()` machen keinen Sinn. Die `__init__()` sollte nicht auf eine Verbindung warten und ``return`` geht da nicht, weil der Wert nirgends landet und damit einfach ignoriert wird.
Das ein `Server` selbst wieder ein Attribut `server` hat ist komisch. Das sollte wohl besser `socket` heissen.
`Server.setupConnection()` ist nirgends definiert, wird aber in `connectAndTransfer()` aufgerufen.
Das `dataTransfer()` ein geöffnetes Socket übergeben bekommt und das am Ende schliesst ist komisch/asymmetrisch. Vielleicht möchte der Aufrufer danach noch irgendetwas senden können. Wenn man öffnen/erstellen in einem Namensraum hat, kann man auch die ``with``-Anweisung verwenden und das schliessen robuster/sicherer gestalten.
`recv(1024)` liefert 1 bis 1024 Bytes. Und zwar auch wenn auf der anderen Seite 50 bytes in einem Aufruf gesendet wurden, musst Du damit rechnen das ein `recv()`-Aufruf davon nur das erste Byte zurück liefert. Und beim nächsten auch wieder nur ein Byte, und beim nächsten vielleicht 3 Byte, und … – der Punkt ist, das Du selbst dafür sorge trägst, dass so oft `recv()` aufgerufen wird, bis Du mindestens eine komplette Nachricht beisammen hast. Wenn das kein Halbduplex-Protokoll ist, was Du Dir da zusammen bastelst, dann kann es auch sein, dass bei einem `recv()` auch ein Teil der nächsten Nachricht schon dabei ist, oder gar mehrere, wobei die letzte unvollständig sein kann. Socketprogrammierung ist scheisse kompliziert. Darum macht man das eigenlich auch nicht selbst, sondern nimmt was fertiges was eine Schicht höher liegt. Beispielsweise HTTP. Da gibt's schöne, kleine Microrahmenwerke wie `bottle` oder `flask`, die einem da sehr viel Arbeit abnehmen.
Nach der 'Statusabfrage' möchtest Du ja eher nicht das Serversocket schliessen! Dann kann ja keine weitere Anfrage mehr über dieses Socket bedient werden.
`Server.sendReceive()` hat wieder die gleichen Probleme. Asymmetrisches schliessen des übergebenen Sockets (`s` ist ein schlechter Name), `recv()` liefert einen Teil des Datenstroms und keine garantiert kompletten Nachrichten, und `send()` sendet auch nicht garantiert alles, sondern muss so oft aufgerufen werden bis tatsächlich alles gesendet wurde. Die Methode liefert die Byteanzahl zurück die tatsächlich mit einem Aufruf gesendet wurden. Hier gibt es aber netterweise die `sendall()`-Methode die das schon für einen macht.
`Server.transmit()` ruft eine nicht vorhandene `setupSocket()`-Methode auf.
Die `Monitor.Monitoring()`-Methode hat keinen sinnvollen Effekt.
`getAmp()` sollte `get_ampere()` heissen.
Insgesamt würde ich die Serverklasse massiv in Frage stellen. Klassen sind kein Selbstzweck. Welches Problem soll die denn lösen? Das kann man bisher auch ganz einfach als Funktionen schreiben, eventuell auch sogar noch als *eine* Funktion.
`Monitor` ist da schon ein wenig sinnvoller, allerdings wäre da der Name `PiLogger` für die Klasse geeigneter und dafür `bus` oder `smbus` für das Attribut an das das `SMBus`-Exemplar gebunden wird.
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 13:35
von Minzent
Vielen Dank euch Beiden,
die Syntaxanmerkungen habe ich mir jetzt zu Herzen genommen. Statt nun die Socketprogrammierung zu fixen nehme ich mir eurer Vorschläge an, das ganze mit HTTP zu lösen. Daher schließe ich an dieser Stelle alles weitere zur Socketprogrammierung und arbeite mich nun in das HTTP Protokoll ein. Danke und bis sehr wahrscheinlich bald!
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 13:37
von __blackjack__
Mal ungetestet wie so ein Server der HTTP-Anfragen entgegennimmt und JSON als Antwort liefert mit Bottle aussehen könnte:
Code: Alles auswählen
#!/usr/bin/env python3
from bottle import default_app, run
from smbus import SMBus
class PiLogger:
def __init__(self):
self.bus = SMBus(1)
def read_volt(self):
return self.bus.read_word_data(0x48, 0x50) * 0.000915541
def read_ampere(self):
ampere_raw = self.bus.read_word_data(0x48, 0x60)
if ampere_raw >= 32768:
ampere_raw -= 65536
return ampere_raw / 2141.634
app = default_app()
@app.route('/volt')
def get_volt():
return {'value': app.config['pilogger'].read_volt()}
@app.route('/ampere')
def get_ampere():
return {'value': app.config['pilogger'].read_ampere()}
@app.route('/read')
def get_values():
pilogger = app.config['pilogger']
return {'volt': pilogger.read_volt(), 'ampere': pilogger.read_ampere()}
@app.route('/read/<what:re:volt|ampere>')
def get_value(what):
return {'value': getattr(app.config['pilogger'], 'read_' + what)()}
def main():
app.config['pilogger'] = PiLogger()
app.run(host='0.0.0.0', port=8080)
if __name__ == '__main__':
main()
Auf dem gleichen Rechner kommt man dann mit `
http://localhost:8080/volt`, `
http://localhost:8080/ampere`, `
http://localhost:8080/read/volt`, und `
http://localhost:8080/read/ampere` an die Einzelwerte heran, und mit `
http://localhost:8080/read` an beide.
Alternativ könnte man sich auch ein einfaches Bottle-Plugin schreiben, das den `PiLogger` als Argument ”injected”.
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 14:40
von Minzent
Ich muss zugeben ich bin jetzt leicht überfordert. Ich finde kein gescheites Tutorial oder eine Einführung in HTTP zwischen zwei Raspberry Pi´s. Deinen Code verstehe ich zwar soweit, aber ich möchte doch letztich nicht vom selben Rechner an die Meßwerte kommen, sondern von meinem anderen Rechner (dem SteuerungsPi).
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 15:03
von __deets__
Dann gibst du die Adresse des anderen Rechners an. Wie beim socket auch. Oder in deinem Browser. Sonst könntest du dieses Forum ja gar nicht besuchen....
Als Alternative würde ich übrigens nochmal MQTT & einen ESP32 statt eines Pis empfehlen. Billiger, robuster
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Donnerstag 9. Mai 2019, 16:43
von __blackjack__
@Minzent: Es gibt kein „HTTP zwischen zwei Raspberry Pi's“, es gibt nur HTTP. Dem Protokoll ist doch völlig egal auf was für Hardware es läuft.
Wikipedia hat eine Seite für HTTP (
https://de.wikipedia.org/wiki/Hypertext ... r_Protocol) wo auch die ganzen Spezifikationen verlinkt sind. Allerdings willst Du das ja auch nicht selbst implementieren – da gibt es ja die besagten Rahmenwerke für. Und für die andere Seite – das Abfragen, die `requests`-Bibliothek.
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Freitag 10. Mai 2019, 08:34
von Tholo
Mein derzeitiges Projekt ist Ähnlich.
Ich arbeite mit einem Nodemcu (Esp32) für ca 8€ und auf diesen ist Micropython installiert.
Hier steht wie das geht
Und der Triggert verschiedene Sensor und Button Daten via Mqtt. Ich finde der Einstieg in Mqtt ist gut und es gibt viele Tuts.
Hier zum Beispiel ein Temperatur Sensor welche Mqtt suf dem Esp32 mit Micropython verschickt
Re: Socket-Server-Programmierung zur Messwertabfrage
Verfasst: Sonntag 12. Mai 2019, 12:16
von DeaD_EyE
Ich nutze z.B. ZMQ für die Prozesskommunikation. Das kann man auch nutzen um von Gerät A Daten an Gerät B zu senden. Man kann aber noch viel mehr damit.
Jedenfalls darf ZMQ nicht im Internet genutzt werden, da keinerlei Sicherheitsmechanismen vorhanden sind.
MQTT hab ich noch nie verwendet. Das Protokoll findet unter anderem Anwendung in der Automatisierungstechnik. Es gibt es für SPS entsprechende Bibliotheken.
Wenn du ein WebInterface haben möchtest, dann nutze doch einfach Flask, Django, oder für APIs z.B.
https://fastapi.tiangolo.com/
Selbst einen Socketserver richtig zu implementieren, ist nicht einfach. Die API ist nicht kaputt, sie einfach nur sehr alt und gewachsen.
Wenn du das trotzdem alles selbst implementieren willst (sehr lehrreich), würde ich dir asyncio empfehlen. Dann lernst du gleich den Umgang mit asyncio
und lernst Protokolle selbst zu implementieren. Die Doku von asyncio ist überarbeitet worden und die API wurde verbessert.
Hier ein Beispiel:
https://docs.python.org/3/library/async ... cho-server
Ich würde aber einfach flask oder so verwenden. Mach es nicht komplizierter, als es sein muss.