Socket-Server-Programmierung zur Messwertabfrage

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

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!
Sirius3
User
Beiträge: 17823
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
Benutzeravatar
__blackjack__
User
Beiträge: 13236
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

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!
Benutzeravatar
__blackjack__
User
Beiträge: 13236
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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”.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

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).
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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
Benutzeravatar
__blackjack__
User
Beiträge: 13236
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

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
Benutzeravatar
DeaD_EyE
User
Beiträge: 1037
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

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.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten