mirror of
https://github.com/uhi22/pyPLC.git
synced 2024-11-20 01:13:58 +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):
|
def __init__(self, callbackAddToTrace):
|
||||||
self.callbackAddToTrace = callbackAddToTrace
|
self.callbackAddToTrace = callbackAddToTrace
|
||||||
self.addToTrace("initializing fsmEvse")
|
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.Tcp = pyPlcTcpSocket.pyPlcTcpServerSocket(self.callbackAddToTrace)
|
||||||
self.state = 0
|
self.state = 0
|
||||||
self.cyclesInState = 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
|
from exiConnector import * # for EXI data handling/converting
|
||||||
import json
|
import json
|
||||||
|
|
||||||
stateInitialized = 0
|
stateNotYetInitialized = 0
|
||||||
stateWaitForSupportedApplicationProtocolResponse = 1
|
stateConnecting = 1
|
||||||
stateWaitForSessionSetupResponse = 2
|
stateConnected = 2
|
||||||
stateWaitForServiceDiscoveryResponse = 3
|
stateWaitForSupportedApplicationProtocolResponse = 3
|
||||||
stateWaitForServicePaymentSelectionResponse = 4
|
stateWaitForSessionSetupResponse = 4
|
||||||
stateWaitForAuthorizationResponse = 5
|
stateWaitForServiceDiscoveryResponse = 5
|
||||||
stateWaitForChargeParameterDiscoveryResponse = 6
|
stateWaitForServicePaymentSelectionResponse = 6
|
||||||
stateWaitForCableCheckResponse = 7
|
stateWaitForAuthorizationResponse = 7
|
||||||
stateWaitForPreChargeResponse = 8
|
stateWaitForChargeParameterDiscoveryResponse = 8
|
||||||
stateWaitForPowerDeliveryResponse = 9
|
stateWaitForCableCheckResponse = 9
|
||||||
stateNotYetInitialized = 10
|
stateWaitForPreChargeResponse = 10
|
||||||
|
stateWaitForPowerDeliveryResponse = 11
|
||||||
|
stateSequenceTimeout = 99
|
||||||
|
|
||||||
|
|
||||||
dinEVSEProcessingType_Finished = "0"
|
dinEVSEProcessingType_Finished = "0"
|
||||||
dinEVSEProcessingType_Ongoing = "1"
|
dinEVSEProcessingType_Ongoing = "1"
|
||||||
|
@ -43,12 +46,33 @@ class fsmPev():
|
||||||
limit = 30*30 # PreCharge may need some time. Wait at least 30s.
|
limit = 30*30 # PreCharge may need some time. Wait at least 30s.
|
||||||
return (self.cyclesInState > limit)
|
return (self.cyclesInState > limit)
|
||||||
|
|
||||||
def stateFunctionInitialized(self):
|
def stateFunctionNotYetInitialized(self):
|
||||||
if (self.Tcp.isConnected):
|
pass # nothing to do, just wait for external event for re-initialization
|
||||||
# we just use the initial request message from the Ioniq. It contains one entry: DIN.
|
|
||||||
self.addToTrace("Sending the initial SupportedApplicationProtocolReq")
|
def stateFunctionConnecting(self):
|
||||||
self.Tcp.transmit(addV2GTPHeader(exiHexToByteArray(exiHexDemoSupportedApplicationProtocolRequestIoniq)))
|
if (self.cyclesInState<30): # The first second in the state just do nothing.
|
||||||
self.enterState(stateWaitForSupportedApplicationProtocolResponse)
|
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):
|
def stateFunctionWaitForSupportedApplicationProtocolResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -65,7 +89,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.enterState(stateWaitForSessionSetupResponse)
|
self.enterState(stateWaitForSessionSetupResponse)
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForSessionSetupResponse(self):
|
def stateFunctionWaitForSessionSetupResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -89,7 +113,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.enterState(stateWaitForServiceDiscoveryResponse)
|
self.enterState(stateWaitForServiceDiscoveryResponse)
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForServiceDiscoveryResponse(self):
|
def stateFunctionWaitForServiceDiscoveryResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -106,7 +130,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.enterState(stateWaitForServicePaymentSelectionResponse)
|
self.enterState(stateWaitForServicePaymentSelectionResponse)
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForServicePaymentSelectionResponse(self):
|
def stateFunctionWaitForServicePaymentSelectionResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -123,7 +147,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.enterState(stateWaitForChargeParameterDiscoveryResponse)
|
self.enterState(stateWaitForChargeParameterDiscoveryResponse)
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForChargeParameterDiscoveryResponse(self):
|
def stateFunctionWaitForChargeParameterDiscoveryResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -140,7 +164,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.enterState(stateWaitForCableCheckResponse)
|
self.enterState(stateWaitForCableCheckResponse)
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForCableCheckResponse(self):
|
def stateFunctionWaitForCableCheckResponse(self):
|
||||||
if (len(self.rxData)>0):
|
if (len(self.rxData)>0):
|
||||||
|
@ -176,7 +200,7 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
|
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(stateSequenceTimeout)
|
||||||
|
|
||||||
def stateFunctionWaitForPreChargeResponse(self):
|
def stateFunctionWaitForPreChargeResponse(self):
|
||||||
if (self.DelayCycles>0):
|
if (self.DelayCycles>0):
|
||||||
|
@ -197,13 +221,20 @@ class fsmPev():
|
||||||
self.Tcp.transmit(msg)
|
self.Tcp.transmit(msg)
|
||||||
self.DelayCycles=15 # wait with the next evaluation approx half a second
|
self.DelayCycles=15 # wait with the next evaluation approx half a second
|
||||||
if (self.isTooLong()):
|
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 = {
|
stateFunctions = {
|
||||||
stateInitialized: stateFunctionInitialized,
|
stateNotYetInitialized: stateFunctionNotYetInitialized,
|
||||||
|
stateConnecting: stateFunctionConnecting,
|
||||||
|
stateConnected: stateFunctionConnected,
|
||||||
stateWaitForSupportedApplicationProtocolResponse: stateFunctionWaitForSupportedApplicationProtocolResponse,
|
stateWaitForSupportedApplicationProtocolResponse: stateFunctionWaitForSupportedApplicationProtocolResponse,
|
||||||
stateWaitForSessionSetupResponse: stateFunctionWaitForSessionSetupResponse,
|
stateWaitForSessionSetupResponse: stateFunctionWaitForSessionSetupResponse,
|
||||||
stateWaitForServiceDiscoveryResponse: stateFunctionWaitForServiceDiscoveryResponse,
|
stateWaitForServiceDiscoveryResponse: stateFunctionWaitForServiceDiscoveryResponse,
|
||||||
|
@ -211,22 +242,15 @@ class fsmPev():
|
||||||
stateWaitForChargeParameterDiscoveryResponse: stateFunctionWaitForChargeParameterDiscoveryResponse,
|
stateWaitForChargeParameterDiscoveryResponse: stateFunctionWaitForChargeParameterDiscoveryResponse,
|
||||||
stateWaitForCableCheckResponse: stateFunctionWaitForCableCheckResponse,
|
stateWaitForCableCheckResponse: stateFunctionWaitForCableCheckResponse,
|
||||||
stateWaitForPreChargeResponse: stateFunctionWaitForPreChargeResponse,
|
stateWaitForPreChargeResponse: stateFunctionWaitForPreChargeResponse,
|
||||||
stateNotYetInitialized: stateFunctionNotYetInitialized
|
stateSequenceTimeout: stateFunctionSequenceTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
def reInit(self):
|
def reInit(self):
|
||||||
self.addToTrace("re-initializing fsmPev")
|
self.addToTrace("re-initializing fsmPev")
|
||||||
self.state = stateInitialized
|
self.Tcp.disconnect()
|
||||||
|
self.state = stateConnecting
|
||||||
self.cyclesInState = 0
|
self.cyclesInState = 0
|
||||||
self.rxData = []
|
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):
|
def __init__(self, addressManager, callbackAddToTrace):
|
||||||
self.callbackAddToTrace = callbackAddToTrace
|
self.callbackAddToTrace = callbackAddToTrace
|
||||||
|
|
|
@ -20,6 +20,10 @@ class pyPlcTcpClientSocket():
|
||||||
def __init__(self, callbackAddToTrace):
|
def __init__(self, callbackAddToTrace):
|
||||||
self.callbackAddToTrace = callbackAddToTrace
|
self.callbackAddToTrace = callbackAddToTrace
|
||||||
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
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.isConnected = False
|
||||||
self.rxData = []
|
self.rxData = []
|
||||||
|
|
||||||
|
@ -32,9 +36,9 @@ class pyPlcTcpClientSocket():
|
||||||
# for connecting, we are still in blocking-mode because
|
# 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"
|
# 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:
|
# 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)
|
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
|
# 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
|
# 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]
|
socket_addr = socket.getaddrinfo(host,port,socket.AF_INET6,socket.SOCK_DGRAM,socket.SOL_UDP)[0][4]
|
||||||
|
|
||||||
#print(socket_addr)
|
#print(socket_addr)
|
||||||
#print("step2b")
|
print("step2b")
|
||||||
# https://stackoverflow.com/questions/5358021/establishing-an-ipv6-connection-using-sockets-in-python
|
# https://stackoverflow.com/questions/5358021/establishing-an-ipv6-connection-using-sockets-in-python
|
||||||
# (address, port, flow info, scope id) 4-tuple for AF_INET6
|
# (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
|
# On Raspberry, the ScopeId is important. Just giving 0 leads to "invalid argument" in case
|
||||||
# of link-local ip-address.
|
# of link-local ip-address.
|
||||||
#self.sock.connect((host, port, 0, 0))
|
#self.sock.connect((host, port, 0, 0))
|
||||||
self.sock.connect(socket_addr)
|
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.sock.setblocking(0) # make this socket non-blocking, so that the recv function will immediately return
|
||||||
self.isConnected = True
|
self.isConnected = True
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
self.addToTrace("connection failed" + str(e))
|
self.addToTrace("connection failed" + str(e))
|
||||||
self.isConnected = False
|
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):
|
def transmit(self, msg):
|
||||||
if (self.isConnected == False):
|
if (self.isConnected == False):
|
||||||
|
|
Loading…
Reference in a new issue