2022-11-04 22:51:07 +00:00
# State machine for the charger
#
#
#------------------------------------------------------------
import pyPlcTcpSocket
import time # for time.sleep()
2023-04-19 07:05:21 +00:00
from helpers import prettyHexMessage , combineValueAndMultiplier
2023-05-25 21:08:52 +00:00
from mytestsuite import *
2023-04-19 07:05:21 +00:00
from random import random
2022-11-07 12:38:06 +00:00
from exiConnector import * # for EXI data handling/converting
2022-11-04 22:51:07 +00:00
stateWaitForSupportedApplicationProtocolRequest = 0
stateWaitForSessionSetupRequest = 1
stateWaitForServiceDiscoveryRequest = 2
2022-11-08 21:53:18 +00:00
stateWaitForServicePaymentSelectionRequest = 3
2022-11-09 18:11:18 +00:00
stateWaitForFlexibleRequest = 4
2022-11-08 21:53:18 +00:00
stateWaitForChargeParameterDiscoveryRequest = 5
2022-11-04 22:51:07 +00:00
stateWaitForCableCheckRequest = 6
stateWaitForPreChargeRequest = 7
stateWaitForPowerDeliveryRequest = 8
class fsmEvse ( ) :
2022-11-22 20:34:27 +00:00
def addToTrace ( self , s ) :
2022-11-22 20:48:05 +00:00
self . callbackAddToTrace ( " [EVSE] " + s )
2022-12-19 17:09:39 +00:00
def publishStatus ( self , s ) :
self . callbackShowStatus ( s , " evseState " )
2023-05-25 21:08:52 +00:00
2023-06-26 17:38:45 +00:00
def publishSoCs ( self , current_soc : int , full_soc : int = - 1 , energy_capacity : int = - 1 , energy_request : int = - 1 , evccid : str = " " , origin : str = " " ) :
2023-05-25 21:08:52 +00:00
if self . callbackSoCStatus is not None :
2023-06-26 17:38:45 +00:00
self . callbackSoCStatus ( current_soc , full_soc , energy_capacity , energy_request , self . evccid , origin )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def enterState ( self , n ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " from " + str ( self . state ) + " entering " + str ( n ) )
2023-03-18 18:14:23 +00:00
if ( self . state != 0 ) and ( n == 0 ) :
self . publishStatus ( " Waiting f AppHandShake " )
2022-11-04 22:51:07 +00:00
self . state = n
self . cyclesInState = 0
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def isTooLong ( self ) :
# The timeout handling function.
2023-05-03 19:26:49 +00:00
return ( self . cyclesInState > 100 ) # 100*33ms=3.3s
2023-05-25 21:08:52 +00:00
2024-03-06 07:25:12 +00:00
def showDecodedTransmitMessage ( self , msg ) :
# decodes the transmit message to show it in the trace.
# This is inefficient, because it calls the exi decoder via the slow
# command line interface, while DEcoding for the transmit data is
# technically not necessary. Only for logging. In case this
# introduces timing problems, just remove the three lines below.
exidataTx = removeV2GTPHeader ( msg )
2024-05-22 08:45:41 +00:00
strConverterResultTx = exiDecode ( exidataTx , " D " + self . schemaSelection )
2024-03-06 07:25:12 +00:00
self . addToTrace ( strConverterResultTx )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def stateFunctionWaitForSupportedApplicationProtocolRequest ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForSupportedApplicationProtocolRequest, received " + prettyHexMessage ( self . rxData ) )
2022-11-07 12:38:06 +00:00
exidata = removeV2GTPHeader ( self . rxData )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
2022-11-08 12:15:36 +00:00
strConverterResult = exiDecode ( exidata , " DH " ) # Decode Handshake-request
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2023-06-27 19:22:21 +00:00
if ( strConverterResult . find ( " supportedAppProtocolReq " ) > 0 ) :
nDinSchemaID = 255 # invalid default value
2024-05-16 07:36:28 +00:00
nIso1SchemaID = 255
2023-06-27 19:22:21 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
nAppProtocol_ArrayLen = int ( jsondict [ " AppProtocol_arrayLen " ] )
2023-06-27 19:22:21 +00:00
self . addToTrace ( " The car supports " + str ( nAppProtocol_ArrayLen ) + " schemas. " )
for i in range ( nAppProtocol_ArrayLen ) :
2023-06-29 05:52:16 +00:00
strNameSpace = jsondict [ " NameSpace_ " + str ( i ) ]
nSchemaId = int ( jsondict [ " SchemaID_ " + str ( i ) ] )
2023-06-27 19:22:21 +00:00
self . addToTrace ( " The NameSpace " + strNameSpace + " has SchemaID " + str ( nSchemaId ) )
if ( strNameSpace . find ( " :din:70121: " ) > 0 ) :
nDinSchemaID = nSchemaId
2024-05-16 07:36:28 +00:00
if ( strNameSpace . find ( " :iso:15118:2:2013 " ) > 0 ) :
nIso1SchemaID = nSchemaId
2023-06-27 19:22:21 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the supportedAppProtocolReq " )
2024-05-16 07:36:28 +00:00
# Strategy for schema selection: pyPLC preferes DIN. If the car does not announce DIN,
# then pyPLC looks for ISO1. If this is also not announced by the car, pyPLC will not
# send a handshake response.
# This means: pyPLC does NOT care for the priority sent by the car. It uses the own
# priority "DIN over ISO1". Reason: DIN is proven-in-use, the ISO implementation still
# work-in-progress.
2023-06-27 19:22:21 +00:00
if ( nDinSchemaID < 255 ) :
self . addToTrace ( " Detected DIN " )
# TESTSUITE: When the EVSE received the Handshake, it selects a new test case.
testsuite_choose_testcase ( )
# Eh for encode handshake, SupportedApplicationProtocolResponse, with SchemaID as parameter
msg = addV2GTPHeader ( exiEncode ( " Eh__ " + str ( nDinSchemaID ) ) )
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . publishStatus ( " Schema negotiated " )
2024-05-16 07:36:28 +00:00
self . schemaSelection = " D " # D for DIN
2023-06-27 19:22:21 +00:00
self . enterState ( stateWaitForSessionSetupRequest )
else :
2024-05-16 07:36:28 +00:00
if ( nIso1SchemaID < 255 ) :
self . addToTrace ( " Detected ISO1 (aka ISO 2013) " )
# TESTSUITE: When the EVSE received the Handshake, it selects a new test case.
testsuite_choose_testcase ( )
# Eh for encode handshake, SupportedApplicationProtocolResponse, with SchemaID as parameter
msg = addV2GTPHeader ( exiEncode ( " Eh__ " + str ( nIso1SchemaID ) ) )
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . Tcp . transmit ( msg )
self . publishStatus ( " Schema negotiated " )
self . schemaSelection = " 1 " # 1 for ISO1
self . enterState ( stateWaitForSessionSetupRequest )
else :
self . addToTrace ( " Error: The connected car does not support DIN or ISO1. At the moment, the pyPLC only supports DIN and ISO1. " )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def stateFunctionWaitForSessionSetupRequest ( self ) :
if ( len ( self . rxData ) > 0 ) :
2023-03-29 06:44:09 +00:00
self . simulatedPresentVoltage = 0
2023-03-18 18:14:23 +00:00
self . addToTrace ( " In state WaitForSessionSetupRequest, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 11:01:54 +00:00
exidata = removeV2GTPHeader ( self . rxData )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
2024-05-16 07:36:28 +00:00
strConverterResult = exiDecode ( exidata , " D " + self . schemaSelection ) # decodes DIN or ISO1
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " SessionSetupReq " ) > 0 ) :
2024-05-16 07:36:28 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " a " ) ) # EDa for Encode, Din, SessionSetupResponse
2023-05-15 20:34:49 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_SequenceError_for_SessionSetup ) ) :
# send a SessionSetupResponse with Responsecode SequenceError
msg = addV2GTPHeader ( " 809a0232417b661514a4cb91e0A02d0691559529548c0841e0fc1af4507460c0 " )
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . Tcp . transmit ( msg )
self . publishStatus ( " Session established " )
2022-11-08 21:53:18 +00:00
self . enterState ( stateWaitForServiceDiscoveryRequest )
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
self . evccid = jsondict . get ( " EVCCID " , " " )
2023-06-26 17:38:45 +00:00
2022-11-04 22:51:07 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def stateFunctionWaitForServiceDiscoveryRequest ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForServiceDiscoveryRequest, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
2024-05-16 07:36:28 +00:00
strConverterResult = exiDecode ( exidata , " D " + self . schemaSelection ) # decodes DIN or ISO1
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " ServiceDiscoveryReq " ) > 0 ) :
# todo: check the request content, and fill response parameters
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " b " ) ) # EDb for Encode, Din, ServiceDiscoveryResponse
2023-05-15 20:34:49 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_SequenceError_for_ServiceDiscoveryRes ) ) :
# send a ServiceDiscoveryRes with Responsecode SequenceError
msg = addV2GTPHeader ( " 809a021a3b7c417774813311a0A120024100c4 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . Tcp . transmit ( msg )
self . publishStatus ( " Services discovered " )
2022-11-08 21:53:18 +00:00
self . enterState ( stateWaitForServicePaymentSelectionRequest )
2022-11-04 22:51:07 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-08 21:53:18 +00:00
def stateFunctionWaitForServicePaymentSelectionRequest ( self ) :
2022-11-04 22:51:07 +00:00
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForServicePaymentSelectionRequest, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
2024-05-16 07:36:28 +00:00
strConverterResult = exiDecode ( exidata , " D " + self . schemaSelection ) # decodes DIN or ISO1
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2024-05-22 08:45:41 +00:00
if ( self . schemaSelection == " D " ) :
strMessageName = " ServicePaymentSelectionReq " # This is the original name in DIN
else :
strMessageName = " PaymentServiceSelectionReq " # In ISO1, they use a slightly different name for the same thing.
if ( strConverterResult . find ( strMessageName ) > 0 ) :
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " c " ) ) # EDc for Encode, Din, ServicePaymentSelectionResponse
2023-05-15 20:34:49 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_SequenceError_for_ServicePaymentSelectionRes ) ) :
# send a ServicePaymentSelectionRes with Responsecode SequenceError
msg = addV2GTPHeader ( " 809a021a3b7c417774813311c0A0 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . Tcp . transmit ( msg )
self . publishStatus ( " ServicePayment selected " )
2022-11-09 18:11:18 +00:00
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified. The Ioniq sends PowerDeliveryReq as next.
2022-11-04 22:51:07 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-09 18:11:18 +00:00
def stateFunctionWaitForFlexibleRequest ( self ) :
2022-11-04 22:51:07 +00:00
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForFlexibleRequest, received " + prettyHexMessage ( self . rxData ) )
2022-11-08 21:53:18 +00:00
exidata = removeV2GTPHeader ( self . rxData )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
2024-05-16 07:36:28 +00:00
strConverterResult = exiDecode ( exidata , " D " + self . schemaSelection ) # decodes DIN or ISO1
2022-11-22 20:34:27 +00:00
self . addToTrace ( strConverterResult )
2022-11-09 18:11:18 +00:00
if ( strConverterResult . find ( " PowerDeliveryReq " ) > 0 ) :
# todo: check the request content, and fill response parameters
2023-05-25 21:08:52 +00:00
self . addToTrace ( " Received PowerDeliveryReq. Extracting SoC parameters " )
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
current_soc = int ( jsondict . get ( " EVRESSSOC " , - 1 ) )
2023-06-26 17:38:45 +00:00
self . publishSoCs ( current_soc , origin = " PowerDeliveryReq " )
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " h " ) ) # EDh for Encode, Din, PowerDeliveryResponse
2023-05-20 09:07:02 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_Failed_for_PowerDeliveryRes ) ) :
# send a PowerDeliveryResponse with Responsecode Failed
msg = addV2GTPHeader ( " 809a0125e6cecc51408420400000 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " PowerDelivery " )
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
2022-11-09 18:11:18 +00:00
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " ChargeParameterDiscoveryReq " ) > 0 ) :
2023-05-25 21:08:52 +00:00
self . addToTrace ( " Received ChargeParameterDiscoveryReq. Extracting SoC parameters via DC " )
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
current_soc = int ( jsondict . get ( " DC_EVStatus.EVRESSSOC " , - 1 ) )
full_soc = int ( jsondict . get ( " FullSOC " , - 1 ) )
energy_capacity = int ( jsondict . get ( " EVEnergyCapacity.Value " , - 1 ) )
energy_request = int ( jsondict . get ( " EVEnergyRequest.Value " , - 1 ) )
2023-06-26 17:38:45 +00:00
self . publishSoCs ( current_soc , full_soc , energy_capacity , energy_request , origin = " ChargeParameterDiscoveryReq " )
2023-05-25 21:08:52 +00:00
2022-11-08 21:53:18 +00:00
# todo: check the request content, and fill response parameters
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " e " ) ) # EDe for Encode, Din, ChargeParameterDiscoveryResponse
2023-05-20 09:07:02 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_ServiceSelectionInvalid_for_ChargeParameterDiscovery ) ) :
# send a ChargeParameterDiscoveryResponse with Responsecode ServiceSelectionInvalid
msg = addV2GTPHeader ( " 809a0125e6cecd50810001ec00201004051828758405500080000101844138101c2432c04081436c900c0c000041435ecc044606000200 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " ChargeParamDiscovery " )
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
2024-01-06 20:03:56 +00:00
self . nCableCheckLoops = 0 # start with a fresh full cable check
2023-05-25 21:08:52 +00:00
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-11-08 21:53:18 +00:00
if ( strConverterResult . find ( " CableCheckReq " ) > 0 ) :
# todo: check the request content, and fill response parameters
2023-05-22 06:20:37 +00:00
# todo: make a real cable check, and while it is ongoing, send "Ongoing".
2023-05-25 21:08:52 +00:00
self . addToTrace ( " Received CableCheckReq. Extracting SoC parameters via DC " )
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
current_soc = int ( jsondict . get ( " DC_EVStatus.EVRESSSOC " , - 1 ) )
2023-06-26 17:38:45 +00:00
self . publishSoCs ( current_soc , - 1 , - 1 , origin = " CableCheckReq " )
2024-04-22 16:42:50 +00:00
if ( self . nCableCheckLoops < 5 ) :
2024-01-06 20:03:56 +00:00
self . nCableCheckLoops + = 1
strCableCheckOngoing = " 1 "
else :
strCableCheckOngoing = " 0 " # Now the cable check is finished.
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " f_ " + strCableCheckOngoing ) ) # EDf for Encode, Din, CableCheckResponse
2023-05-22 06:20:37 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_Failed_for_CableCheckRes ) ) :
# send a CableCheckResponse with Responsecode Failed
msg = addV2GTPHeader ( " 809a0125e6cecc5020804080000400 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " CableCheck " )
2023-05-13 22:22:26 +00:00
if ( not testsuite_faultinjection_is_triggered ( TC_EVSE_Timeout_during_CableCheck ) ) :
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
2023-03-29 06:44:09 +00:00
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-11-09 18:11:18 +00:00
if ( strConverterResult . find ( " PreChargeReq " ) > 0 ) :
2023-04-19 07:05:21 +00:00
# check the request content, and fill response parameters
uTarget = 220 # default in case we cannot decode the requested voltage
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strEVTargetVoltageValue = jsondict [ " EVTargetVoltage.Value " ]
strEVTargetVoltageMultiplier = jsondict [ " EVTargetVoltage.Multiplier " ]
2023-04-19 07:05:21 +00:00
uTarget = combineValueAndMultiplier ( strEVTargetVoltageValue , strEVTargetVoltageMultiplier )
self . addToTrace ( " EV wants EVTargetVoltage " + str ( uTarget ) )
except :
self . addToTrace ( " ERROR: Could not decode the PreChargeReq " )
2023-03-29 06:44:09 +00:00
# simulating preCharge
2023-04-19 07:05:21 +00:00
if ( self . simulatedPresentVoltage < uTarget / 2 ) :
self . simulatedPresentVoltage = uTarget / 2
if ( self . simulatedPresentVoltage < uTarget - 30 ) :
self . simulatedPresentVoltage + = 20
if ( self . simulatedPresentVoltage < uTarget ) :
2023-04-16 12:09:28 +00:00
self . simulatedPresentVoltage + = 5
2024-05-27 16:40:35 +00:00
strPresentVoltage = str ( int ( self . simulatedPresentVoltage * 10 ) / 10 ) # "345"
2024-05-26 19:47:02 +00:00
# in case we control a real power supply: give the precharge target to it
self . hardwareInterface . setPowerSupplyVoltageAndCurrent ( uTarget , 1 )
2023-04-19 07:05:21 +00:00
self . callbackShowStatus ( strPresentVoltage , " EVSEPresentVoltage " )
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " g_ " + strPresentVoltage ) ) # EDg for Encode, Din, PreChargeResponse
2023-05-04 06:53:01 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_Shutdown_during_PreCharge ) ) :
# send a PreChargeResponse with StatusCode EVSE_Shutdown, to simulate a user-triggered session stop
msg = addV2GTPHeader ( " 809a02180189551e24fc9e9160004100008182800000 " )
2023-05-22 06:20:37 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_Failed_for_PreChargeRes ) ) :
# send a PreChargeResponse with ResponseCode Failed
msg = addV2GTPHeader ( " 809a0125e6cecc516080408000008284de880800 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2023-03-29 06:44:09 +00:00
self . publishStatus ( " PreCharging " + strPresentVoltage )
2023-05-13 22:22:26 +00:00
if ( not testsuite_faultinjection_is_triggered ( TC_EVSE_Timeout_during_PreCharge ) ) :
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-11-11 11:29:13 +00:00
if ( strConverterResult . find ( " ContractAuthenticationReq " ) > 0 ) :
2024-07-23 19:00:58 +00:00
# Ask the hardwareInterface, whether the user already presented a valid RFID or similar
if ( self . hardwareInterface . isUserAuthenticated ( ) ) :
strAuthFinished = " 1 "
self . addToTrace ( " Contract is fine " )
else :
strAuthFinished = " 0 "
self . addToTrace ( " Contract is not (yet) fine " )
msg = addV2GTPHeader ( exiEncode ( " EDl_ " + strAuthFinished ) ) # EDl for Encode, Din, ContractAuthenticationResponse
2023-05-20 09:07:02 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_SequenceError_for_ContractAuthenticationRes ) ) :
# send a ContractAuthenticationResponse with Responsecode SequenceError
msg = addV2GTPHeader ( " 809a021a3b7c417774813310c0A200 " )
2022-11-22 20:34:27 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " ContractAuthentication " )
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2024-05-22 08:45:41 +00:00
if ( strConverterResult . find ( " AuthorizationReq " ) > 0 ) :
2024-07-23 19:00:58 +00:00
# Ask the hardwareInterface, whether the user already presented a valid RFID or similar
if ( self . hardwareInterface . isUserAuthenticated ( ) ) :
strAuthFinished = " 1 "
self . addToTrace ( " User is Authorized " )
else :
strAuthFinished = " 0 "
self . addToTrace ( " User is not (yet) authorized " )
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " l_ " + strAuthFinished ) ) # E1l for Encode, Iso1, AuthorizationResponse
2024-05-22 08:45:41 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
self . showDecodedTransmitMessage ( msg )
self . publishStatus ( " Authorization " )
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest )
2022-12-08 23:22:18 +00:00
if ( strConverterResult . find ( " CurrentDemandReq " ) > 0 ) :
2023-04-19 07:05:21 +00:00
# check the request content, and fill response parameters
uTarget = 220 # default in case we cannot decode the requested voltage
2024-04-29 16:59:27 +00:00
iTarget = 1 # default...
2023-04-19 07:05:21 +00:00
try :
2023-06-29 05:52:16 +00:00
jsondict = json . loads ( strConverterResult )
strEVTargetVoltageValue = jsondict [ " EVTargetVoltage.Value " ]
strEVTargetVoltageMultiplier = jsondict [ " EVTargetVoltage.Multiplier " ]
2023-04-19 07:05:21 +00:00
uTarget = combineValueAndMultiplier ( strEVTargetVoltageValue , strEVTargetVoltageMultiplier )
2024-04-29 16:59:27 +00:00
strEVTargetCurrentValue = jsondict [ " EVTargetCurrent.Value " ]
strEVTargetCurrentMultiplier = jsondict [ " EVTargetCurrent.Multiplier " ]
iTarget = combineValueAndMultiplier ( strEVTargetCurrentValue , strEVTargetCurrentMultiplier )
2023-04-19 07:05:21 +00:00
self . addToTrace ( " EV wants EVTargetVoltage " + str ( uTarget ) )
2023-06-29 05:52:16 +00:00
current_soc = int ( jsondict . get ( " DC_EVStatus.EVRESSSOC " , - 1 ) )
full_soc = int ( jsondict . get ( " FullSOC " , - 1 ) )
energy_capacity = int ( jsondict . get ( " EVEnergyCapacity.Value " , - 1 ) )
energy_request = int ( jsondict . get ( " EVEnergyRequest.Value " , - 1 ) )
2023-06-26 17:38:45 +00:00
self . publishSoCs ( current_soc , full_soc , energy_capacity , energy_request , origin = " CurrentDemandReq " )
2023-05-25 21:08:52 +00:00
2023-06-26 17:38:45 +00:00
self . callbackShowStatus ( str ( current_soc ) , " soc " )
2024-04-29 16:59:27 +00:00
self . callbackShowStatus ( str ( uTarget ) + " V, " + str ( iTarget ) + " A " , " UandI " )
2023-05-25 21:08:52 +00:00
2023-04-19 07:05:21 +00:00
except :
self . addToTrace ( " ERROR: Could not decode the CurrentDemandReq " )
self . simulatedPresentVoltage = uTarget + 3 * random ( ) # The charger provides the voltage which is demanded by the car.
2024-05-27 16:40:35 +00:00
strPresentVoltage = str ( int ( self . simulatedPresentVoltage * 10 ) / 10 ) # "345"
2023-04-19 07:05:21 +00:00
self . callbackShowStatus ( strPresentVoltage , " EVSEPresentVoltage " )
2023-04-26 18:41:45 +00:00
strEVSEPresentCurrent = " 1 " # Just as a dummy current
2023-12-11 19:49:34 +00:00
if ( self . blChargeStopTrigger == 1 ) :
# User pressed the STOP button on the charger. Send EVSE_Shutdown.
self . addToTrace ( " User pressed the STOP button on the charger. Sending EVSE_Shutdown. " )
strEVSEStatus = " 2 " # 2=EVSE_Shutdown, means the user stopped the session on the charger.
else :
# The normal case. No stop requested from user. Just send EVSE_Ready.
strEVSEStatus = " 1 " # 1=EVSE_Ready
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " i_ " + strPresentVoltage + " _ " + strEVSEPresentCurrent + " _ " + strEVSEStatus ) ) # EDi for Encode, Din, CurrentDemandRes
2023-05-04 06:53:01 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_Malfunction_during_CurrentDemand ) ) :
# send a CurrentDemandResponse with StatusCode EVSE_Malfunction, to simulate e.g. a voltage overshoot
msg = addV2GTPHeader ( " 809a02203fa9e71c31bc920100821b430b933b4b7339032b93937b908e08043000081828440201818000040060a11c06030306402038441380 " )
2023-05-13 22:22:26 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_Shutdown_during_CurrentDemand ) ) :
# send a CurrentDemandResponse with StatusCode EVSE_Shutdown, to simulate a user stop request
msg = addV2GTPHeader ( " 809a0125e15c2cd0e000410000018280001818000000040a1b648030300002038486580800 " )
2023-05-22 06:20:37 +00:00
if ( testsuite_faultinjection_is_triggered ( TC_EVSE_ResponseCode_Failed_for_CurrentDemandRes ) ) :
# send a CurrentDemandResponse with ResponseCode Failed
msg = addV2GTPHeader ( " 809a0125e6cecc50e0804080000082867dc8081818000000040a1b64802030882702038486580800 " )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " CurrentDemand " )
2023-05-13 22:22:26 +00:00
if ( not testsuite_faultinjection_is_triggered ( TC_EVSE_Timeout_during_CurrentDemand ) ) :
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-12-08 23:22:18 +00:00
if ( strConverterResult . find ( " WeldingDetectionReq " ) > 0 ) :
# todo: check the request content, and fill response parameters
2023-12-12 21:10:33 +00:00
# simulate the decreasing voltage during the weldingDetection:
self . simulatedPresentVoltage = self . simulatedPresentVoltage * 0.8 + 3 * random ( )
2024-05-27 16:40:35 +00:00
strPresentVoltage = str ( int ( self . simulatedPresentVoltage * 10 ) / 10 ) # "345"
2023-12-12 21:10:33 +00:00
self . callbackShowStatus ( strPresentVoltage , " EVSEPresentVoltage " )
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " j_ " + strPresentVoltage ) ) # EDj for Encode, Din, WeldingDetectionRes
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " WeldingDetection " )
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-12-08 23:22:18 +00:00
if ( strConverterResult . find ( " SessionStopReq " ) > 0 ) :
# todo: check the request content, and fill response parameters
2024-05-22 08:45:41 +00:00
msg = addV2GTPHeader ( exiEncode ( " E " + self . schemaSelection + " k " ) ) # EDk for Encode, Din, SessionStopRes
2024-03-06 07:25:12 +00:00
self . showDecodedTransmitMessage ( msg )
2022-12-08 23:22:18 +00:00
self . addToTrace ( " responding " + prettyHexMessage ( msg ) )
2022-12-19 17:09:39 +00:00
self . publishStatus ( " SessionStop " )
2023-05-25 21:08:52 +00:00
self . Tcp . transmit ( msg )
self . enterState ( stateWaitForFlexibleRequest ) # todo: not clear, what is specified in DIN
2022-11-11 11:29:13 +00:00
2022-11-09 18:11:18 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-09 18:11:18 +00:00
def stateFunctionWaitForChargeParameterDiscoveryRequest ( self ) :
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-09 18:11:18 +00:00
def stateFunctionWaitForCableCheckRequest ( self ) :
2022-11-04 22:51:07 +00:00
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def stateFunctionWaitForPreChargeRequest ( self ) :
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
def stateFunctionWaitForPowerDeliveryRequest ( self ) :
if ( len ( self . rxData ) > 0 ) :
2022-11-22 20:34:27 +00:00
self . addToTrace ( " In state WaitForPowerDeliveryRequest, received " + prettyHexMessage ( self . rxData ) )
self . addToTrace ( " Todo: Reaction in state WaitForPowerDeliveryRequest is not implemented yet. " )
2022-11-04 22:51:07 +00:00
self . rxData = [ ]
self . enterState ( 0 )
if ( self . isTooLong ( ) ) :
self . enterState ( 0 )
2023-05-25 21:08:52 +00:00
stateFunctions = {
2022-11-04 22:51:07 +00:00
stateWaitForSupportedApplicationProtocolRequest : stateFunctionWaitForSupportedApplicationProtocolRequest ,
stateWaitForSessionSetupRequest : stateFunctionWaitForSessionSetupRequest ,
stateWaitForServiceDiscoveryRequest : stateFunctionWaitForServiceDiscoveryRequest ,
2022-11-08 21:53:18 +00:00
stateWaitForServicePaymentSelectionRequest : stateFunctionWaitForServicePaymentSelectionRequest ,
2022-11-09 18:11:18 +00:00
stateWaitForFlexibleRequest : stateFunctionWaitForFlexibleRequest ,
2022-11-08 21:53:18 +00:00
stateWaitForChargeParameterDiscoveryRequest : stateFunctionWaitForChargeParameterDiscoveryRequest ,
2022-11-04 22:51:07 +00:00
stateWaitForCableCheckRequest : stateFunctionWaitForCableCheckRequest ,
stateWaitForPreChargeRequest : stateFunctionWaitForPreChargeRequest ,
stateWaitForPowerDeliveryRequest : stateFunctionWaitForPowerDeliveryRequest ,
}
2023-05-25 21:08:52 +00:00
2022-11-07 08:21:25 +00:00
def reInit ( self ) :
2023-05-25 21:08:52 +00:00
self . addToTrace ( " re-initializing fsmEvse " )
2022-11-07 08:21:25 +00:00
self . state = 0
self . cyclesInState = 0
self . rxData = [ ]
2023-03-29 06:44:09 +00:00
self . simulatedPresentVoltage = 0
2023-03-18 18:14:23 +00:00
self . Tcp . resetTheConnection ( )
2023-05-25 21:08:52 +00:00
2023-03-18 18:14:23 +00:00
def socketStateNotification ( self , notification ) :
if ( notification == 0 ) :
# The TCP informs us, that the connection is broken.
# Let's restart the state machine.
self . publishStatus ( " TCP conn broken " )
self . addToTrace ( " re-initializing fsmEvse due to broken connection " )
self . reInit ( )
if ( notification == 1 ) :
# The TCP informs us, that it is listening, means waiting for incoming connection.
self . publishStatus ( " Listening TCP " )
if ( notification == 2 ) :
# The TCP informs us, that a connection is established.
self . publishStatus ( " TCP connected " )
2022-11-07 08:21:25 +00:00
2023-05-25 21:08:52 +00:00
def __init__ ( self , addressManager , callbackAddToTrace , hardwareInterface , callbackShowStatus , callbackSoCStatus = None ) :
2022-11-22 20:34:27 +00:00
self . callbackAddToTrace = callbackAddToTrace
2022-12-19 17:09:39 +00:00
self . callbackShowStatus = callbackShowStatus
2023-05-25 21:08:52 +00:00
self . callbackSoCStatus = callbackSoCStatus
2022-12-19 17:09:39 +00:00
#todo self.addressManager = addressManager
2024-05-26 19:47:02 +00:00
self . hardwareInterface = hardwareInterface
2023-05-25 21:08:52 +00:00
self . addToTrace ( " initializing fsmEvse " )
2022-11-29 07:40:34 +00:00
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 )
2023-03-18 18:14:23 +00:00
self . Tcp = pyPlcTcpSocket . pyPlcTcpServerSocket ( self . callbackAddToTrace , self . socketStateNotification )
2022-11-04 22:51:07 +00:00
self . state = 0
self . cyclesInState = 0
self . rxData = [ ]
2023-06-26 17:38:45 +00:00
self . evccid = " "
2023-12-11 19:49:34 +00:00
self . blChargeStopTrigger = 0
2024-01-06 20:03:56 +00:00
self . nCableCheckLoops = 0
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +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 " + str(self.rxData))
2022-11-04 22:51:07 +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 )
2023-05-25 21:08:52 +00:00
2023-12-11 19:49:34 +00:00
def stopCharging ( self ) :
self . blChargeStopTrigger = 1
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00
if __name__ == " __main__ " :
print ( " Testing the evse state machine " )
evse = fsmEvse ( )
print ( " Press Ctrl-Break for aborting " )
while ( True ) :
time . sleep ( 0.1 )
evse . mainfunction ( )
2023-05-25 21:08:52 +00:00
2022-11-04 22:51:07 +00:00