From 9a65441442d7351d041e318a83a1962fee90f5f2 Mon Sep 17 00:00:00 2001 From: uhi22 Date: Tue, 18 Apr 2023 21:50:46 +0200 Subject: [PATCH] feature: PevMode: integrated connection manager --- connMgr.py | 17 ++++ fsmPev.py | 4 +- pyPlcHomeplug.py | 200 +++++++++++++++++++++-------------------------- pyPlcIpv6.py | 4 +- pyPlcWorker.py | 26 +++--- 5 files changed, 124 insertions(+), 127 deletions(-) diff --git a/connMgr.py b/connMgr.py index 5735219..5e42bad 100644 --- a/connMgr.py +++ b/connMgr.py @@ -30,6 +30,18 @@ class connMgr(): def getConnectionLevel(self): return self.ConnectionLevel + def printDebugInfos(self): + s = "[CONNMGR] " + str(self.timerEthLink) + " " \ + + str(self.timerModemLocal) + " " \ + + str(self.timerModemRemote) + " " \ + + str(self.timerSlac) + " " \ + + str(self.timerSDP) + " " \ + + str(self.timerTCP) + " " \ + + str(self.timerAppl) + " " \ + + " --> " + str(self.ConnectionLevel) + self.addToTrace(s) + + def __init__(self, callbackAddToTrace, callbackShowStatus): self.timerEthLink = 0 self.timerModemLocal = 0 @@ -44,6 +56,8 @@ class connMgr(): self.addToTrace = callbackAddToTrace def mainfunction(self): + # shortcut: we do not check the ethernet link, instead, we assume it is just always present. + self.timerEthLink = 10 # count all the timers down if (self.timerEthLink>0): self.timerEthLink-=1 @@ -85,6 +99,9 @@ class connMgr(): if (self.ConnectionLevelOld!=self.ConnectionLevel): self.addToTrace("[CONNMGR] ConnectionLevel changed from " + str(self.ConnectionLevelOld) + " to " + str(self.ConnectionLevel)) self.ConnectionLevelOld = self.ConnectionLevel + if ((self.cycles % 33)==0): + # once per second + self.printDebugInfos() self.cycles+=1 def ModemFinderOk(self, numberOfFoundModems): diff --git a/fsmPev.py b/fsmPev.py index d7c5921..25abb78 100644 --- a/fsmPev.py +++ b/fsmPev.py @@ -43,6 +43,7 @@ class fsmPev(): self.callbackShowStatus(s, "pevState", strAuxInfo1, strAuxInfo2) def exiDecode(self, exidata, schema): + self.connMgr.ApplOk() s = compactHexMessage(exidata) strDateTime=datetime.today().strftime('%Y-%m-%dT%H:%M:%S.%f') self.exiLogFile.write(strDateTime + "=" + schema + " " + s +"\n") # write the EXI data to the exiLogFile @@ -593,7 +594,7 @@ class fsmPev(): self.cyclesInState = 0 self.rxData = [] - def __init__(self, addressManager, callbackAddToTrace, hardwareInterface, callbackShowStatus): + def __init__(self, addressManager, connMgr, callbackAddToTrace, hardwareInterface, callbackShowStatus): self.callbackAddToTrace = callbackAddToTrace self.callbackShowStatus = callbackShowStatus self.addToTrace("initializing fsmPev") @@ -601,6 +602,7 @@ class fsmPev(): self.exiLogFile.write("init\n") self.Tcp = pyPlcTcpSocket.pyPlcTcpClientSocket(self.callbackAddToTrace) self.addressManager = addressManager + self.connMgr = connMgr self.hardwareInterface = hardwareInterface self.state = stateNotYetInitialized self.sessionId = "DEAD55AADEAD55AA" diff --git a/pyPlcHomeplug.py b/pyPlcHomeplug.py index d21bf2d..164c8b8 100644 --- a/pyPlcHomeplug.py +++ b/pyPlcHomeplug.py @@ -582,7 +582,6 @@ class pyPlcHomeplug(): self.isDeveloperLocalKey = 1 else: self.addToTrace("This is NOT the developer NMK.") - self.localModemFound=1 s = "" # The getkey response contains the Network ID (NID), even if the request was rejected. We store the NID, # to have it available for the next request. Use case: A fresh started, unconnected non-Coordinator @@ -606,6 +605,8 @@ class pyPlcHomeplug(): self.addToTrace("SetKeyCnf says 0, this would be a bad sign for local modem, but normal for remote.") else: self.addToTrace("SetKeyCnf says " + str(result) + ", this is formally 'rejected', but indeed ok.") + self.publishStatus("modem is", "restarting") + self.connMgr.SlacOk() def evaluateGetSwCnf(self): # The GET_SW confirmation. This contains the software version of the homeplug modem. @@ -629,13 +630,13 @@ class pyPlcHomeplug(): def evaluateSlacParamReq(self): # We received a SLAC_PARAM request from the PEV. This is the initiation of a SLAC procedure. # We extract the pev MAC from it. - self.addToTrace("received SLAC_PARAM.REQ") - for i in range(0, 6): - self.pevMac[i] = self.myreceivebuffer[6+i] - self.addressManager.setPevMac(self.pevMac) - self.showStatus(prettyMac(self.pevMac), "pevmac") - # If we want to emulate an EVSE, we want to answer. if (self.iAmEvse==1): + self.addToTrace("received SLAC_PARAM.REQ") + for i in range(0, 6): + self.pevMac[i] = self.myreceivebuffer[6+i] + self.addressManager.setPevMac(self.pevMac) + self.showStatus(prettyMac(self.pevMac), "pevmac") + # We are EVSE, we want to answer. self.showStatus("SLAC started", "evseState") self.composeSlacParamCnf() self.addToTrace("[EVSE] transmitting CM_SLAC_PARAM.CNF") @@ -767,74 +768,63 @@ class pyPlcHomeplug(): self.evseSlacHandlerState = 1 # setkey was done return + def publishStatus(self, s1, s2="", s3=""): + self.showStatus(s1+s2+s3, "pevState") + + def modemFinder_Mainfunction(self): + if ((self.connMgr.getConnectionLevel()==5) and (self.mofi_state==0)): + # We want the modem search only, if no connection is present at all. + self.addToTrace("[ModemFinder] Starting modem search") + self.publishStatus("Modem search") + self.composeGetSwReq() + self.transmit(self.mytransmitbuffer) + self.numberOfSoftwareVersionResponses = 0 # we want to count the modems. Start from zero. + self.mofi_stateDelay = 15 # 0.5s should be sufficient to receive the software versions from the modems + self.mofi_state = 1 + return + if (self.mofi_state==1): + # waiting for responses of the modems + if (self.mofi_stateDelay>0): + self.mofi_stateDelay-=1 + return + # waiting time is expired. Lets look how many responses we got. + self.addToTrace("[ModemFinder] Number of modems:" + str(self.numberOfSoftwareVersionResponses)) + self.publishStatus("Modems:", str(self.numberOfSoftwareVersionResponses)) + if (self.numberOfSoftwareVersionResponses>0): + self.connMgr.ModemFinderOk(self.numberOfSoftwareVersionResponses) + self.mofi_stateDelay = 15 # 0.5s to show the number of modems, before we start a new search if necessary + self.mofi_state=2 + return + if (self.mofi_state==2): + # just waiting, to give the user time to read the result. + if (self.mofi_stateDelay>0): + self.mofi_stateDelay-=1 + return + self.mofi_state=0 # back to idle state def runPevSequencer(self): # in PevMode, check whether homeplug modem is connected, run the SLAC and SDP self.pevSequenceCyclesInState+=1 + if (self.connMgr.getConnectionLevel()<10): + # we have no modem seen. --> nothing to do for the SLAC + if (self.pevSequenceState!=STATE_INITIAL): + self.enterState(STATE_INITIAL) + return + if (self.connMgr.getConnectionLevel()>=20): + # we have two modems in the AVLN. This means, the modem pairing is already done. --> nothing to do for the SLAC + if (self.pevSequenceState!=STATE_INITIAL): + self.enterState(STATE_INITIAL) + return if (self.pevSequenceState==STATE_INITIAL): # Initial state. # In real life we would check whether we see 5% PWM on the pilot line. We skip this check. self.isSimulationMode = self.isForcedSimulationMode # from command line, we can force the simulation mode self.isSDPDone = 0 - self.numberOfFoundModems = 0 - self.localModemFound = 0 self.isDeveloperLocalKey = 0 self.nEvseModemMissingCounter = 0 - self.showStatus("Modem search", "pevState") - # First action: find the connected homeplug modems, by sending a GET_KEY - self.composeGetKey() - self.addToTrace("[PEVSLAC] searching for modems, transmitting GET_KEY.REQ...") - self.transmit(self.mytransmitbuffer) - self.enterState(STATE_MODEM_SEARCH_ONGOING) - return - if (self.pevSequenceState==STATE_MODEM_SEARCH_ONGOING): # Waiting for the modems to answer. - if (self.pevSequenceCyclesInState>=10): - # It was sufficient time to get the answers from the modems. - self.addToTrace("[PEVSLAC] It was sufficient time to get the answers from the modems.") - # Let's see what we received. - if (self.numberOfFoundModems==0): - self.addToTrace("[PEVSLAC] No modem connected. We assume, we run in a simulation environment.") - self.isSimulationMode = 1 - self.enterState(STATE_READY_FOR_SLAC) - return - else: - if (self.localModemFound): - # we have at least a local modem. Maybe more remote. - # We want to make sure, that the local modem is set to the "default key", so that - # it is possible to connect to the PLC network for development purposes. - # That's why we check the key, and if it is not the intended, we set the default. - if (self.isDeveloperLocalKey): - self.enterState(STATE_READY_FOR_SLAC) - return - else: - self.composeSetKey() # set the default ("developer") key - self.addToTrace("[PEVSLAC] setting the default developer key...") - self.transmit(self.mytransmitbuffer) - self.enterState(STATE_WAITING_FOR_MODEM_RESTARTED) - return - else: - # we found modems, but non of it could be identified as the local modem. This happens - # when we do not use the matching NID for the request. We try again, because in the - # first response we got a better NID in the meanwhile. - self.addToTrace("[PEVSLAC] local modem not yet identified.") - self.numberOfFoundModems = 0 - # find the connected homeplug modems, by sending a GET_KEY - self.composeGetKey() - self.addToTrace("[PEVSLAC] searching for modems, transmitting GET_KEY.REQ...") - self.transmit(self.mytransmitbuffer) - self.enterState(STATE_MODEM_SEARCH_ONGOING) - return - - return - if (self.pevSequenceState==STATE_WAITING_FOR_MODEM_RESTARTED): # Waiting for the modem to restart after SetKey. - if (self.pevSequenceCyclesInState>=100): - self.addToTrace("[PEVSLAC] Assuming the modem is up again.") - self.enterState(STATE_READY_FOR_SLAC) + self.enterState(STATE_READY_FOR_SLAC) return if (self.pevSequenceState==STATE_READY_FOR_SLAC): - if (self.numberOfFoundModems==0): - self.showStatus("NoModem, simuSLAC", "pevState") - else: - self.showStatus("Starting SLAC", "pevState") + self.showStatus("Starting SLAC", "pevState") self.addToTrace("[PEVSLAC] Checkpoint100: Sending SLAC_PARAM.REQ...") self.composeSlacParamReq() self.transmit(self.mytransmitbuffer) @@ -961,49 +951,37 @@ class pyPlcHomeplug(): if (self.isSimulationMode): self.addToTrace("[PEVSLAC] But this is only simulated.") self.nEvseModemMissingCounter=0 - self.pevSequenceDelayCycles=0 - self.composeGetSwReq() - self.addToTrace("[PEVSLAC] Requesting SW versions from the modems...") - self.transmit(self.mytransmitbuffer) - self.enterState(STATE_WAITING_FOR_SW_VERSIONS) + self.connMgr.ModemFinderOk(2) # Two modems were found. + # This is the end of the SLAC. + # The AVLN is established, we have at least two modems in the network. + self.enterState(STATE_INITIAL) + return - if (self.pevSequenceState==STATE_WAITING_FOR_SW_VERSIONS): - if (self.pevSequenceCyclesInState>=2): # 2 cycles = 60ms are more than sufficient. - # Measured: The local modem answers in less than 1ms. The remote modem in ~5ms. - # It was sufficient time to get the answers from the modems. - self.addToTrace("[PEVSLAC] It was sufficient time to get the SW versions from the modems.") - self.enterState(STATE_READY_FOR_SDP) + # invalid state is reached. As robustness measure, go to initial state. + self.enterState(STATE_INITIAL) + + def runSdpStateMachine(self): + if (self.connMgr.getConnectionLevel()<20): + # We have no AVLN established. It does not make sense to start SDP. + self.sdp_state = 0 return - if (self.pevSequenceState==STATE_READY_FOR_SDP): - # 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. - if (self.isSDPDone): - # SDP is already done. No need to do it again. We are finished for the normal case. - # But we want to check whether the connection is still alive, so we start the - # modem-search from time to time. - self.pevSequenceDelayCycles = 300 # e.g. 10s - self.enterState(STATE_WAITING_FOR_RESTART2) - return - # SDP was not done yet. Now we start it. - self.showStatus("SDP ongoing", "pevState") - self.addToTrace("[PEVSLAC] Checkpoint200: SDP was not done yet. Now we start it.") + if (self.connMgr.getConnectionLevel()>20): + # SDP was already successful. No need to run it again. + self.sdp_state = 0 + return + # The ConnectionLevel demands the SDP. + if (self.sdp_state==0): # Next step is to discover the chargers communication controller (SECC) using discovery protocol (SDP). + self.publishStatus("SDP ongoing") + self.addToTrace("[SDP] Checkpoint200: Starting SDP.") self.pevSequenceDelayCycles=0 self.SdpRepetitionCounter = 50 # prepare the number of retries for the SDP. The more the better. - self.enterState(STATE_SDP) + self.sdp_state = 1 return - - if (self.pevSequenceState==STATE_SDP): # SDP request transmission and waiting for SDP response. - if (len(self.addressManager.getSeccIp())>0): - # we received an SDP response, and can start the high-level communication - self.showStatus("SDP finished", "pevState") - self.addToTrace("[PEVSLAC] Now we know the chargers IP.") - self.isSDPDone = 1 - self.callbackReadyForTcp(1) - # Continue with checking the connection, for the case somebody pulls the plug. - self.pevSequenceDelayCycles = 300 # e.g. 10s - self.enterState(STATE_WAITING_FOR_RESTART2) - return + if (self.sdp_state == 1): # SDP request transmission and waiting for SDP response. + # The normal state transition in case of received SDP response is done in + # the IPv6 receive handler. This will inform the ConnectionManager, and we will stop here + # because of the increased ConnectionLevel. if (self.pevSequenceDelayCycles>0): # just waiting until next action self.pevSequenceDelayCycles-=1 @@ -1014,15 +992,11 @@ class pyPlcHomeplug(): self.ipv6.initiateSdpRequest() self.SdpRepetitionCounter-=1 self.pevSequenceDelayCycles = 15 # e.g. half-a-second delay until re-try of the SDP - self.enterState(STATE_SDP) # stick in the same state return # All repetitions are over, no SDP response was seen. Back to the beginning. - self.addToTrace("[PEVSLAC] ERROR: Did not receive SDP response. Starting from the beginning.") - self.enterState(STATE_INITIAL) - return - # invalid state is reached. As robustness measure, go to initial state. - self.enterState(STATE_INITIAL) - + self.addToTrace("[SDP] ERROR: Did not receive SDP response. Giving up.") + self.sdp_state = 0 + def findEthernetAdaptor(self): self.strInterfaceName="eth0" # default, if the real is not found @@ -1053,19 +1027,21 @@ class pyPlcHomeplug(): 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): + def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, mode=C_LISTEN_MODE, addrMan=None, connMgr=None, isSimulationMode=0): self.mytransmitbuffer = bytearray("Hallo das ist ein Test", 'UTF-8') self.nPacketsReceived = 0 self.callbackAddToTrace = callbackAddToTrace self.callbackShowStatus = callbackShowStatus - self.callbackReadyForTcp = callbackReadyForTcp self.addressManager = addrMan + self.connMgr = connMgr self.randomMac = 0 self.pevSequenceState = 0 self.pevSequenceCyclesInState = 0 self.evseSlacHandlerState = 0 self.numberOfSoftwareVersionResponses = 0 self.numberOfFoundModems = 0 + self.mofi_state = 0 + self.mofi_stateDelay = 0 self.isForcedSimulationMode = isSimulationMode # simulation without homeplug modem #self.sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50) # eth3 means: Third entry from back, in the list of interfaces, which is provided by pcap.findalldevs. @@ -1093,7 +1069,7 @@ class pyPlcHomeplug(): self.evseMac = [0x55, 0x56, 0x57, 0xAA, 0xAA, 0xAA ] # a default evse MAC. Will be overwritten later. self.myMAC = self.addressManager.getLocalMacAddress() self.runningCounter=0 - self.ipv6 = pyPlcIpv6.ipv6handler(self.transmit, self.addressManager, self.callbackShowStatus) + self.ipv6 = pyPlcIpv6.ipv6handler(self.transmit, self.addressManager, self.connMgr, self.callbackShowStatus) self.ipv6.ownMac = self.myMAC self.udplog = udplog.udplog(self.transmit, self.addressManager) self.udplog.log("Test message to verify the syslog. pyPlcHomeplug.py is in the init function.") @@ -1130,7 +1106,9 @@ class pyPlcHomeplug(): self.sniffer.dispatch(100, self.receiveCallback, None) self.showStatus("nPacketsReceived=" + str(self.nPacketsReceived)) if (self.iAmPev==1): - self.runPevSequencer() # run the message sequencer for the PEV side + self.modemFinder_Mainfunction() # run the modem finder cyclic function + self.runPevSequencer() # run the SLAC message sequencer for the PEV side + self.runSdpStateMachine() # run the SDP state machine if (self.iAmEvse==1): self.runEvseSlacHandler(); # run the SLAC state machine on EVSE side diff --git a/pyPlcIpv6.py b/pyPlcIpv6.py index 0d8f556..1ac7f7c 100644 --- a/pyPlcIpv6.py +++ b/pyPlcIpv6.py @@ -189,6 +189,7 @@ class ipv6handler(): seccTcpPort = (self.udpPayload[8+16]*256) + self.udpPayload[8+16+1] self.addressManager.setSeccIp(self.SeccIp) self.addressManager.setSeccTcpPort(seccTcpPort) + self.connMgr.SdpOk() return print("v2gptPayloadType " + hex(v2gptPayloadType) + " not supported") @@ -354,11 +355,12 @@ class ipv6handler(): if (self.nextheader == 0x06): # it is an TCP frame self.evaluateTcpPacket() - def __init__(self, transmitCallback, addressManager, callbackShowStatus): + def __init__(self, transmitCallback, addressManager, connMgr, callbackShowStatus): self.enterEvseMode() #self.enterListenMode() self.transmit = transmitCallback self.addressManager = addressManager + self.connMgr = connMgr self.callbackShowStatus = callbackShowStatus #self.exiLogFile = open('SnifferExiLog.txt', 'w') # 16 bytes, a default IPv6 address for the charging station diff --git a/pyPlcWorker.py b/pyPlcWorker.py index b557432..a231e00 100644 --- a/pyPlcWorker.py +++ b/pyPlcWorker.py @@ -14,6 +14,7 @@ import addressManager import time import subprocess import hardwareInterface +import connMgr class pyPlcWorker(): @@ -27,7 +28,8 @@ class pyPlcWorker(): self.callbackShowStatus = callbackShowStatus self.oldAvlnStatus = 0 self.isSimulationMode = isSimulationMode - self.hp = pyPlcHomeplug.pyPlcHomeplug(self.workerAddToTrace, self.showStatus, self.mode, self.addressManager, self.callbackReadyForTcp, self.isSimulationMode) + self.connMgr = connMgr.connMgr(self.workerAddToTrace, self.showStatus) + self.hp = pyPlcHomeplug.pyPlcHomeplug(self.workerAddToTrace, self.showStatus, self.mode, self.addressManager, self.connMgr, self.isSimulationMode) self.hardwareInterface = hardwareInterface.hardwareInterface(self.workerAddToTrace, self.showStatus) self.hp.printToUdp("pyPlcWorker init") # Find out the version number, using git. @@ -40,7 +42,7 @@ class pyPlcWorker(): if (self.mode == C_EVSE_MODE): self.evse = fsmEvse.fsmEvse(self.addressManager, self.workerAddToTrace, self.hardwareInterface, self.showStatus) if (self.mode == C_PEV_MODE): - self.pev = fsmPev.fsmPev(self.addressManager, self.workerAddToTrace, self.hardwareInterface, self.showStatus) + self.pev = fsmPev.fsmPev(self.addressManager, self.connMgr, self.workerAddToTrace, self.hardwareInterface, self.showStatus) def __del__(self): if (self.mode == C_PEV_MODE): try: @@ -60,24 +62,20 @@ class pyPlcWorker(): if (selection == "pevState"): self.hardwareInterface.showOnDisplay(s, strAuxInfo1, strAuxInfo2) - def callbackReadyForTcp(self, status): - if (status==1): + def handleTcpConnectionTrigger(self): + if (self.mode == C_PEV_MODE) and (self.connMgr.getConnectionLevel()==50) and (self.oldAvlnStatus==0): self.workerAddToTrace("[PLCWORKER] Network is established, ready for TCP.") - #self.workerAddToTrace("[PLCWORKER] Waiting....") - #time.sleep(5) - #self.workerAddToTrace("[PLCWORKER] now...") - if (self.oldAvlnStatus==0): - self.oldAvlnStatus = 1 - if (self.mode == C_PEV_MODE): - self.pev.reInit() - - else: - self.workerAddToTrace("[PLCWORKER] no network") + self.oldAvlnStatus = 1 + self.pev.reInit() + return + if (self.connMgr.getConnectionLevel()<50): self.oldAvlnStatus = 0 def mainfunction(self): self.nMainFunctionCalls+=1 #self.showStatus("pyPlcWorker loop " + str(self.nMainFunctionCalls)) + self.connMgr.mainfunction() + self.handleTcpConnectionTrigger() self.hp.mainfunction() # call the lower-level workers self.hardwareInterface.mainfunction() if (self.mode == C_EVSE_MODE):