2022-11-07 08:21:25 +00:00
# State machine for the car
#
#
#------------------------------------------------------------
import pyPlcTcpSocket
import time # for time.sleep()
2022-12-20 07:35:58 +00:00
from datetime import datetime
2023-04-05 19:01:43 +00:00
from helpers import prettyHexMessage , compactHexMessage , combineValueAndMultiplier
2023-05-16 07:29:42 +00:00
from mytestsuite import *
2022-11-07 12:38:06 +00:00
from exiConnector import * # for EXI data handling/converting
2022-11-16 18:33:37 +00:00
import json
2023-04-15 18:35:51 +00:00
from configmodule import getConfigValue , getConfigValueBool
2022-11-07 08:21:25 +00:00
2022-12-08 23:22:18 +00:00
2022-11-29 07:40:34 +00:00
stateNotYetInitialized = 0
stateConnecting = 1
stateConnected = 2
stateWaitForSupportedApplicationProtocolResponse = 3
stateWaitForSessionSetupResponse = 4
stateWaitForServiceDiscoveryResponse = 5
stateWaitForServicePaymentSelectionResponse = 6
2022-12-02 12:02:40 +00:00
stateWaitForContractAuthenticationResponse = 7
2022-11-29 07:40:34 +00:00
stateWaitForChargeParameterDiscoveryResponse = 8
2023-05-04 17:08:58 +00:00
stateWaitForConnectorLock = 9
stateWaitForCableCheckResponse = 10
stateWaitForPreChargeResponse = 11
stateWaitForContactorsClosed = 12
stateWaitForPowerDeliveryResponse = 13
stateWaitForCurrentDemandResponse = 14
stateWaitForWeldingDetectionResponse = 15
stateWaitForSessionStopResponse = 16
stateChargingFinished = 17
2023-05-03 19:57:36 +00:00
stateUnrecoverableError = 88
2022-11-29 07:40:34 +00:00
stateSequenceTimeout = 99
2023-05-04 17:08:58 +00:00
stateSafeShutDownWaitForChargerShutdown = 111
stateSafeShutDownWaitForContactorsOpen = 222
stateEnd = 1000
2022-11-29 07:40:34 +00:00
2022-11-07 08:21:25 +00:00
2022-11-18 11:27:24 +00:00
dinEVSEProcessingType_Finished = " 0 "
dinEVSEProcessingType_Ongoing = " 1 "
2022-11-07 08:21:25 +00:00
class fsmPev ( ) :
2022-11-22 20:34:27 +00:00
def addToTrace ( self , s ) :
2022-11-22 20:48:05 +00:00
self . callbackAddToTrace ( " [PEV] " + s )
2022-11-22 20:34:27 +00:00
2023-02-27 09:53:42 +00:00
def publishStatus ( self , s , strAuxInfo1 = " " , strAuxInfo2 = " " ) :
self . callbackShowStatus ( s , " pevState " , strAuxInfo1 , strAuxInfo2 )
2022-12-19 17:09:39 +00:00
2022-12-09 11:06:10 +00:00
def exiDecode ( self , exidata , schema ) :
2023-04-18 19:50:46 +00:00
self . connMgr . ApplOk ( )
2022-12-09 11:06:10 +00:00
s = compactHexMessage ( exidata )
2022-12-20 07:35:58 +00:00
strDateTime = datetime . today ( ) . strftime ( ' % Y- % m- %d T % H: % M: % S. %f ' )
self . exiLogFile . write ( strDateTime + " = " + schema + " " + s + " \n " ) # write the EXI data to the exiLogFile
2022-12-09 11:06:10 +00:00
return exiDecode ( exidata , schema ) # call the decoder
def exiEncode ( self , input ) :
schema = input [ 0 : 2 ]
exidata = exiEncode ( input ) # call the encoder
s = exidata # it is already a hex string
2022-12-20 07:35:58 +00:00
strDateTime = datetime . today ( ) . strftime ( ' % Y- % m- %d T % H: % M: % S. %f ' )
self . exiLogFile . write ( strDateTime + " = " + schema + " " + s + " \n " ) # write the EXI data to the exiLogFile
2022-12-09 11:06:10 +00:00
return exidata
2023-04-11 20:09:26 +00:00
def prettifyState ( self , statenumber ) :
s = " unknownState "
if ( statenumber == stateNotYetInitialized ) :
s = " NotYetInitialized "
if ( statenumber == stateConnecting ) :
s = " Connecting "
if ( statenumber == stateConnected ) :
s = " Connected "
if ( statenumber == stateWaitForSupportedApplicationProtocolResponse ) :
s = " WaitForSupportedApplicationProtocolResponse "
if ( statenumber == stateWaitForSessionSetupResponse ) :
s = " WaitForSessionSetupResponse "
if ( statenumber == stateWaitForServiceDiscoveryResponse ) :
s = " WaitForServiceDiscoveryResponse "
if ( statenumber == stateWaitForServicePaymentSelectionResponse ) :
s = " WaitForServicePaymentSelectionResponse "
if ( statenumber == stateWaitForContractAuthenticationResponse ) :
s = " WaitForContractAuthenticationResponse "
if ( statenumber == stateWaitForChargeParameterDiscoveryResponse ) :
s = " WaitForChargeParameterDiscoveryResponse "
2023-05-04 17:08:58 +00:00
if ( statenumber == stateWaitForConnectorLock ) :
s = " WaitForConnectorLock "
2023-04-11 20:09:26 +00:00
if ( statenumber == stateWaitForCableCheckResponse ) :
s = " WaitForCableCheckResponse "
if ( statenumber == stateWaitForPreChargeResponse ) :
s = " WaitForPreChargeResponse "
2023-05-03 19:25:01 +00:00
if ( statenumber == stateWaitForContactorsClosed ) :
s = " WaitForContactorsClosed "
2023-04-11 20:09:26 +00:00
if ( statenumber == stateWaitForPowerDeliveryResponse ) :
s = " WaitForPowerDeliveryResponse "
if ( statenumber == stateWaitForCurrentDemandResponse ) :
s = " WaitForCurrentDemandResponse "
if ( statenumber == stateWaitForWeldingDetectionResponse ) :
s = " WaitForWeldingDetectionResponse "
if ( statenumber == stateWaitForSessionStopResponse ) :
s = " WaitForSessionStopResponse "
if ( statenumber == stateChargingFinished ) :
s = " ChargingFinished "
2023-05-04 06:53:01 +00:00
if ( statenumber == stateUnrecoverableError ) :
s = " UnrecoverableError "
2023-04-11 20:09:26 +00:00
if ( statenumber == stateSequenceTimeout ) :
s = " SequenceTimeout "
2023-05-04 17:08:58 +00:00
if ( statenumber == stateSafeShutDownWaitForChargerShutdown ) :
s = " SafeShutDownWaitForChargerShutdown "
if ( statenumber == stateSafeShutDownWaitForContactorsOpen ) :
s = " SafeShutDownWaitForContactorsOpen "
if ( statenumber == stateEnd ) :
s = " End "
2023-04-11 20:09:26 +00:00
return s
2023-05-10 17:17:26 +00:00
def isErrorEvseStatusCode ( self , strEvseStatusCode ) :
# 0 is EVSE_NotReady. This may be normal, no error, just wait.
# 1 is EVSE_Ready: The normal case.
if ( strEvseStatusCode == " 2 " ) : # EVSE_Shutdown: The user stopped the charging normally on charger.
self . addToTrace ( " EVSE_Shutdown. Seems the user canceled the charging on the charger. " )
self . publishStatus ( " EVSE_Shutdown " )
return True
if ( strEvseStatusCode == " 3 " ) : # EVSE_UtilityInterruptEvent: Stopped or power reduction
self . addToTrace ( " EVSE_UtilityInterruptEvent. " )
self . publishStatus ( " EVSE_UtilityInterruptEvent " )
return True
# 4 is EVSE_IsolationMonitoringActive. This is normal, no error.
if ( strEvseStatusCode == " 5 " ) : # EVSE_EmergencyShutdown: Error or Notaus button
self . addToTrace ( " EVSE_EmergencyShutdown. " )
self . publishStatus ( " EVSE_EmergencyShutdown " )
return True
if ( strEvseStatusCode == " 6 " ) : # EVSE_Malfunction: Error
self . addToTrace ( " EVSE_Malfunction. " )
self . publishStatus ( " EVSE_Malfunction " )
return True
if ( strEvseStatusCode == " 7 " ) : # Reserved, Error
return True
if ( strEvseStatusCode == " 8 " ) : # Reserved, Error
return True
if ( strEvseStatusCode == " 9 " ) : # Reserved, Error
return True
if ( strEvseStatusCode == " 10 " ) : # Reserved, Error
return True
if ( strEvseStatusCode == " 11 " ) : # Reserved, Error
return True
return False # no critical error detected
2022-12-09 13:23:32 +00:00
def sendChargeParameterDiscoveryReq ( self ) :
soc = str ( self . hardwareInterface . getSoc ( ) )
msg = addV2GTPHeader ( self . exiEncode ( " EDE_ " + self . sessionId + " _ " + soc ) ) # EDE for Encode, Din, ChargeParameterDiscovery.
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
def sendCableCheckReq ( self ) :
soc = str ( self . hardwareInterface . getSoc ( ) )
msg = addV2GTPHeader ( self . exiEncode ( " EDF_ " + self . sessionId + " _ " + soc ) ) # EDF for Encode, Din, CableCheckReq
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
2023-05-22 06:20:37 +00:00
# Since the response to the CableCheckRequest may need longer, inform the connection manager to be patient.
# This makes sure, that the timeout of the state machine comes before the timeout of the connectionManager, so
# that we enter the safe shutdown sequence as intended.
self . connMgr . ApplOk ( 31 )
2022-12-09 13:23:32 +00:00
def sendCurrentDemandReq ( self ) :
soc = str ( self . hardwareInterface . getSoc ( ) )
2022-12-09 22:45:58 +00:00
EVTargetCurrent = str ( self . hardwareInterface . getAccuMaxCurrent ( ) )
EVTargetVoltage = str ( self . hardwareInterface . getAccuMaxVoltage ( ) )
2022-12-09 13:23:32 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDI_ " + self . sessionId + " _ " + soc + " _ " + EVTargetCurrent + " _ " + EVTargetVoltage ) ) # EDI for Encode, Din, CurrentDemandReq
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
def sendWeldingDetectionReq ( self ) :
soc = str ( self . hardwareInterface . getSoc ( ) )
msg = addV2GTPHeader ( self . exiEncode ( " EDJ_ " + self . sessionId + " _ " + soc ) ) # EDI for Encode, Din, WeldingDetectionReq
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
2022-11-07 08:21:25 +00:00
def enterState ( self , n ) :
2023-04-11 20:09:26 +00:00
self . addToTrace ( " from " + str ( self . state ) + " : " + self . prettifyState ( self . state ) + " entering " + str ( n ) + " : " + self . prettifyState ( n ) )
2022-11-07 08:21:25 +00:00
self . state = n
self . cyclesInState = 0
def isTooLong ( self ) :
# The timeout handling function.
2023-05-10 18:56:48 +00:00
limit = 66 # number of call cycles until timeout. Default 66 cycles with 30ms, means approx. 2 seconds.
# This 2s is the specified timeout time for many messages, fitting to the
# performance time of 1.5s. Exceptions see below.
2023-05-03 06:03:51 +00:00
if ( self . state == stateWaitForChargeParameterDiscoveryResponse ) :
limit = 5 * 33 # On some charger models, the chargeParameterDiscovery needs more than a second. Wait at least 5s.
2022-11-18 11:27:24 +00:00
if ( self . state == stateWaitForCableCheckResponse ) :
2023-04-16 19:21:04 +00:00
limit = 30 * 33 # CableCheck may need some time. Wait at least 30s.
2022-11-18 11:27:24 +00:00
if ( self . state == stateWaitForPreChargeResponse ) :
2023-04-16 19:21:04 +00:00
limit = 30 * 33 # PreCharge may need some time. Wait at least 30s.
if ( self . state == stateWaitForPowerDeliveryResponse ) :
2023-05-10 18:56:48 +00:00
limit = 6 * 33 # PowerDelivery may need some time. Wait at least 6s. On Compleo charger, observed more than 1s until response.
# specified performance time is 4.5s (ISO)
if ( self . state == stateWaitForCurrentDemandResponse ) :
limit = 5 * 33 # Test with 5s timeout. Just experimental.
# The specified performance time is 25ms (ISO), the specified timeout 250ms.
2022-11-18 11:27:24 +00:00
return ( self . cyclesInState > limit )
2022-11-07 08:21:25 +00:00
2022-11-29 07:40:34 +00:00
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
2022-12-01 10:51:11 +00:00
evseIp = self . addressManager . getSeccIp ( ) # the chargers IP address which was announced in SDP
seccTcpPort = self . addressManager . getSeccTcpPort ( ) # the chargers TCP port which was announced in SDP
2023-03-20 20:32:57 +00:00
self . addToTrace ( " Checkpoint301: connecting " )
2022-12-01 10:51:11 +00:00
self . Tcp . connect ( evseIp , seccTcpPort ) # This is a blocking call. If we come back, we are connected, or not.
2022-11-29 07:40:34 +00:00
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 " )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " TCP connected " )
self . isUserStopRequest = False
2022-11-29 07:40:34 +00:00
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.
2023-03-20 20:32:57 +00:00
self . addToTrace ( " Checkpoint400: Sending the initial SupportedApplicationProtocolReq " )
2022-11-29 07:40:34 +00:00
self . Tcp . transmit ( addV2GTPHeader ( exiHexToByteArray ( exiHexDemoSupportedApplicationProtocolRequestIoniq ) ) )
2023-06-27 19:22:21 +00:00
# For testing purposes, we can also use the requests from other cars:
#self.Tcp.transmit(addV2GTPHeader(exiHexToByteArray(exiHexDemoSupportedApplicationProtocolRequestTesla)))
#self.Tcp.transmit(addV2GTPHeader(exiHexToByteArray(exiHexDemoSupportedApplicationProtocolRequestBMWiX3)))
2022-12-10 16:08:51 +00:00
self . hardwareInterface . resetSimulation ( )
2022-11-29 07:40:34 +00:00
self . enterState ( stateWaitForSupportedApplicationProtocolResponse )
2022-11-07 08:21:25 +00:00
def stateFunctionWaitForSupportedApplicationProtocolResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForSupportedApplicationProtocolResponse, received " + prettyHexMessage ( self . rxData ) )
2023-05-13 22:22:26 +00:00
if ( ( self . rxData [ 0 ] != 0x01 ) or ( self . rxData [ 1 ] != 0xFE ) ) :
# it is no EXI data. Print it to log, it could be a TESTSUITE notification.
self . addToTrace ( " TESTSUITE notification. Seems we are running a test case. TTTTTTTTTTTTTTTTTTTTTTT " )
2023-05-13 23:07:54 +00:00
if ( len ( self . rxData ) < = 20 ) :
# it was the length of the testsuite notification. We are finished with this message.
self . rxData = [ ]
return
else :
# There was more data than the 20 byte testsuite notification. Most likely the EXI comes in the same message.
self . rxData = self . rxData [ 20 : ]
2022-11-08 11:01:54 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " Dh " ) # Decode Handshake-response
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " supportedAppProtocolRes " ) > 0 ) :
# todo: check the request content, and fill response parameters
2022-12-19 17:09:39 +00:00
self . publishStatus ( " Schema negotiated " )
2023-03-20 20:32:57 +00:00
self . addToTrace ( " Checkpoint403: Schema negotiated. And Checkpoint500: Will send SessionSetupReq " )
2023-03-23 07:50:53 +00:00
self . addToTrace ( " EDA_ " + self . evccid )
msg = addV2GTPHeader ( self . exiEncode ( " EDA_ " + self . evccid ) ) # EDA for Encode, Din, SessionSetupReq
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-11-08 21:53:18 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForSessionSetupResponse )
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-11-08 11:01:54 +00:00
def stateFunctionWaitForSessionSetupResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForSessionSetupResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " SessionSetupRes " ) > 0 ) :
# todo: check the request content, and fill response parameters
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2022-11-16 18:33:37 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strSessionId = jsondict [ " header.SessionID " ]
strResponseCode = jsondict [ " ResponseCode " ]
2023-03-20 20:32:57 +00:00
self . addToTrace ( " Checkpoint506: The Evse decided for SessionId " + strSessionId )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " Session established " )
2022-11-16 18:33:37 +00:00
self . sessionId = strSessionId
except :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " ERROR: Could not decode the sessionID " )
2023-12-12 11:23:49 +00:00
2023-07-03 18:28:10 +00:00
if ( ( strResponseCode != " OK_NewSessionEstablished " ) and ( strResponseCode != " OK " ) ) :
# According to the standard, the only valid response code is OK_NewSessionEstablished.
# But the ABB chargers use "OK", so we need to accept this, too. Discussed
# here: https://openinverter.org/forum/viewtopic.php?p=58399#p58399
2023-05-15 17:45:11 +00:00
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2022-11-24 06:46:45 +00:00
self . addToTrace ( " Will send ServiceDiscoveryReq " )
2022-12-09 11:06:10 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDB_ " + self . sessionId ) ) # EDB for Encode, Din, ServiceDiscoveryRequest
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-11-08 21:53:18 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForServiceDiscoveryResponse )
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForServiceDiscoveryResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForServiceDiscoveryResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " ServiceDiscoveryRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
2023-05-15 17:45:11 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the ServiceDiscoveryResponse " )
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2023-02-27 09:53:42 +00:00
self . publishStatus ( " ServDisc done " )
2022-11-24 06:46:45 +00:00
self . addToTrace ( " Will send ServicePaymentSelectionReq " )
2022-12-09 11:06:10 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDC_ " + self . sessionId ) ) # EDC for Encode, Din, ServicePaymentSelection
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-11-08 21:53:18 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForServicePaymentSelectionResponse )
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForServicePaymentSelectionResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForServicePaymentSelectionResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " ServicePaymentSelectionRes " ) > 0 ) :
# todo: check the request content, and fill response parameters
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
2023-05-15 17:45:11 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the ServicePaymentSelectionResponse " )
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2023-02-27 09:53:42 +00:00
self . publishStatus ( " ServPaySel done " )
2023-03-20 20:32:57 +00:00
self . addToTrace ( " Checkpoint530: Will send ContractAuthenticationReq " )
2022-12-09 11:06:10 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDL_ " + self . sessionId ) ) # EDL for Encode, Din, ContractAuthenticationReq.
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-11-08 21:53:18 +00:00
self . Tcp . transmit ( msg )
2022-12-02 12:02:40 +00:00
self . numberOfContractAuthenticationReq = 1 # This is the first request.
self . enterState ( stateWaitForContractAuthenticationResponse )
2022-11-08 21:53:18 +00:00
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-12-02 12:02:40 +00:00
def stateFunctionWaitForContractAuthenticationResponse ( self ) :
if ( self . cyclesInState < 30 ) : # The first second in the state just do nothing.
return
if ( len ( self . rxData ) > 0 ) :
self . addToTrace ( " In state WaitForContractAuthentication, received " + prettyHexMessage ( self . rxData ) )
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-12-02 12:02:40 +00:00
self . addToTrace ( strConverterResult )
if ( strConverterResult . find ( " ContractAuthenticationRes " ) > 0 ) :
# In normal case, we can have two results here: either the Authentication is needed (the user
# needs to authorize by RFID card or app, or something like this.
# Or, the authorization is finished. This is shown by EVSEProcessing=Finished.
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
2023-05-15 17:45:11 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the ContractAuthenticationResponse " )
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2022-12-19 17:09:39 +00:00
if ( strConverterResult . find ( ' " EVSEProcessing " : " Finished " ' ) > 0 ) :
2023-02-27 09:53:42 +00:00
self . publishStatus ( " Auth finished " )
2023-03-26 00:19:32 +00:00
self . addToTrace ( " Checkpoint538: Auth is Finished. Will send ChargeParameterDiscoveryReq " )
2022-12-09 13:23:32 +00:00
self . sendChargeParameterDiscoveryReq ( )
2022-12-02 13:56:38 +00:00
self . numberOfChargeParameterDiscoveryReq = 1 # first message
2022-12-02 12:02:40 +00:00
self . enterState ( stateWaitForChargeParameterDiscoveryResponse )
else :
# Not (yet) finished.
if ( self . numberOfContractAuthenticationReq > = 120 ) : # approx 120 seconds, maybe the user searches two minutes for his RFID card...
self . addToTrace ( " Authentication lasted too long. " + str ( self . numberOfContractAuthenticationReq ) + " Giving up. " )
self . enterState ( stateSequenceTimeout )
else :
# Try again.
self . numberOfContractAuthenticationReq + = 1 # count the number of tries.
2023-02-27 09:53:42 +00:00
self . publishStatus ( " Waiting f Auth " )
2022-12-02 12:02:40 +00:00
self . addToTrace ( " Not (yet) finished. Will again send ContractAuthenticationReq # " + str ( self . numberOfContractAuthenticationReq ) )
2022-12-09 11:06:10 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDL_ " + self . sessionId ) ) # EDL for Encode, Din, ContractAuthenticationReq.
2022-12-02 12:02:40 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
# We just stay in the same state, until the timeout elapses.
self . enterState ( stateWaitForContractAuthenticationResponse )
if ( self . isTooLong ( ) ) :
# The timeout in case if nothing is received at all.
self . enterState ( stateSequenceTimeout )
2022-11-08 11:01:54 +00:00
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForChargeParameterDiscoveryResponse ( self ) :
2022-12-02 13:56:38 +00:00
if ( self . cyclesInState < 30 ) : # The first second in the state just do nothing.
return
2022-11-08 21:53:18 +00:00
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForChargeParameterDiscoveryResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " ChargeParameterDiscoveryRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2023-06-30 16:29:10 +00:00
strEVSEProcessing = " na "
strEVSEStatusCode_text = " na "
2023-05-15 17:45:11 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
2023-06-30 16:29:10 +00:00
strEVSEProcessing = jsondict [ " EVSEProcessing " ]
strEVSEStatusCode_text = jsondict [ " EVSEStatusCode_text " ]
2023-05-15 17:45:11 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the ChargeParameterDiscoveryResponse " )
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2023-06-30 16:29:10 +00:00
# We can have three cases here:
# (A) The charger needs more time to show the charge parameters. It does not say "EVSEProcessing": "Finished".
# (B) The charger finished to tell the charge parameters, but still needs more time for internal purposes.
# It says "EVSEProcessing": "Finished", but also "EVSEStatusCode_text": "EVSE_NotReady". Observed and discussed
2023-06-30 20:30:44 +00:00
# here: https://openinverter.org/forum/viewtopic.php?p=58239#p58239.
# Update: This seems to be a normal case for Compleo, and NO reason to wait. So we go to the next step (cable check).
2023-06-30 16:29:10 +00:00
# (C) The charger is really finished and able to continue with the next step (cable check).
2023-06-30 20:30:44 +00:00
if ( ( strEVSEProcessing == " Finished " ) ) :
# Case B and C
2023-02-27 09:53:42 +00:00
self . publishStatus ( " ChargeParams discovered " )
2023-05-04 17:08:58 +00:00
self . addToTrace ( " Checkpoint550: ChargeParams are discovered. Will change to state C. " )
2023-06-20 12:12:21 +00:00
#Report charger paramters
2023-06-29 05:52:16 +00:00
maxI = combineValueAndMultiplier ( jsondict [ " EVSEMaximumCurrentLimit.Value " ] , jsondict [ " EVSEMaximumCurrentLimit.Multiplier " ] )
maxV = combineValueAndMultiplier ( jsondict [ " EVSEMaximumVoltageLimit.Value " ] , jsondict [ " EVSEMaximumVoltageLimit.Multiplier " ] )
2023-06-20 20:44:49 +00:00
self . hardwareInterface . setChargerParameters ( maxV , maxI )
2022-12-06 17:35:17 +00:00
# pull the CP line to state C here:
self . hardwareInterface . setStateC ( )
2023-05-04 17:08:58 +00:00
self . addToTrace ( " Checkpoint555: Locking the connector. " )
self . hardwareInterface . triggerConnectorLocking ( )
self . enterState ( stateWaitForConnectorLock )
2022-12-02 13:56:38 +00:00
else :
2023-06-30 16:29:10 +00:00
# Not (yet) finished. Cases A and B.
2023-05-12 20:18:51 +00:00
if ( self . numberOfChargeParameterDiscoveryReq > = 60 ) : # approx 60 seconds, should be sufficient for the charger to find its parameters... The ISO allows up to 55s reaction time and 60s timeout for "ongoing".
2022-12-02 13:56:38 +00:00
self . addToTrace ( " ChargeParameterDiscovery lasted too long. " + str ( self . numberOfChargeParameterDiscoveryReq ) + " Giving up. " )
self . enterState ( stateSequenceTimeout )
else :
# Try again.
self . numberOfChargeParameterDiscoveryReq + = 1 # count the number of tries.
2023-02-27 09:53:42 +00:00
self . publishStatus ( " disc ChargeParams " )
2022-12-02 13:56:38 +00:00
self . addToTrace ( " Not (yet) finished. Will again send ChargeParameterDiscoveryReq # " + str ( self . numberOfChargeParameterDiscoveryReq ) )
2022-12-09 13:23:32 +00:00
self . sendChargeParameterDiscoveryReq ( )
2022-12-02 13:56:38 +00:00
# we stay in the same state
self . enterState ( stateWaitForChargeParameterDiscoveryResponse )
2022-11-08 21:53:18 +00:00
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-11-08 21:53:18 +00:00
2023-05-04 17:08:58 +00:00
def stateFunctionWaitForConnectorLock ( self ) :
if ( self . hardwareInterface . isConnectorLocked ( ) ) :
self . addToTrace ( " Checkpoint560: Connector Lock confirmed. Will send CableCheckReq. " )
self . sendCableCheckReq ( )
self . numberOfCableCheckReq = 1 # This is the first request.
self . enterState ( stateWaitForCableCheckResponse )
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForCableCheckResponse ( self ) :
2022-12-02 13:56:38 +00:00
if ( self . cyclesInState < 30 ) : # The first second in the state just do nothing.
return
2022-11-08 21:53:18 +00:00
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForCableCheckResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " CableCheckRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2022-11-18 11:27:24 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
strEVSEProcessing = jsondict [ " EVSEProcessing " ]
2023-03-03 09:23:37 +00:00
self . addToTrace ( " The CableCheck result is " + strResponseCode + " " + strEVSEProcessing )
2022-11-18 11:27:24 +00:00
except :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " ERROR: Could not decode the CableCheckRes " )
2023-05-15 17:45:11 +00:00
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2022-11-18 11:27:24 +00:00
# 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.
2022-12-04 08:14:47 +00:00
if ( ( strEVSEProcessing == " Finished " ) and ( strResponseCode == " OK " ) ) :
2023-02-27 09:53:42 +00:00
self . publishStatus ( " CbleChck done " )
2022-11-24 06:46:45 +00:00
self . addToTrace ( " The EVSE says that the CableCheck is finished and ok. " )
self . addToTrace ( " Will send PreChargeReq " )
2022-12-09 13:23:32 +00:00
soc = self . hardwareInterface . getSoc ( )
EVTargetVoltage = self . hardwareInterface . getAccuVoltage ( )
msg = addV2GTPHeader ( self . exiEncode ( " EDG_ " + self . sessionId + " _ " + str ( soc ) + " _ " + str ( EVTargetVoltage ) ) ) # EDG for Encode, Din, PreChargeReq
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-11-18 11:27:24 +00:00
self . Tcp . transmit ( msg )
2023-05-22 06:20:37 +00:00
self . connMgr . ApplOk ( 31 ) # PreChargeResponse may need longer. Inform the connection manager to be patient.
2022-11-18 11:27:24 +00:00
self . enterState ( stateWaitForPreChargeResponse )
else :
2023-05-12 20:18:51 +00:00
if ( self . numberOfCableCheckReq > 60 ) : # approx 60s should be sufficient for cable check. The ISO allows up to 55s reaction time and 60s timeout for "ongoing".
2022-12-02 13:56:38 +00:00
self . addToTrace ( " CableCheck lasted too long. " + str ( self . numberOfCableCheckReq ) + " Giving up. " )
self . enterState ( stateSequenceTimeout )
else :
# cable check not yet finished or finished with bad result -> try again
self . numberOfCableCheckReq + = 1
2023-02-27 09:53:42 +00:00
self . publishStatus ( " CbleChck ongoing " , format ( self . hardwareInterface . getInletVoltage ( ) , " .0f " ) + " V " )
2022-12-02 13:56:38 +00:00
self . addToTrace ( " Will again send CableCheckReq " )
2022-12-09 13:23:32 +00:00
self . sendCableCheckReq ( )
2022-12-02 13:56:38 +00:00
# stay in the same state
self . enterState ( stateWaitForCableCheckResponse )
2022-11-18 11:27:24 +00:00
2022-11-08 21:53:18 +00:00
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForPreChargeResponse ( self ) :
2022-12-10 16:08:51 +00:00
self . hardwareInterface . simulatePreCharge ( )
2022-11-20 18:48:52 +00:00
if ( self . DelayCycles > 0 ) :
self . DelayCycles - = 1
return
2022-11-08 21:53:18 +00:00
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForPreChargeResponse, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " PreChargeRes " ) > 0 ) :
2023-04-05 19:01:43 +00:00
u = 0 # a default voltage of 0V in case we cannot convert the actual value
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2023-05-04 06:53:01 +00:00
strEVSEStatusCode = " 0 " # default in case the decoding does not work
2023-04-05 19:01:43 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
strEVSEPresentVoltageValue = jsondict [ " EVSEPresentVoltage.Value " ]
strEVSEPresentVoltageMultiplier = jsondict [ " EVSEPresentVoltage.Multiplier " ]
2023-04-05 19:01:43 +00:00
u = combineValueAndMultiplier ( strEVSEPresentVoltageValue , strEVSEPresentVoltageMultiplier )
self . callbackShowStatus ( format ( u , " .1f " ) , " EVSEPresentVoltage " )
2023-06-29 05:52:16 +00:00
strEVSEStatusCode = jsondict [ " DC_EVSEStatus.EVSEStatusCode " ]
2023-04-05 19:01:43 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the PreChargeResponse " )
self . addToTrace ( " PreChargeResponse received. " )
2023-05-15 17:45:11 +00:00
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2023-05-10 17:17:26 +00:00
if ( self . isErrorEvseStatusCode ( strEVSEStatusCode ) ) :
2023-05-04 06:53:01 +00:00
self . enterState ( stateUnrecoverableError )
return
2023-04-15 18:35:51 +00:00
if ( getConfigValueBool ( " use_evsepresentvoltage_for_precharge_end " ) ) :
2023-04-05 19:01:43 +00:00
# We want to use the EVSEPresentVoltage, which was reported by the charger, as end-criteria for the precharging.
s = " EVSEPresentVoltage " + str ( u ) + " V, "
else :
# We want to use the physically measured inlet voltage as end-criteria for the precharging.
u = self . hardwareInterface . getInletVoltage ( )
s = " U_Inlet " + str ( u ) + " V, "
2022-12-10 16:08:51 +00:00
s = s + " U_Accu " + str ( self . hardwareInterface . getAccuVoltage ( ) ) + " V "
self . addToTrace ( s )
2023-04-15 18:35:51 +00:00
if ( abs ( u - self . hardwareInterface . getAccuVoltage ( ) ) < float ( getConfigValue ( " u_delta_max_for_end_of_precharge " ) ) ) :
2023-05-03 19:25:01 +00:00
self . addToTrace ( " Difference between accu voltage and inlet voltage is small. " )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " PreCharge done " )
if ( self . isLightBulbDemo ) :
# For light-bulb-demo, nothing to do here.
self . addToTrace ( " This is a light bulb demo. Do not turn-on the relay at end of precharge. " )
else :
# In real-world-case, turn the power relay on.
2023-05-03 19:25:01 +00:00
self . addToTrace ( " Checkpoint590: Turning the contactors on. " )
2022-12-19 17:09:39 +00:00
self . hardwareInterface . setPowerRelayOn ( )
2023-05-03 19:25:01 +00:00
self . DelayCycles = 10 # 10*33ms = 330ms waiting for contactors
self . enterState ( stateWaitForContactorsClosed )
2022-12-08 23:22:18 +00:00
else :
2023-04-05 19:01:43 +00:00
self . publishStatus ( " PreChrge ongoing " , format ( u , " .0f " ) + " V " )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " Difference too big. Continuing PreCharge. " )
2022-12-09 13:23:32 +00:00
soc = self . hardwareInterface . getSoc ( )
EVTargetVoltage = self . hardwareInterface . getAccuVoltage ( )
msg = addV2GTPHeader ( self . exiEncode ( " EDG_ " + self . sessionId + " _ " + str ( soc ) + " _ " + str ( EVTargetVoltage ) ) ) # EDG for Encode, Din, PreChargeReq
2022-12-08 23:22:18 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . DelayCycles = 15 # wait with the next evaluation approx half a second
2022-11-08 21:53:18 +00:00
if ( self . isTooLong ( ) ) :
2022-11-29 07:40:34 +00:00
self . enterState ( stateSequenceTimeout )
2023-05-03 19:25:01 +00:00
def stateFunctionWaitForContactorsClosed ( self ) :
if ( self . DelayCycles > 0 ) :
self . DelayCycles - = 1
return
if ( self . isLightBulbDemo ) :
readyForNextState = 1 # if it's just a bulb demo, we do not wait for contactor, because it is not switched in this moment.
else :
readyForNextState = self . hardwareInterface . getPowerRelayConfirmation ( ) # check if the contactor is closed
if ( readyForNextState ) :
self . addToTrace ( " Contactors are confirmed to be closed. " )
self . publishStatus ( " Contactors ON " )
if ( readyForNextState ) :
self . addToTrace ( " Sending PowerDeliveryReq. " )
soc = self . hardwareInterface . getSoc ( )
msg = addV2GTPHeader ( self . exiEncode ( " EDH_ " + self . sessionId + " _ " + str ( soc ) + " _ " + " 1 " ) ) # EDH for Encode, Din, PowerDeliveryReq, ON
self . wasPowerDeliveryRequestedOn = True
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForPowerDeliveryResponse )
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
2022-12-08 23:22:18 +00:00
def stateFunctionWaitForPowerDeliveryResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
self . addToTrace ( " In state WaitForPowerDeliveryRes, received " + prettyHexMessage ( self . rxData ) )
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-12-08 23:22:18 +00:00
self . addToTrace ( strConverterResult )
if ( strConverterResult . find ( " PowerDeliveryRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
2023-05-15 17:45:11 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the PowerDeliveryResponse " )
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2022-12-08 23:22:18 +00:00
if ( self . wasPowerDeliveryRequestedOn ) :
2023-02-27 09:53:42 +00:00
self . publishStatus ( " PwrDelvy ON success " )
2023-03-26 00:19:32 +00:00
self . addToTrace ( " Checkpoint700: Starting the charging loop with CurrentDemandReq " )
2022-12-09 13:23:32 +00:00
self . sendCurrentDemandReq ( )
2022-12-08 23:22:18 +00:00
self . enterState ( stateWaitForCurrentDemandResponse )
else :
# We requested "OFF". So we turn-off the Relay and continue with the Welding detection.
2023-02-27 09:53:42 +00:00
self . publishStatus ( " PwrDelvry OFF success " )
2023-05-04 17:08:58 +00:00
self . addToTrace ( " Checkpoint806: PowerDelivery Off confirmed. " )
self . addToTrace ( " Checkpoint810: Changing CP line to State B. " )
# set the CP line to B
self . hardwareInterface . setStateB ( )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " Turning off the relay and starting the WeldingDetection " )
self . hardwareInterface . setPowerRelayOff ( )
2022-12-19 17:09:39 +00:00
self . hardwareInterface . setRelay2Off ( )
self . isBulbOn = False
2022-12-09 13:23:32 +00:00
self . sendWeldingDetectionReq ( )
2022-12-08 23:22:18 +00:00
self . enterState ( stateWaitForWeldingDetectionResponse )
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
def stateFunctionWaitForCurrentDemandResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
self . addToTrace ( " In state WaitForCurrentDemandRes, received " + prettyHexMessage ( self . rxData ) )
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-12-08 23:22:18 +00:00
self . addToTrace ( strConverterResult )
if ( strConverterResult . find ( " CurrentDemandRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2023-04-05 19:01:43 +00:00
u = 0 # a default voltage of 0V in case we cannot convert the actual value
2023-05-04 06:53:01 +00:00
strEVSEStatusCode = " 0 " # default in case the decoding does not work
2023-04-05 19:01:43 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strResponseCode = jsondict [ " ResponseCode " ]
strEVSEPresentVoltageValue = jsondict [ " EVSEPresentVoltage.Value " ]
strEVSEPresentVoltageMultiplier = jsondict [ " EVSEPresentVoltage.Multiplier " ]
strEVSEPresentCurrentValue = jsondict [ " EVSEPresentCurrent.Value " ]
strEVSEPresentCurrentMultiplier = jsondict [ " EVSEPresentCurrent.Multiplier " ]
2023-04-05 19:01:43 +00:00
u = combineValueAndMultiplier ( strEVSEPresentVoltageValue , strEVSEPresentVoltageMultiplier )
2023-06-20 20:44:49 +00:00
i = combineValueAndMultiplier ( strEVSEPresentCurrentValue , strEVSEPresentCurrentMultiplier )
2023-04-05 19:01:43 +00:00
self . callbackShowStatus ( format ( u , " .1f " ) , " EVSEPresentVoltage " )
2023-06-29 05:52:16 +00:00
strEVSEStatusCode = jsondict [ " DC_EVSEStatus.EVSEStatusCode " ]
2023-06-20 20:44:49 +00:00
self . hardwareInterface . setChargerVoltageAndCurrent ( u , i )
2023-04-05 19:01:43 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the PreChargeResponse " )
2023-05-15 17:45:11 +00:00
if ( strResponseCode != " OK " ) :
self . addToTrace ( " Wrong response code. Aborting. " )
self . enterState ( stateUnrecoverableError )
return
2023-05-10 17:17:26 +00:00
if ( self . isErrorEvseStatusCode ( strEVSEStatusCode ) ) :
2023-05-04 06:53:01 +00:00
self . enterState ( stateUnrecoverableError )
return
2023-04-15 18:35:51 +00:00
if ( getConfigValueBool ( " use_physical_inlet_voltage_during_chargeloop " ) ) :
2023-04-05 19:01:43 +00:00
# Instead of using the voltage which is reported by the charger, use the physically measured.
u = self . hardwareInterface . getInletVoltage ( )
2022-12-08 23:22:18 +00:00
# as long as the accu is not full and no stop-demand from the user, we continue charging
2022-12-19 17:09:39 +00:00
if ( self . hardwareInterface . getIsAccuFull ( ) or self . isUserStopRequest ) :
if ( self . hardwareInterface . getIsAccuFull ( ) ) :
self . publishStatus ( " Accu full " )
self . addToTrace ( " Accu is full. Sending PowerDeliveryReq Stop. " )
else :
2023-02-27 09:53:42 +00:00
self . publishStatus ( " User req stop " )
2022-12-19 17:09:39 +00:00
self . addToTrace ( " User requested stop. Sending PowerDeliveryReq Stop. " )
2022-12-09 13:23:32 +00:00
soc = self . hardwareInterface . getSoc ( )
msg = addV2GTPHeader ( self . exiEncode ( " EDH_ " + self . sessionId + " _ " + str ( soc ) + " _ " + " 0 " ) ) # EDH for Encode, Din, PowerDeliveryReq, OFF
2022-12-08 23:22:18 +00:00
self . wasPowerDeliveryRequestedOn = False
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForPowerDeliveryResponse )
else :
# continue charging loop
2023-04-05 19:01:43 +00:00
self . publishStatus ( " Charging " , format ( u , " .0f " ) + " V " , format ( self . hardwareInterface . getSoc ( ) , " .1f " ) + " % " )
2022-12-09 13:23:32 +00:00
self . sendCurrentDemandReq ( )
2022-12-08 23:22:18 +00:00
self . enterState ( stateWaitForCurrentDemandResponse )
2022-12-19 17:09:39 +00:00
if ( self . isLightBulbDemo ) :
2023-02-27 09:53:42 +00:00
if ( self . cyclesLightBulbDelay < = 33 * 2 ) :
2022-12-19 17:09:39 +00:00
self . cyclesLightBulbDelay + = 1
else :
if ( not self . isBulbOn ) :
2023-02-27 09:53:42 +00:00
self . addToTrace ( " This is a light bulb demo. Turning-on the bulb when 2s in the main charging loop. " )
2022-12-19 17:09:39 +00:00
self . hardwareInterface . setPowerRelayOn ( )
self . hardwareInterface . setRelay2On ( )
self . isBulbOn = True
2022-12-08 23:22:18 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
def stateFunctionWaitForWeldingDetectionResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
self . addToTrace ( " In state WaitForWeldingDetectionResponse, received " + prettyHexMessage ( self . rxData ) )
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-12-08 23:22:18 +00:00
self . addToTrace ( strConverterResult )
if ( strConverterResult . find ( " WeldingDetectionRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2022-12-09 13:23:32 +00:00
# todo: add real welding detection here, run in welding detection loop until finished.
2023-02-27 09:53:42 +00:00
self . publishStatus ( " WldingDet done " )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " Sending SessionStopReq " )
2022-12-09 11:06:10 +00:00
msg = addV2GTPHeader ( self . exiEncode ( " EDK_ " + self . sessionId ) ) # EDI for Encode, Din, SessionStopReq
2022-12-08 23:22:18 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForSessionStopResponse )
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
def stateFunctionWaitForSessionStopResponse ( self ) :
if ( len ( self . rxData ) > 0 ) :
self . addToTrace ( " In state WaitForSessionStopRes, received " + prettyHexMessage ( self . rxData ) )
exidata = removeV2GTPHeader ( self . rxData )
self . rxData = [ ]
2022-12-09 11:06:10 +00:00
strConverterResult = self . exiDecode ( exidata , " DD " ) # Decode DIN
2022-12-08 23:22:18 +00:00
self . addToTrace ( strConverterResult )
if ( strConverterResult . find ( " SessionStopRes " ) > 0 ) :
2023-05-15 17:45:11 +00:00
strResponseCode = " na "
2022-12-08 23:22:18 +00:00
# req -508
2023-05-15 17:45:11 +00:00
# Unlocking of the connector in the next state.
2023-02-27 09:53:42 +00:00
self . publishStatus ( " Stopped normally " )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " Charging is finished " )
self . enterState ( stateChargingFinished )
if ( self . isTooLong ( ) ) :
self . enterState ( stateSequenceTimeout )
def stateFunctionChargingFinished ( self ) :
2023-05-04 17:08:58 +00:00
# charging is finished.
# Finally unlock the connector
self . addToTrace ( " Charging successfully finished. Unlocking the connector " )
self . hardwareInterface . triggerConnectorUnlocking ( )
2023-05-16 07:29:42 +00:00
testsuite_reportstatus ( " TSRS_ChargingFinished " )
2023-05-04 17:08:58 +00:00
self . enterState ( stateEnd )
2022-11-29 07:40:34 +00:00
def stateFunctionSequenceTimeout ( self ) :
2023-05-04 17:08:58 +00:00
# Here we end, if we run into a timeout in the state machine.
2022-12-19 17:09:39 +00:00
self . publishStatus ( " ERROR Timeout " )
2023-05-04 17:08:58 +00:00
# Initiate the safe-shutdown-sequence.
self . addToTrace ( " Safe-shutdown-sequence: setting state B " )
self . hardwareInterface . setStateB ( ) # setting CP line to B disables in the charger the current flow.
self . DelayCycles = 66 # 66*30ms=2s for charger shutdown
self . enterState ( stateSafeShutDownWaitForChargerShutdown )
2022-11-15 18:36:20 +00:00
2023-05-03 19:57:36 +00:00
def stateFunctionUnrecoverableError ( self ) :
# Here we end, if the EVSE reported an error code, which terminates the charging session.
self . publishStatus ( " ERROR reported " )
2023-05-04 17:08:58 +00:00
# Initiate the safe-shutdown-sequence.
self . addToTrace ( " Safe-shutdown-sequence: setting state B " )
self . hardwareInterface . setStateB ( ) # setting CP line to B disables in the charger the current flow.
self . DelayCycles = 66 # 66*30ms=2s for charger shutdown
self . enterState ( stateSafeShutDownWaitForChargerShutdown )
def stateFunctionSafeShutDownWaitForChargerShutdown ( self ) :
# wait state, to give the charger the time to stop the current.
2023-05-07 20:31:44 +00:00
self . connMgr . ApplOk ( ) # Trigger the communication manager, so that it does not disturb our safe-shutdown.
2023-05-04 17:08:58 +00:00
if ( self . DelayCycles > 0 ) :
self . DelayCycles - = 1
2023-05-07 20:31:44 +00:00
return
2023-05-04 17:08:58 +00:00
# Now the current flow is stopped by the charger. We can safely open the contactors:
self . addToTrace ( " Safe-shutdown-sequence: opening contactors " )
self . hardwareInterface . setPowerRelayOff ( )
self . hardwareInterface . setRelay2Off ( )
self . DelayCycles = 33 # 33*30ms=1s for opening the contactors
self . enterState ( stateSafeShutDownWaitForContactorsOpen )
def stateFunctionSafeShutDownWaitForContactorsOpen ( self ) :
# wait state, to give the contactors the time to open.
2023-05-07 20:31:44 +00:00
self . connMgr . ApplOk ( ) # Trigger the communication manager, so that it does not disturb our safe-shutdown.
2023-05-04 17:08:58 +00:00
if ( self . DelayCycles > 0 ) :
self . DelayCycles - = 1
return
# Finally, when we have no current and no voltage, unlock the connector
self . addToTrace ( " Safe-shutdown-sequence: unlocking the connector " )
self . hardwareInterface . triggerConnectorUnlocking ( )
2023-05-16 07:29:42 +00:00
testsuite_reportstatus ( " TSRS_SafeShutdownFinished " )
2023-05-04 17:08:58 +00:00
# This is the end of the safe-shutdown-sequence.
self . enterState ( stateEnd )
def stateFunctionEnd ( self ) :
# Just stay here, until we get re-initialized after a new SLAC/SDP.
pass
2023-05-03 19:57:36 +00:00
2022-11-07 08:21:25 +00:00
stateFunctions = {
2022-11-29 07:40:34 +00:00
stateNotYetInitialized : stateFunctionNotYetInitialized ,
stateConnecting : stateFunctionConnecting ,
stateConnected : stateFunctionConnected ,
2022-11-07 08:21:25 +00:00
stateWaitForSupportedApplicationProtocolResponse : stateFunctionWaitForSupportedApplicationProtocolResponse ,
2022-11-08 11:01:54 +00:00
stateWaitForSessionSetupResponse : stateFunctionWaitForSessionSetupResponse ,
2022-11-08 21:53:18 +00:00
stateWaitForServiceDiscoveryResponse : stateFunctionWaitForServiceDiscoveryResponse ,
stateWaitForServicePaymentSelectionResponse : stateFunctionWaitForServicePaymentSelectionResponse ,
2022-12-02 12:02:40 +00:00
stateWaitForContractAuthenticationResponse : stateFunctionWaitForContractAuthenticationResponse ,
2022-11-08 21:53:18 +00:00
stateWaitForChargeParameterDiscoveryResponse : stateFunctionWaitForChargeParameterDiscoveryResponse ,
2023-05-04 17:08:58 +00:00
stateWaitForConnectorLock : stateFunctionWaitForConnectorLock ,
2022-11-08 21:53:18 +00:00
stateWaitForCableCheckResponse : stateFunctionWaitForCableCheckResponse ,
stateWaitForPreChargeResponse : stateFunctionWaitForPreChargeResponse ,
2023-05-03 19:25:01 +00:00
stateWaitForContactorsClosed : stateFunctionWaitForContactorsClosed ,
2022-12-08 23:22:18 +00:00
stateWaitForPowerDeliveryResponse : stateFunctionWaitForPowerDeliveryResponse ,
stateWaitForCurrentDemandResponse : stateFunctionWaitForCurrentDemandResponse ,
stateWaitForWeldingDetectionResponse : stateFunctionWaitForWeldingDetectionResponse ,
stateWaitForSessionStopResponse : stateFunctionWaitForSessionStopResponse ,
stateChargingFinished : stateFunctionChargingFinished ,
2023-05-03 19:57:36 +00:00
stateUnrecoverableError : stateFunctionUnrecoverableError ,
2023-05-04 17:08:58 +00:00
stateSequenceTimeout : stateFunctionSequenceTimeout ,
stateSafeShutDownWaitForChargerShutdown : stateFunctionSafeShutDownWaitForChargerShutdown ,
stateSafeShutDownWaitForContactorsOpen : stateFunctionSafeShutDownWaitForContactorsOpen ,
stateEnd : stateFunctionEnd
2022-11-07 08:21:25 +00:00
}
2022-12-19 17:09:39 +00:00
def stopCharging ( self ) :
# API function to stop the charging.
self . isUserStopRequest = True
2022-12-08 23:22:18 +00:00
2022-11-07 08:21:25 +00:00
def reInit ( self ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " re-initializing fsmPev " )
2022-11-29 07:40:34 +00:00
self . Tcp . disconnect ( )
2022-12-06 17:35:17 +00:00
self . hardwareInterface . setStateB ( )
2022-12-08 23:22:18 +00:00
self . hardwareInterface . setPowerRelayOff ( )
2022-12-19 17:09:39 +00:00
self . hardwareInterface . setRelay2Off ( )
self . isBulbOn = False
self . cyclesLightBulbDelay = 0
2022-11-29 07:40:34 +00:00
self . state = stateConnecting
2022-11-07 08:21:25 +00:00
self . cyclesInState = 0
self . rxData = [ ]
2023-04-18 19:50:46 +00:00
def __init__ ( self , addressManager , connMgr , callbackAddToTrace , hardwareInterface , callbackShowStatus ) :
2022-11-22 20:34:27 +00:00
self . callbackAddToTrace = callbackAddToTrace
2022-12-19 17:09:39 +00:00
self . callbackShowStatus = callbackShowStatus
2022-11-22 20:34:27 +00:00
self . addToTrace ( " initializing fsmPev " )
2022-12-09 11:06:10 +00:00
self . exiLogFile = open ( ' PevExiLog.txt ' , ' a ' )
self . exiLogFile . write ( " init \n " )
2022-11-22 20:34:27 +00:00
self . Tcp = pyPlcTcpSocket . pyPlcTcpClientSocket ( self . callbackAddToTrace )
2022-11-15 23:22:49 +00:00
self . addressManager = addressManager
2023-04-18 19:50:46 +00:00
self . connMgr = connMgr
2022-12-06 17:35:17 +00:00
self . hardwareInterface = hardwareInterface
2022-11-15 18:36:20 +00:00
self . state = stateNotYetInitialized
2022-11-16 18:33:37 +00:00
self . sessionId = " DEAD55AADEAD55AA "
2023-03-23 07:50:53 +00:00
self . evccid = addressManager . getLocalMacAsTwelfCharString ( )
2022-11-15 18:36:20 +00:00
self . cyclesInState = 0
2022-11-20 18:48:52 +00:00
self . DelayCycles = 0
2022-11-15 18:36:20 +00:00
self . rxData = [ ]
2023-04-15 18:35:51 +00:00
self . isLightBulbDemo = getConfigValueBool ( " light_bulb_demo " )
2022-12-19 17:09:39 +00:00
self . isBulbOn = False
self . cyclesLightBulbDelay = 0
self . isUserStopRequest = False
2022-11-15 18:36:20 +00:00
# we do NOT call the reInit, because we want to wait with the connection until external trigger comes
2022-11-07 08:21:25 +00:00
2022-12-09 11:06:10 +00:00
def __del__ ( self ) :
self . exiLogFile . write ( " closing \n " )
self . exiLogFile . close ( )
2022-11-07 08:21:25 +00:00
def mainfunction ( self ) :
#self.Tcp.mainfunction() # call the lower-level worker
if ( self . Tcp . isRxDataAvailable ( ) ) :
self . rxData = self . Tcp . getRxData ( )
2022-11-22 20:34:27 +00:00
#self.addToTrace("received " + prettyHexMessage(self.rxData))
2022-11-07 08:21:25 +00:00
# run the state machine:
self . cyclesInState + = 1 # for timeout handling, count how long we are in a state
self . stateFunctions [ self . state ] ( self )
if __name__ == " __main__ " :
print ( " Testing the pev state machine " )
pev = fsmPev ( )
print ( " Press Ctrl-Break for aborting " )
while ( True ) :
time . sleep ( 0.1 )
pev . mainfunction ( )