mirror of
https://github.com/uhi22/pyPLC.git
synced 2024-11-10 01:05:42 +00:00
added retry of TCP connection on PEV side
This commit is contained in:
parent
6db498feb6
commit
c97eb8c393
3 changed files with 93 additions and 41 deletions
|
@ -184,6 +184,10 @@ class fsmEvse():
|
|||
def __init__(self, callbackAddToTrace):
|
||||
self.callbackAddToTrace = callbackAddToTrace
|
||||
self.addToTrace("initializing fsmEvse")
|
||||
self.faultInjectionDelayUntilSocketOpen_s = 0
|
||||
if (self.faultInjectionDelayUntilSocketOpen_s>0):
|
||||
self.addToTrace("Fault injection: waiting " + str(self.faultInjectionDelayUntilSocketOpen_s) + " s until opening the TCP socket.")
|
||||
time.sleep(self.faultInjectionDelayUntilSocketOpen_s)
|
||||
self.Tcp = pyPlcTcpSocket.pyPlcTcpServerSocket(self.callbackAddToTrace)
|
||||
self.state = 0
|
||||
self.cyclesInState = 0
|
||||
|
|
98
fsmPev.py
98
fsmPev.py
|
@ -10,17 +10,20 @@ from helpers import prettyHexMessage
|
|||
from exiConnector import * # for EXI data handling/converting
|
||||
import json
|
||||
|
||||
stateInitialized = 0
|
||||
stateWaitForSupportedApplicationProtocolResponse = 1
|
||||
stateWaitForSessionSetupResponse = 2
|
||||
stateWaitForServiceDiscoveryResponse = 3
|
||||
stateWaitForServicePaymentSelectionResponse = 4
|
||||
stateWaitForAuthorizationResponse = 5
|
||||
stateWaitForChargeParameterDiscoveryResponse = 6
|
||||
stateWaitForCableCheckResponse = 7
|
||||
stateWaitForPreChargeResponse = 8
|
||||
stateWaitForPowerDeliveryResponse = 9
|
||||
stateNotYetInitialized = 10
|
||||
stateNotYetInitialized = 0
|
||||
stateConnecting = 1
|
||||
stateConnected = 2
|
||||
stateWaitForSupportedApplicationProtocolResponse = 3
|
||||
stateWaitForSessionSetupResponse = 4
|
||||
stateWaitForServiceDiscoveryResponse = 5
|
||||
stateWaitForServicePaymentSelectionResponse = 6
|
||||
stateWaitForAuthorizationResponse = 7
|
||||
stateWaitForChargeParameterDiscoveryResponse = 8
|
||||
stateWaitForCableCheckResponse = 9
|
||||
stateWaitForPreChargeResponse = 10
|
||||
stateWaitForPowerDeliveryResponse = 11
|
||||
stateSequenceTimeout = 99
|
||||
|
||||
|
||||
dinEVSEProcessingType_Finished = "0"
|
||||
dinEVSEProcessingType_Ongoing = "1"
|
||||
|
@ -43,12 +46,33 @@ class fsmPev():
|
|||
limit = 30*30 # PreCharge may need some time. Wait at least 30s.
|
||||
return (self.cyclesInState > limit)
|
||||
|
||||
def stateFunctionInitialized(self):
|
||||
if (self.Tcp.isConnected):
|
||||
# we just use the initial request message from the Ioniq. It contains one entry: DIN.
|
||||
self.addToTrace("Sending the initial SupportedApplicationProtocolReq")
|
||||
self.Tcp.transmit(addV2GTPHeader(exiHexToByteArray(exiHexDemoSupportedApplicationProtocolRequestIoniq)))
|
||||
self.enterState(stateWaitForSupportedApplicationProtocolResponse)
|
||||
def stateFunctionNotYetInitialized(self):
|
||||
pass # nothing to do, just wait for external event for re-initialization
|
||||
|
||||
def stateFunctionConnecting(self):
|
||||
if (self.cyclesInState<30): # The first second in the state just do nothing.
|
||||
return
|
||||
evseIp = self.addressManager.getSeccIp() # the EVSE IP address which was found out with SDP
|
||||
self.Tcp.connect(evseIp, 15118) # This is a blocking call. If we come back, we are connected, or not.
|
||||
if (not self.Tcp.isConnected):
|
||||
# Bad case: Connection did not work. May happen if we are too fast and the charger needs more
|
||||
# time until the socket is ready. Or the charger is defective. Or somebody pulled the plug.
|
||||
# No matter what is the reason, we just try again and again. What else would make sense?
|
||||
self.addToTrace("Connection failed. Will try again.")
|
||||
self.reInit() # stay in same state, reset the cyclesInState and try again
|
||||
return
|
||||
else:
|
||||
# Good case: We are connected. Change to the next state.
|
||||
self.addToTrace("connected")
|
||||
self.enterState(stateConnected)
|
||||
return
|
||||
|
||||
def stateFunctionConnected(self):
|
||||
# We have a freshly established TCP channel. We start the V2GTP/EXI communication now.
|
||||
# We just use the initial request message from the Ioniq. It contains one entry: DIN.
|
||||
self.addToTrace("Sending the initial SupportedApplicationProtocolReq")
|
||||
self.Tcp.transmit(addV2GTPHeader(exiHexToByteArray(exiHexDemoSupportedApplicationProtocolRequestIoniq)))
|
||||
self.enterState(stateWaitForSupportedApplicationProtocolResponse)
|
||||
|
||||
def stateFunctionWaitForSupportedApplicationProtocolResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -65,7 +89,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.enterState(stateWaitForSessionSetupResponse)
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForSessionSetupResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -89,7 +113,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.enterState(stateWaitForServiceDiscoveryResponse)
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForServiceDiscoveryResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -106,7 +130,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.enterState(stateWaitForServicePaymentSelectionResponse)
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForServicePaymentSelectionResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -123,7 +147,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.enterState(stateWaitForChargeParameterDiscoveryResponse)
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForChargeParameterDiscoveryResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -140,7 +164,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.enterState(stateWaitForCableCheckResponse)
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForCableCheckResponse(self):
|
||||
if (len(self.rxData)>0):
|
||||
|
@ -176,7 +200,7 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionWaitForPreChargeResponse(self):
|
||||
if (self.DelayCycles>0):
|
||||
|
@ -197,13 +221,20 @@ class fsmPev():
|
|||
self.Tcp.transmit(msg)
|
||||
self.DelayCycles=15 # wait with the next evaluation approx half a second
|
||||
if (self.isTooLong()):
|
||||
self.enterState(0)
|
||||
self.enterState(stateSequenceTimeout)
|
||||
|
||||
def stateFunctionSequenceTimeout(self):
|
||||
# Here we end, if we run into a timeout in the state machine. This is an error case, and
|
||||
# we should re-initalize and try again to get a communication.
|
||||
# Todo: Maybe we want even inform the pyPlcHomeplug to do a new SLAC.
|
||||
# For the moment, we just re-establish the TCP connection.
|
||||
self.reInit()
|
||||
|
||||
def stateFunctionNotYetInitialized(self):
|
||||
pass # nothing to do, just wait for external event for re-initialization
|
||||
|
||||
stateFunctions = {
|
||||
stateInitialized: stateFunctionInitialized,
|
||||
stateNotYetInitialized: stateFunctionNotYetInitialized,
|
||||
stateConnecting: stateFunctionConnecting,
|
||||
stateConnected: stateFunctionConnected,
|
||||
stateWaitForSupportedApplicationProtocolResponse: stateFunctionWaitForSupportedApplicationProtocolResponse,
|
||||
stateWaitForSessionSetupResponse: stateFunctionWaitForSessionSetupResponse,
|
||||
stateWaitForServiceDiscoveryResponse: stateFunctionWaitForServiceDiscoveryResponse,
|
||||
|
@ -211,22 +242,15 @@ class fsmPev():
|
|||
stateWaitForChargeParameterDiscoveryResponse: stateFunctionWaitForChargeParameterDiscoveryResponse,
|
||||
stateWaitForCableCheckResponse: stateFunctionWaitForCableCheckResponse,
|
||||
stateWaitForPreChargeResponse: stateFunctionWaitForPreChargeResponse,
|
||||
stateNotYetInitialized: stateFunctionNotYetInitialized
|
||||
stateSequenceTimeout: stateFunctionSequenceTimeout
|
||||
}
|
||||
|
||||
def reInit(self):
|
||||
self.addToTrace("re-initializing fsmPev")
|
||||
self.state = stateInitialized
|
||||
self.Tcp.disconnect()
|
||||
self.state = stateConnecting
|
||||
self.cyclesInState = 0
|
||||
self.rxData = []
|
||||
if (not self.Tcp.isConnected):
|
||||
#evseIp = 'fe80:0000:0000:0000:c690:83f3:fbcb:980e'
|
||||
evseIp = self.addressManager.getSeccIp() # the EVSE IP address which was found out with SDP
|
||||
self.Tcp.connect(evseIp, 15118)
|
||||
if (not self.Tcp.isConnected):
|
||||
self.addToTrace("connection failed")
|
||||
else:
|
||||
self.addToTrace("connected")
|
||||
|
||||
def __init__(self, addressManager, callbackAddToTrace):
|
||||
self.callbackAddToTrace = callbackAddToTrace
|
||||
|
|
|
@ -20,6 +20,10 @@ class pyPlcTcpClientSocket():
|
|||
def __init__(self, callbackAddToTrace):
|
||||
self.callbackAddToTrace = callbackAddToTrace
|
||||
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
# https://stackoverflow.com/questions/29217502/socket-error-address-already-in-use
|
||||
# The SO_REUSEADDR flag tells the kernel to reuse a local socket in TIME_WAIT state, without
|
||||
# waiting for its natural timeout to expire.
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.isConnected = False
|
||||
self.rxData = []
|
||||
|
||||
|
@ -32,9 +36,9 @@ class pyPlcTcpClientSocket():
|
|||
# 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
|
||||
|
@ -52,19 +56,39 @@ class pyPlcTcpClientSocket():
|
|||
socket_addr = socket.getaddrinfo(host,port,socket.AF_INET6,socket.SOCK_DGRAM,socket.SOL_UDP)[0][4]
|
||||
|
||||
#print(socket_addr)
|
||||
#print("step2b")
|
||||
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:
|
||||
self.addToTrace("connection failed" + str(e))
|
||||
self.isConnected = False
|
||||
|
||||
def disconnect(self):
|
||||
# When connection is broken, the OS may still keep the information of socket usage in the background,
|
||||
# and this could raise the error "A connect request was made on an already connected socket" when
|
||||
# we try a simple connect again.
|
||||
# This is explained here:
|
||||
# https://www.codeproject.com/Questions/1187207/A-connect-request-was-made-on-an-already-connected
|
||||
try:
|
||||
self.sock.close() # Close is not just closing. It means destroying the socket object. So we
|
||||
# need a new one:
|
||||
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
# https://stackoverflow.com/questions/29217502/socket-error-address-already-in-use
|
||||
# The SO_REUSEADDR flag tells the kernel to reuse a local socket in TIME_WAIT state, without
|
||||
# waiting for its natural timeout to expire.
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
pass
|
||||
except socket.error as e:
|
||||
# if it was already closed, it is also fine.
|
||||
pass
|
||||
self.isConnected = False
|
||||
|
||||
def transmit(self, msg):
|
||||
if (self.isConnected == False):
|
||||
|
|
Loading…
Reference in a new issue