From 43d96f333efe9dc79376daa1bcbff54cb59bb592 Mon Sep 17 00:00:00 2001 From: uhi22 Date: Tue, 22 Nov 2022 21:34:27 +0100 Subject: [PATCH] Logging to UDP broadcast port 514 --- fsmEvse.py | 58 ++++++++++++++++-------------- fsmPev.py | 74 ++++++++++++++++++++------------------ pyPlcHomeplug.py | 15 ++++++-- pyPlcTcpSocket.py | 44 +++++++++++++---------- pyPlcWorker.py | 23 +++++++----- udplog.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 91 deletions(-) create mode 100755 udplog.py diff --git a/fsmEvse.py b/fsmEvse.py index 5baab89..5e0fd21 100644 --- a/fsmEvse.py +++ b/fsmEvse.py @@ -20,8 +20,11 @@ stateWaitForPreChargeRequest = 7 stateWaitForPowerDeliveryRequest = 8 class fsmEvse(): + def addToTrace(self, s): + self.callbackAddToTrace(s) + def enterState(self, n): - print("from " + str(self.state) + " entering " + str(n)) + self.addToTrace("from " + str(self.state) + " entering " + str(n)) self.state = n self.cyclesInState = 0 @@ -32,31 +35,31 @@ class fsmEvse(): def stateFunctionWaitForSupportedApplicationProtocolRequest(self): if (len(self.rxData)>0): - print("In state WaitForSupportedApplicationProtocolRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForSupportedApplicationProtocolRequest, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DH") # Decode Handshake-request - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ProtocolNamespace=urn:din")>0): # todo: of course we should care for schemaID and prio also here - print("Detected DIN") + self.addToTrace("Detected DIN") # Eh for encode handshake, SupportedApplicationProtocolResponse msg = addV2GTPHeader(exiEncode("Eh")) - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForSessionSetupRequest) def stateFunctionWaitForSessionSetupRequest(self): if (len(self.rxData)>0): - print("In state stateFunctionWaitForSessionSetupRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state stateFunctionWaitForSessionSetupRequest, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("SessionSetupReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDa")) # EDa for Encode, Din, SessionSetupResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForServiceDiscoveryRequest) if (self.isTooLong()): @@ -64,15 +67,15 @@ class fsmEvse(): def stateFunctionWaitForServiceDiscoveryRequest(self): if (len(self.rxData)>0): - print("In state WaitForServiceDiscoveryRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForServiceDiscoveryRequest, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ServiceDiscoveryReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDb")) # EDb for Encode, Din, ServiceDiscoveryResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForServicePaymentSelectionRequest) if (self.isTooLong()): @@ -80,15 +83,15 @@ class fsmEvse(): def stateFunctionWaitForServicePaymentSelectionRequest(self): if (len(self.rxData)>0): - print("In state WaitForServicePaymentSelectionRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForServicePaymentSelectionRequest, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ServicePaymentSelectionReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDc")) # EDc for Encode, Din, ServicePaymentSelectionResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified. The Ioniq sends PowerDeliveryReq as next. if (self.isTooLong()): @@ -96,40 +99,40 @@ class fsmEvse(): def stateFunctionWaitForFlexibleRequest(self): if (len(self.rxData)>0): - print("In state WaitForFlexibleRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForFlexibleRequest, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("PowerDeliveryReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDh")) # EDh for Encode, Din, PowerDeliveryResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified in DIN if (strConverterResult.find("ChargeParameterDiscoveryReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDe")) # EDe for Encode, Din, ChargeParameterDiscoveryResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified in DIN if (strConverterResult.find("CableCheckReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDf")) # EDf for Encode, Din, CableCheckResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified in DIN if (strConverterResult.find("PreChargeReq")>0): # todo: check the request content, and fill response parameters strPresentVoltage = "345" msg = addV2GTPHeader(exiEncode("EDg_"+strPresentVoltage)) # EDg for Encode, Din, PreChargeResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified in DIN if (strConverterResult.find("ContractAuthenticationReq")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDl")) # EDl for Encode, Din, ContractAuthenticationResponse - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForFlexibleRequest) # todo: not clear, what is specified in DIN @@ -152,8 +155,8 @@ class fsmEvse(): def stateFunctionWaitForPowerDeliveryRequest(self): if (len(self.rxData)>0): - print("In state WaitForPowerDeliveryRequest, received " + prettyHexMessage(self.rxData)) - print("Todo: Reaction in state WaitForPowerDeliveryRequest is not implemented yet.") + self.addToTrace("In state WaitForPowerDeliveryRequest, received " + prettyHexMessage(self.rxData)) + self.addToTrace("Todo: Reaction in state WaitForPowerDeliveryRequest is not implemented yet.") self.rxData = [] self.enterState(0) if (self.isTooLong()): @@ -173,13 +176,14 @@ class fsmEvse(): } def reInit(self): - print("re-initializing fsmEvse") + self.addToTrace("re-initializing fsmEvse") self.state = 0 self.cyclesInState = 0 self.rxData = [] - def __init__(self): - print("initializing fsmEvse") + def __init__(self, callbackAddToTrace): + self.callbackAddToTrace = callbackAddToTrace + self.addToTrace("initializing fsmEvse") self.Tcp = pyPlcTcpSocket.pyPlcTcpServerSocket() self.state = 0 self.cyclesInState = 0 @@ -189,7 +193,7 @@ class fsmEvse(): self.Tcp.mainfunction() # call the lower-level worker if (self.Tcp.isRxDataAvailable()): self.rxData = self.Tcp.getRxData() - #print("received " + str(self.rxData)) + #self.addToTrace("received " + str(self.rxData)) # run the state machine: self.cyclesInState += 1 # for timeout handling, count how long we are in a state self.stateFunctions[self.state](self) diff --git a/fsmPev.py b/fsmPev.py index e4fd8ef..7340c5f 100644 --- a/fsmPev.py +++ b/fsmPev.py @@ -26,6 +26,9 @@ dinEVSEProcessingType_Finished = "0" dinEVSEProcessingType_Ongoing = "1" class fsmPev(): + def addToTrace(self, s): + self.callbackAddToTrace(s) + def enterState(self, n): print("from " + str(self.state) + " entering " + str(n)) self.state = n @@ -48,15 +51,15 @@ class fsmPev(): def stateFunctionWaitForSupportedApplicationProtocolResponse(self): if (len(self.rxData)>0): - print("In state WaitForSupportedApplicationProtocolResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForSupportedApplicationProtocolResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "Dh") # Decode Handshake-response - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("supportedAppProtocolRes")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDA")) # EDA for Encode, Din, SessionSetupReq - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForSessionSetupResponse) if (self.isTooLong()): @@ -64,22 +67,22 @@ class fsmPev(): def stateFunctionWaitForSessionSetupResponse(self): if (len(self.rxData)>0): - print("In state WaitForSessionSetupResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForSessionSetupResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("SessionSetupRes")>0): # todo: check the request content, and fill response parameters try: y = json.loads(strConverterResult) strSessionId = y["header.SessionID"] - print("[PEV] The Evse decided for SessionId " + strSessionId) + self.addToTrace("[PEV] The Evse decided for SessionId " + strSessionId) self.sessionId = strSessionId except: - print("ERROR: Could not decode the sessionID") + self.addToTrace("ERROR: Could not decode the sessionID") msg = addV2GTPHeader(exiEncode("EDB_"+self.sessionId)) # EDB for Encode, Din, ServiceDiscoveryRequest - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForServiceDiscoveryResponse) if (self.isTooLong()): @@ -87,15 +90,15 @@ class fsmPev(): def stateFunctionWaitForServiceDiscoveryResponse(self): if (len(self.rxData)>0): - print("In state WaitForServiceDiscoveryResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForServiceDiscoveryResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ServiceDiscoveryRes")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDC_"+self.sessionId)) # EDC for Encode, Din, ServicePaymentSelection - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForServicePaymentSelectionResponse) if (self.isTooLong()): @@ -103,15 +106,15 @@ class fsmPev(): def stateFunctionWaitForServicePaymentSelectionResponse(self): if (len(self.rxData)>0): - print("In state WaitForServicePaymentSelectionResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForServicePaymentSelectionResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ServicePaymentSelectionRes")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDE_"+self.sessionId)) # EDE for Encode, Din, ChargeParameterDiscovery. We ignore Authorization, not specified in DIN. - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForChargeParameterDiscoveryResponse) if (self.isTooLong()): @@ -119,15 +122,15 @@ class fsmPev(): def stateFunctionWaitForChargeParameterDiscoveryResponse(self): if (len(self.rxData)>0): - print("In state WaitForChargeParameterDiscoveryResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForChargeParameterDiscoveryResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("ChargeParameterDiscoveryRes")>0): # todo: check the request content, and fill response parameters msg = addV2GTPHeader(exiEncode("EDF_"+self.sessionId)) # EDF for Encode, Din, CableCheck - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForCableCheckResponse) if (self.isTooLong()): @@ -135,32 +138,32 @@ class fsmPev(): def stateFunctionWaitForCableCheckResponse(self): if (len(self.rxData)>0): - print("In state WaitForCableCheckResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForCableCheckResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("CableCheckRes")>0): try: y = json.loads(strConverterResult) strResponseCode = y["ResponseCode"] strEVSEProcessing = y["EVSEProcessing"] - print("[PEV] The CableCheck result is " + strResponseCode + " " + strEVSEProcessing) + self.addToTrace("[PEV] The CableCheck result is " + strResponseCode + " " + strEVSEProcessing) except: - print("ERROR: Could not decode the CableCheckRes") + self.addToTrace("ERROR: Could not decode the CableCheckRes") # todo: check the request content, and fill response parameters # We have two cases here: # 1) The charger says "cable check is finished and cable ok", by setting ResponseCode=OK and EVSEProcessing=Finished. # 2) Else: The charger says "need more time or cable not ok". In this case, we just run into timeout and start from the beginning. if ((strEVSEProcessing==dinEVSEProcessingType_Finished) and (strResponseCode=="OK")): msg = addV2GTPHeader(exiEncode("EDG_"+self.sessionId)) # EDG for Encode, Din, PreCharge - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.enterState(stateWaitForPreChargeResponse) else: # cable check not yet finished or finished with bad result -> try again msg = addV2GTPHeader(exiEncode("EDF_"+self.sessionId)) # EDF for Encode, Din, CableCheck - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) if (self.isTooLong()): @@ -171,17 +174,17 @@ class fsmPev(): self.DelayCycles-=1 return if (len(self.rxData)>0): - print("In state WaitForPreChargeResponse, received " + prettyHexMessage(self.rxData)) + self.addToTrace("In state WaitForPreChargeResponse, received " + prettyHexMessage(self.rxData)) exidata = removeV2GTPHeader(self.rxData) self.rxData = [] strConverterResult = exiDecode(exidata, "DD") # Decode DIN - print(strConverterResult) + self.addToTrace(strConverterResult) if (strConverterResult.find("PreChargeRes")>0): # todo: check the request content, and fill response parameters - print("PreCharge aknowledge received.") - print("As Demo, we stay in PreCharge forever.") + self.addToTrace("PreCharge aknowledge received.") + self.addToTrace("As Demo, we stay in PreCharge forever.") msg = addV2GTPHeader(exiEncode("EDG_"+self.sessionId)) # EDG for Encode, Din, PreCharge - print("responding " + prettyHexMessage(msg)) + self.addToTrace("responding " + prettyHexMessage(msg)) self.Tcp.transmit(msg) self.DelayCycles=15 # wait with the next evaluation approx half a second if (self.isTooLong()): @@ -203,7 +206,7 @@ class fsmPev(): } def reInit(self): - print("re-initializing fsmPev") + self.addToTrace("re-initializing fsmPev") self.state = stateInitialized self.cyclesInState = 0 self.rxData = [] @@ -212,13 +215,14 @@ class fsmPev(): evseIp = self.addressManager.getSeccIp() # the EVSE IP address which was found out with SDP self.Tcp.connect(evseIp, 15118) if (not self.Tcp.isConnected): - print("connection failed") + self.addToTrace("connection failed") else: - print("connected") + self.addToTrace("connected") - def __init__(self, addressManager): - print("initializing fsmPev") - self.Tcp = pyPlcTcpSocket.pyPlcTcpClientSocket() + def __init__(self, addressManager, callbackAddToTrace): + self.callbackAddToTrace = callbackAddToTrace + self.addToTrace("initializing fsmPev") + self.Tcp = pyPlcTcpSocket.pyPlcTcpClientSocket(self.callbackAddToTrace) self.addressManager = addressManager self.state = stateNotYetInitialized self.sessionId = "DEAD55AADEAD55AA" @@ -231,7 +235,7 @@ class fsmPev(): #self.Tcp.mainfunction() # call the lower-level worker if (self.Tcp.isRxDataAvailable()): self.rxData = self.Tcp.getRxData() - #print("received " + prettyHexMessage(self.rxData)) + #self.addToTrace("received " + prettyHexMessage(self.rxData)) # run the state machine: self.cyclesInState += 1 # for timeout handling, count how long we are in a state self.stateFunctions[self.state](self) diff --git a/pyPlcHomeplug.py b/pyPlcHomeplug.py index 2ea7577..56f6d20 100644 --- a/pyPlcHomeplug.py +++ b/pyPlcHomeplug.py @@ -35,6 +35,8 @@ import pcap import pyPlcIpv6 +import udplog +import time from helpers import * # prettyMac etc from pyPlcModes import * @@ -918,8 +920,10 @@ class pyPlcHomeplug(): self.pevSequenceDelayCycles=30 self.enterState(STATE_WAITING_FOR_RESTART2) return - # The EVSE modem is present. + # The EVSE modem is present (or we are simulating) self.addToTrace("[PEVSLAC] EVSE is up, pairing successful.") + if (self.isSimulationMode): + self.addToTrace("[PEVSLAC] But this is only simulated.") self.nEvseModemMissingCounter=0 # The AVLN is established, we have at least two modems in the network. # If we did not SDP up to now, let's do it. @@ -992,7 +996,10 @@ class pyPlcHomeplug(): self.iAmEvse = 0 # not emulating a charging station self.iAmPev = 0 # not emulating a vehicle self.ipv6.enterListenMode() - self.showStatus("LISTEN mode", "mode") + self.showStatus("LISTEN mode", "mode") + + def printToUdp(self, s): + self.udplog.log(s) def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, mode=C_LISTEN_MODE, addrMan=None, callbackReadyForTcp=None, isSimulationMode=0): self.mytransmitbuffer = bytearray("Hallo das ist ein Test", 'UTF-8') @@ -1033,6 +1040,10 @@ class pyPlcHomeplug(): self.runningCounter=0 self.ipv6 = pyPlcIpv6.ipv6handler(self.transmit, self.addressManager) self.ipv6.ownMac = self.myMAC + self.udplog = udplog.udplog(self.transmit, self.addressManager) + for k in range(0, 10): + self.udplog.log("Test message number " + str(k)) + time.sleep(0.1) if (mode == C_LISTEN_MODE): self.enterListenMode() if (mode == C_EVSE_MODE): diff --git a/pyPlcTcpSocket.py b/pyPlcTcpSocket.py index d5ae645..6923464 100644 --- a/pyPlcTcpSocket.py +++ b/pyPlcTcpSocket.py @@ -17,20 +17,24 @@ import os import subprocess class pyPlcTcpClientSocket(): - def __init__(self): + def __init__(self, callbackAddToTrace): + self.callbackAddToTrace = callbackAddToTrace self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.isConnected = False self.rxData = [] + + def addToTrace(self, s): + self.callbackAddToTrace(s) def connect(self, host, port): try: - print("connecting to " + str(host) + " port " + str(port) + "...") + self.addToTrace("connecting to " + str(host) + " port " + str(port) + "...") # for connecting, we are still in blocking-mode because # otherwise we run into error "[Errno 10035] A non-blocking socket operation could not be completed immediately" # We set a shorter timeout, so we do not block too long if the connection is not established: - print("step1") + #print("step1") self.sock.settimeout(0.5) - print("step2") + #print("step2") # https://stackoverflow.com/questions/71022092/python3-socket-program-udp-ipv6-bind-function-with-link-local-ip-address-gi # While on Windows10 just connecting to a remote link-local-address works, under @@ -41,25 +45,25 @@ class pyPlcTcpClientSocket(): # host = "fe80::c690:83f3:fbcb:980e%eth0" # ok with socket.getaddrinfo if (os.name != 'nt'): # We are at the Raspberry - print(host[0:5].lower()) + #print(host[0:5].lower()) if (host[0:5].lower()=="fe80:"): - print("This is a link local address. We need to add %eth0 at the end.") + #print("This is a link local address. We need to add %eth0 at the end.") host = host + "%eth0" socket_addr = socket.getaddrinfo(host,port,socket.AF_INET6,socket.SOCK_DGRAM,socket.SOL_UDP)[0][4] - print(socket_addr) - print("step2b") + #print(socket_addr) + #print("step2b") # https://stackoverflow.com/questions/5358021/establishing-an-ipv6-connection-using-sockets-in-python # (address, port, flow info, scope id) 4-tuple for AF_INET6 # On Raspberry, the ScopeId is important. Just giving 0 leads to "invalid argument" in case # of link-local ip-address. #self.sock.connect((host, port, 0, 0)) self.sock.connect(socket_addr) - print("step3") + #print("step3") self.sock.setblocking(0) # make this socket non-blocking, so that the recv function will immediately return self.isConnected = True except socket.error as e: - print("connection failed", e) + self.addToTrace("connection failed", e) self.isConnected = False def transmit(self, msg): @@ -73,7 +77,7 @@ class pyPlcTcpClientSocket(): sent = self.sock.send(msg[totalsent:]) if sent == 0: self.isConnected = False - print("socket connection broken") + self.addToTrace("socket connection broken") return -1 totalsent = totalsent + sent except: @@ -116,7 +120,8 @@ class pyPlcTcpClientSocket(): return d class pyPlcTcpServerSocket(): - def __init__(self): + def __init__(self, callbackAddToTrace): + self.callbackAddToTrace = callbackAddToTrace # Todo: find the link-local IPv6 address automatically. #self.ipAdress = 'fe80::e0ad:99ac:52eb:85d3' #self.ipAdress = 'fe80::c690:83f3:fbcb:980e%15' @@ -133,19 +138,22 @@ class pyPlcTcpServerSocket(): self.ourSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ourSocket.bind((self.ipAdress, self.tcpPort)) self.ourSocket.listen(1) - print("pyPlcTcpSocket listening on port " + str(self.tcpPort)) + self.addToTrace("pyPlcTcpSocket listening on port " + str(self.tcpPort)) hostname=socket.gethostname() IPAddr=socket.gethostbyname(hostname) addressInfo = socket.getaddrinfo(hostname, None, socket.AF_INET6) #print("Your Computer Name is:"+hostname) - print("The socket is linked the following IP addresses:") + self.addToTrace("The socket is linked the following IP addresses:") for i in range(0, len(addressInfo)): #fe80::4c46:fea5:b6c9:25a9 IPv6Addr = addressInfo[i][4][0] - print(IPv6Addr) + self.addToTrace(IPv6Addr) self.read_list = [self.ourSocket] self.rxData = [] + def addToTrace(self, s): + self.callbackAddToTrace(s) + def isRxDataAvailable(self): return (len(self.rxData)>0) @@ -167,7 +175,7 @@ class pyPlcTcpServerSocket(): while totalsent < MSGLEN: sent = self.read_list[1].send(txMessage[totalsent:]) if sent == 0: - print("socket connection broken") + self.addToTrace("socket connection broken") return -1 totalsent = totalsent + sent return 0 # success @@ -184,7 +192,7 @@ class pyPlcTcpServerSocket(): client_socket, address = self.ourSocket.accept() # and we append this new socket to the list of sockets, which in the next loop will be handled by the select. self.read_list.append(client_socket) - print("Connection from", address) + self.addToTrace("Connection from", address) else: # It is not the "listener socket", it is an above created "client socket" for talking with a client. # Let's take the data from it: @@ -198,7 +206,7 @@ class pyPlcTcpServerSocket(): # print("received data:", data) self.rxData = data else: - print("connection closed") + self.addToTrace("connection closed") s.close() self.read_list.remove(s) diff --git a/pyPlcWorker.py b/pyPlcWorker.py index 9a8d94e..4913e19 100644 --- a/pyPlcWorker.py +++ b/pyPlcWorker.py @@ -23,28 +23,33 @@ class pyPlcWorker(): self.callbackShowStatus = callbackShowStatus self.oldAvlnStatus = 0 self.isSimulationMode = isSimulationMode - self.hp = pyPlcHomeplug.pyPlcHomeplug(self.callbackAddToTrace, self.callbackShowStatus, self.mode, self.addressManager, self.callbackReadyForTcp, self.isSimulationMode) + self.hp = pyPlcHomeplug.pyPlcHomeplug(self.workerAddToTrace, self.callbackShowStatus, self.mode, self.addressManager, self.callbackReadyForTcp, self.isSimulationMode) + self.hp.printToUdp("pyPlcWorker init") if (self.mode == C_EVSE_MODE): - self.evse = fsmEvse.fsmEvse() + self.evse = fsmEvse.fsmEvse(self.workerAddToTrace) if (self.mode == C_PEV_MODE): - self.pev = fsmPev.fsmPev(self.addressManager) + self.pev = fsmPev.fsmPev(self.addressManager, self.workerAddToTrace) - def addToTrace(self, s): - self.callbackAddToTrace(s) + def workerAddToTrace(self, s): + # The central logging function. All logging messages from the different parts of the project + # shall come here. + #print("workerAddToTrace " + s) + self.callbackAddToTrace(s) # give the message to the upper level, eg for console log. + self.hp.printToUdp(s) # give the message to the udp for remote logging. def showStatus(self, s, selection = ""): self.callbackShowStatus(s, selection) def callbackReadyForTcp(self, status): if (status==1): - print("[PLCWORKER] Network is established, ready for TCP.") + self.workerAddToTrace("[PLCWORKER] Network is established, ready for TCP.") if (self.oldAvlnStatus==0): self.oldAvlnStatus = 1 if (self.mode == C_PEV_MODE): self.pev.reInit() else: - print("[PLCWORKER] no network") + self.workerAddToTrace("[PLCWORKER] no network") self.oldAvlnStatus = 0 def mainfunction(self): @@ -67,7 +72,7 @@ class pyPlcWorker(): self.hp.enterPevMode() if (not hasattr(self, 'pev')): print("creating pev") - self.pev = fsmPev.fsmPev(self.addressManager) + self.pev = fsmPev.fsmPev(self.addressManager, self.workerAddToTrace) self.pev.reInit() if (strAction == "E"): print("switching to EVSE mode") @@ -78,7 +83,7 @@ class pyPlcWorker(): self.hp.enterEvseMode() if (not hasattr(self, 'evse')): print("creating fsmEvse") - self.evse = fsmEvse.fsmEvse() + self.evse = fsmEvse.fsmEvse(self.workerAddToTrace) self.evse.reInit() if (strAction == "L"): print("switching to LISTEN mode") diff --git a/udplog.py b/udplog.py new file mode 100755 index 0000000..23d6149 --- /dev/null +++ b/udplog.py @@ -0,0 +1,90 @@ + +# This module "prints" log messages to UDP broadcasts to port 514. + +from helpers import prettyMac + +class udplog(): + def fillMac(self, macbytearray, position=6): # position 6 is the source MAC + for i in range(0, 6): + self.EthTxFrame[6+i] = macbytearray[i] + + + def log(self, s): + strLevel="<15>" + strMessage=s+"\0" + + lenPayLoad = 4 + len(strMessage) + + buffer=bytearray(lenPayLoad+28) + # syslog level (4 characters) + for i in range(0, len(strLevel)): + buffer[28+i] = ord(strLevel[i]) + # the message, terminated by 00 + for i in range(0, len(strMessage)): + buffer[32+i] = ord(strMessage[i]) + buffer[0] = 0x45 # IP header len + buffer[1] = 0 + iplen=len(buffer) # including IP header and all payload + buffer[2] = iplen >> 8 + buffer[3] = (iplen & 0xff) + ipid = 0x8a9f + buffer[4] = ipid >> 8 + buffer[5] = (ipid & 0xff) + fragoffset = 0 + buffer[6] = fragoffset >> 8 + buffer[7] = (fragoffset & 0xff) + buffer[8] = 0x80 # ttl + buffer[9] = 0x11 # proto + checksum = 0 + buffer[10] = checksum >> 8 + buffer[11] = (checksum & 0xff) + # source ip 4 bytes + buffer[12] = 192 # does this really matter? + buffer[13] = 168 + buffer[14] = 2 + buffer[15] = 222 + # destination ip 4 bytes: broadcast + buffer[16] = 0xff + buffer[17] = 0xff + buffer[18] = 0xff + buffer[19] = 0xff + # source port + buffer[20] = 0xff + buffer[21] = 0x95 + # destination port + buffer[22] = 0x02 + buffer[23] = 0x02 + udplen = lenPayLoad + 8 # payload plus 8 byte udp header + buffer[24] = udplen >> 8 + buffer[25] = (udplen & 0xff) + udpchecksum = 0 + buffer[26] = udpchecksum >> 8 + buffer[27] = (udpchecksum & 0xff) + + + + # packs the IP packet into an ethernet packet + self.EthTxFrame = bytearray(len(buffer) + 6 + 6 + 2) # Ethernet header needs 14 bytes: + # 6 bytes destination MAC + # 6 bytes source MAC + # 2 bytes EtherType + # fill the destination MAC with broadcast MAC + self.EthTxFrame[0] = 0xFF + self.EthTxFrame[1] = 0xFF + self.EthTxFrame[2] = 0xFF + self.EthTxFrame[3] = 0xFF + self.EthTxFrame[4] = 0xFF + self.EthTxFrame[5] = 0xFF + self.fillMac(self.ownMac) # bytes 6 to 11 are the source MAC + self.EthTxFrame[12] = 0x08 # 0800 is IPv4 + self.EthTxFrame[13] = 0x00 + for i in range(0, len(buffer)): + self.EthTxFrame[14+i] = buffer[i] + self.transmit(self.EthTxFrame) + + + def __init__(self, transmitCallback, addressManager): + self.transmit = transmitCallback + self.addressManager = addressManager + self.ownMac = self.addressManager.getLocalMacAddress() + print("udplog started with ownMac " + prettyMac(self.ownMac))