Ladesteuerung einer Batterie via SocketServer

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

Guten Tag,

Ich habe ein Skript für die Ladesteuerung einer Batterie geschrieben (bitte keine DIskussion über den Sinn dahinter... es ist für mich erstmal ein Weg in ein Thema reinzukommen), das grundsätzliche Prinzip sieht folgendermaßen aus: Ich habe eine Ladeplatine, die ich mit einem Raspberry Pi (im Folgenden "Steuerungspi") via CAN anspreche. Je nach Nachricht, die ich der Ladeplatine schicke, setzt diese etwa eine Spannung an um die Batterie zu laden, sendet einen Heartbeat (wichtig, weil sonst die Ladeplatine nach einer gewissen Zeit "aus geht" ) oder beendet den Ladevorgang.

Weiterhin habe ich einen zweiten RasPi mit einem Pilogger (im Folgenden MessPi) der den Ladevorgang überwacht, also Spannung und Strom misst und abspeichert.

Nun möchte ich den Ladevorgang beenden und ein Relais schalten sobald der Messpi die Ladeschlussspannung misst. Dafür habe ich mich dazu entschieden einen Socketserver herzustellen, der solange wartet, bis der Messpi den Befehl sendet, dass die Ladeschlussspannung erreicht wurde. Da ich erst seit etwa 2 Monaten lerne zu Programmieren und auch vorher nie mit einem Raspberry in Berührung kam bitte ich um Nachsicht. Ich habe mir folgendes Tutorial für meinen Anwendungsfall zurechtgeschnitten. Wenn ich "nur" die Skripte für die Übertragung von Daten und daraus resultierenden Aktionen laufen lasse klappt eigentlich alles. Doch jetzt möchte ich das ganze in mein Ladeskript implementieren und bin dadurch leicht überfordert....


zunächst einmal mein Ladeskript auf dem Steuerpi (dem Sever):

Code: Alles auswählen

import can
from colorama import Fore
import socket
from Relais import closeRelais

STOP_CHARGE = 0x00200039
START_CHARGE = 0x00200038
HEARTBEAT_REQUEST = 0x0FE00000   #Can-Nachrichten für die Ladeplatine

host = ''
port = 5560


def setupServer():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Socket created.")
    try:
        s.bind((host, port))
    except socket.error as msg:
        print(msg)
    print("Socket bind complete.")
    return s

def setupConnection():
    s.listen(1) # Allows one connection at a time.
    conn, address = s.accept()
    print("Connected to: " + address[0] + ":" + str(address[1]))
    return conn


def dataTransfer(conn):
    # A big loop that sends/receives data until told not to.
    while True:
        data = conn.recv(1024) # receive the data
        data = data.decode('utf-8')
        # Split the data such that you separate the command from the rest of the data.
        dataMessage = data.split(' ', 1)
        command = dataMessage[0]
        if command == 'RELAIS_OFF':
            closeRelais()
            print("Relais geschlossen")
            reply = 'Relais closed'
            s.close()
        elif command == 'KILL':
            print("Our server is shutting down.")
            s.close()
            break
        else:
            reply = 'Unknown Command'
        # Send the reply back to the client
        conn.sendall(str.encode(reply))
        print("Data has been sent!")
    conn.close()


def send_message(bus, id, periodic=None):
    msg = can.Message(arbitration_id=id, data=[], extended_id=True)
    if periodic is None:
        bus.send(msg)
    else:
        bus.send_periodic(msg, periodic)

def start_charge(bus):
    send_message(bus, START_CHARGE)
    print(Fore.BLUE + "\n" "Charging initiated" "\n")
    print(Fore.RESET)

def stop_charge(bus):
    send_message(bus, STOP_CHARGE)
    print("Charging stopped. Battery full. Stop program")

def heartbeat(bus):
    send_message(bus, HEARTBEAT_REQUEST, periodic=10)


def main():

    try:
        bus = can.interface.Bus(channel='can0', bustype='socketcan_native') # Bus-channel aufbauen
    except OSError:
        print('Cannot find PiCAN board.')
        return

    try:
    	start_charge(bus)
    	heartbeat(bus)
        s = setupServer() #Socket herstellen und daten empfangen
        while True:
            try:
                print("1")
                conn = setupConnection()
                print("2")
                dataTransfer(conn)
                print("connected")
            except:
                print("broken")
                break  
    except KeyboardInterrupt:
        print(Fore.RED + '\n\r keyboard interrupted\n')
        print(Fore.RESET)
    finally:
        stop_charge(bus)

if __name__ == '__main__':
    main()
Und hier die Skripte auf dem Messpi:

Main

Code: Alles auswählen

from time import sleep
from Client import transmit
from Get_Data import getVolt
import sys


TRIGGER_VOLT = 41
SLEEP_TIME = 3



def voltMonitor():
    print("About to take a reading.")
    volt = float(getVolt())
    print("Our volt is: " + str(volt))
    if volt > float(TRIGGER_VOLT):
        message = 'RELAIS_OFF'
        print("Transmitting data.")
        response = transmit(message)
        print(response)
        sys.exit()



while True:

    voltMonitor()
    sleep(SLEEP_TIME)
Client

Code: Alles auswählen

import socket
from time import sleep

host = '192.168.1.187'
port = 5560

def setupSocket():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    return s

def sendReceive(s, message):
    s.send(str.encode(message))
    reply = s.recv(1024)
    print("We have received a reply")
    print("Send KILL message.")
    s.send(str.encode("KILL"))
    s.close()
    reply = reply.decode('utf-8')
    return reply

def transmit(message):
    s = setupSocket()
    response = sendReceive(s, message)
    return response
Get_Data

Code: Alles auswählen

import smbus

pilogger = smbus.SMBus(1)


def getVolt():
    volt_raw = pilogger.read_word_data(0x48, 0x50)
    volt = volt_raw * 0.000915541
    return volt

def getAmp():
    ampere_raw = pilogger.read_word_data(0x48, 0x60)
    if ampere_raw >= 32768:
        ampere_raw = ampere_raw - 65536  
    ampere = ampere_raw / 2141.634
    return ampere

Die Skripte auf dem Messpi machen soweit was sie tun sollen, jede Sekunde wird die Spannung aus dem Meßwertregister gezogen und umgewandelt.

Das Problem was ich habe ist, dass das Ladeskript auf dem Steuerpi nicht in dem dataTransfer-Loop bleibt und dort solange wartet, bis die "Relais-OFF" Nachricht vom Messpi kommt. Wenn ich das Ladeskript starte erhalte ich Folgendes:

Socket created.
Socket bind complete.
1
broken

Charging initiated

Charging stopped. Battery full. Stop program


scheinbar stimmt irgendwas mit der Connection nicht.. Da dieses Youtube tutorial auch dauernd irgendwelche Zeilen verändert und anpasst scheint da irgendwas verloren gegangen zu sein. Wenn ich nur das "Server" Skript auf dem Steuerpi laufen lasse, dann ist es so, dass der Steuerpi nach "socket bind complete" wartet.

Server

Code: Alles auswählen

import socket
from Relais import closeRelais

host = ''
port = 5560


def setupServer():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Socket created.")
    try:
        s.bind((host, port))
    except socket.error as msg:
        print(msg)
    print("Socket bind complete.")
    return s

def setupConnection():
    s.listen(1) # Allows one connection at a time.
    conn, address = s.accept()
    print("Connected to: " + address[0] + ":" + str(address[1]))
    return conn


def dataTransfer(conn):
    # A big loop that sends/receives data until told not to.
    while True:
        # Receive the data
        data = conn.recv(1024) # receive the data
        data = data.decode('utf-8')
        # Split the data such that you separate the command
        # from the rest of the data.
        dataMessage = data.split(' ', 1)
        command = dataMessage[0]
        if command == 'RELAIS_OFF':
            closeRelais()
            print("Relais geschlossen")
            reply = 'Relais closed'
            s.close()
        elif command == 'KILL':
            print("Our server is shutting down.")
            s.close()
            break
        else:
            reply = 'Unknown Command'
        # Send the reply back to the client
        conn.sendall(str.encode(reply))
        print("Data has been sent!")
    conn.close()
        

s = setupServer()

while True:
    try:
        conn = setupConnection()
        dataTransfer(conn)
    except:
        print("broken")
        break
Wenn auf dem Messpi dann die Main läuft und die Triggervolt erreicht passiert folgendes:

Socket created
Socket bind complete.
(an dieser stelle wird gewartet, bis Triggervolt erreicht wird, dann:)
Connected to: 192.168.1.161:41886
Relais geschlossen
Data has been sent!
Our server is shutting down.
broken


Wie gesagt schaffe ich es nicht, die Skripte für die Ladesteuerung und dieses Socketskript zu vereinen... Ich hoffe ihr könnt mir vielleicht weiterhelfen.

Grüße
Minzent
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Minzent: Vergiss erst einmal alles, was Du über Socketprogrammierung im Internet gefunden hast, das ist zu 99.999% kaputter Schrott. Benutze ein bereits etabliertes Protokoll wie Telnet oder HTTP, und die dazu passende Bibliothek, statt Dir selbst eins zu erfinden, das garantiert nicht richtig funktioniert.

Generell: halte Dich an die Konventionen. Funktionen schreibt man klein_mit_unterstricht, nicht setupServer sondern setup_server. Module schreibt man auch komplett klein. Benutze nicht einbuchstabige Variablennamen, server_socket ist besser als s. Funktionen müssen alles, was sie brauchen, über ihre Argumente bekommen damit sie nicht per NameError aussteigen.
Niemals nackte Excepts benutzen, denn so verschluckst Du auch Programmierfehler, wie der oben genannte NameError. Das Exceptionhandling macht nichts sinnvolles, außer "broken" zu schrieben, also komplett weg damit.

Im Main-Skript: sys.exit sollte man nicht verwenden, hier böte es sich an, einen Rückgabewert zu benutzen. Der Client benutzt wie der Server Sockets falsch (auch wenn Dein Youtube-Tutorial etwas anderes behauptet).
Antworten