mirror of
https://github.com/uhi22/pyPLC.git
synced 2024-11-20 01:13:58 +00:00
added SLAC sequencer for PevMode
This commit is contained in:
parent
a055846a3f
commit
6794100922
4 changed files with 315 additions and 88 deletions
|
@ -65,6 +65,11 @@ class addressManager():
|
||||||
self.pevMac = pevMac
|
self.pevMac = pevMac
|
||||||
print("[addressManager] pev has MAC " + prettyMac(self.pevMac))
|
print("[addressManager] pev has MAC " + prettyMac(self.pevMac))
|
||||||
|
|
||||||
|
def setEvseMac(self, evseMac):
|
||||||
|
# During the SLAC, the MAC of the EVSE was found out. Store it, maybe we need it later.
|
||||||
|
self.evseMac = evseMac
|
||||||
|
print("[addressManager] evse has MAC " + prettyMac(self.evseMac))
|
||||||
|
|
||||||
def setPevIp(self, pevIp):
|
def setPevIp(self, pevIp):
|
||||||
# During SDP, the IPv6 of the PEV was found out. Store it, maybe we need it later.
|
# During SDP, the IPv6 of the PEV was found out. Store it, maybe we need it later.
|
||||||
if (type(pevIp)==type(bytearray([0]))):
|
if (type(pevIp)==type(bytearray([0]))):
|
||||||
|
|
14
fsmPev.py
14
fsmPev.py
|
@ -19,6 +19,7 @@ stateWaitForChargeParameterDiscoveryResponse = 6
|
||||||
stateWaitForCableCheckResponse = 7
|
stateWaitForCableCheckResponse = 7
|
||||||
stateWaitForPreChargeResponse = 8
|
stateWaitForPreChargeResponse = 8
|
||||||
stateWaitForPowerDeliveryResponse = 9
|
stateWaitForPowerDeliveryResponse = 9
|
||||||
|
stateNotYetInitialized = 10
|
||||||
|
|
||||||
class fsmPev():
|
class fsmPev():
|
||||||
def enterState(self, n):
|
def enterState(self, n):
|
||||||
|
@ -145,7 +146,10 @@ class fsmPev():
|
||||||
print("As Demo, we stay in PreCharge until the timeout elapses.")
|
print("As Demo, we stay in PreCharge until the timeout elapses.")
|
||||||
if (self.isTooLong()):
|
if (self.isTooLong()):
|
||||||
self.enterState(0)
|
self.enterState(0)
|
||||||
|
|
||||||
|
def stateFunctionNotYetInitialized(self):
|
||||||
|
pass # nothing to do, just wait for external event for re-initialization
|
||||||
|
|
||||||
stateFunctions = {
|
stateFunctions = {
|
||||||
stateInitialized: stateFunctionInitialized,
|
stateInitialized: stateFunctionInitialized,
|
||||||
stateWaitForSupportedApplicationProtocolResponse: stateFunctionWaitForSupportedApplicationProtocolResponse,
|
stateWaitForSupportedApplicationProtocolResponse: stateFunctionWaitForSupportedApplicationProtocolResponse,
|
||||||
|
@ -155,6 +159,7 @@ class fsmPev():
|
||||||
stateWaitForChargeParameterDiscoveryResponse: stateFunctionWaitForChargeParameterDiscoveryResponse,
|
stateWaitForChargeParameterDiscoveryResponse: stateFunctionWaitForChargeParameterDiscoveryResponse,
|
||||||
stateWaitForCableCheckResponse: stateFunctionWaitForCableCheckResponse,
|
stateWaitForCableCheckResponse: stateFunctionWaitForCableCheckResponse,
|
||||||
stateWaitForPreChargeResponse: stateFunctionWaitForPreChargeResponse,
|
stateWaitForPreChargeResponse: stateFunctionWaitForPreChargeResponse,
|
||||||
|
stateNotYetInitialized: stateFunctionNotYetInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
def reInit(self):
|
def reInit(self):
|
||||||
|
@ -163,7 +168,7 @@ class fsmPev():
|
||||||
self.cyclesInState = 0
|
self.cyclesInState = 0
|
||||||
self.rxData = []
|
self.rxData = []
|
||||||
if (not self.Tcp.isConnected):
|
if (not self.Tcp.isConnected):
|
||||||
self.Tcp.connect('fe80::e0ad:99ac:52eb:85d3', 15118)
|
self.Tcp.connect('fe80:0000:0000:0000:c690:83f3:fbcb:980e', 15118) # todo: use the EVSE IP address which was found out with SDP
|
||||||
if (not self.Tcp.isConnected):
|
if (not self.Tcp.isConnected):
|
||||||
print("connection failed")
|
print("connection failed")
|
||||||
else:
|
else:
|
||||||
|
@ -172,7 +177,10 @@ class fsmPev():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
print("initializing fsmPev")
|
print("initializing fsmPev")
|
||||||
self.Tcp = pyPlcTcpSocket.pyPlcTcpClientSocket()
|
self.Tcp = pyPlcTcpSocket.pyPlcTcpClientSocket()
|
||||||
self.reInit()
|
self.state = stateNotYetInitialized
|
||||||
|
self.cyclesInState = 0
|
||||||
|
self.rxData = []
|
||||||
|
# we do NOT call the reInit, because we want to wait with the connection until external trigger comes
|
||||||
|
|
||||||
def mainfunction(self):
|
def mainfunction(self):
|
||||||
#self.Tcp.mainfunction() # call the lower-level worker
|
#self.Tcp.mainfunction() # call the lower-level worker
|
||||||
|
|
369
pyPlcHomeplug.py
369
pyPlcHomeplug.py
|
@ -40,7 +40,6 @@ from pyPlcModes import *
|
||||||
|
|
||||||
MAC_BROADCAST = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ]
|
MAC_BROADCAST = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ]
|
||||||
|
|
||||||
|
|
||||||
CM_SET_KEY = 0x6008
|
CM_SET_KEY = 0x6008
|
||||||
CM_GET_KEY = 0x600C
|
CM_GET_KEY = 0x600C
|
||||||
CM_SC_JOIN = 0x6010
|
CM_SC_JOIN = 0x6010
|
||||||
|
@ -67,6 +66,7 @@ CM_VALIDATE = 0x6078
|
||||||
CM_SLAC_MATCH = 0x607C
|
CM_SLAC_MATCH = 0x607C
|
||||||
CM_SLAC_USER_DATA = 0x6080
|
CM_SLAC_USER_DATA = 0x6080
|
||||||
CM_ATTEN_PROFILE = 0x6084
|
CM_ATTEN_PROFILE = 0x6084
|
||||||
|
CM_GET_SW = 0xA000
|
||||||
|
|
||||||
MMTYPE_REQ = 0x0000
|
MMTYPE_REQ = 0x0000
|
||||||
MMTYPE_CNF = 0x0001
|
MMTYPE_CNF = 0x0001
|
||||||
|
@ -294,14 +294,55 @@ class pyPlcHomeplug():
|
||||||
# rest is 00
|
# rest is 00
|
||||||
|
|
||||||
def composeStartAttenCharInd(self):
|
def composeStartAttenCharInd(self):
|
||||||
# todo
|
# reference: see wireshark interpreted frame from ioniq
|
||||||
print("todo: implement composeStartAttenCharInd")
|
self.mytransmitbuffer = bytearray(60)
|
||||||
pass
|
self.cleanTransmitBuffer()
|
||||||
|
# Destination MAC
|
||||||
|
self.fillDestinationMac(MAC_BROADCAST)
|
||||||
|
# Source MAC
|
||||||
|
self.fillSourceMac(self.myMAC)
|
||||||
|
# Protocol
|
||||||
|
self.mytransmitbuffer[12]=0x88 # Protocol HomeplugAV
|
||||||
|
self.mytransmitbuffer[13]=0xE1
|
||||||
|
self.mytransmitbuffer[14]=0x01 # version
|
||||||
|
self.mytransmitbuffer[15]=0x6A # START_ATTEN_CHAR.IND
|
||||||
|
self.mytransmitbuffer[16]=0x60 #
|
||||||
|
self.mytransmitbuffer[17]=0x00 # 2 bytes fragmentation information. 0000 means: unfragmented.
|
||||||
|
self.mytransmitbuffer[18]=0x00 #
|
||||||
|
self.mytransmitbuffer[19]=0x00 # apptype
|
||||||
|
self.mytransmitbuffer[20]=0x00 # sectype
|
||||||
|
self.mytransmitbuffer[21]=0x0a # number of sounds: 10
|
||||||
|
self.mytransmitbuffer[22]=0x06 # timeout N*100ms
|
||||||
|
self.mytransmitbuffer[23]=0x01 # response type
|
||||||
|
self.fillSourceMac(self.myMAC, 24) # 24 to 29: sound_forwarding_sta, MAC of the PEV
|
||||||
|
self.fillSourceMac(self.myMAC, 30) # 30 to 37: runid, filled with MAC of PEV and two bytes 00 00
|
||||||
|
# rest is 00
|
||||||
|
|
||||||
def composeNmbcSoundInd(self):
|
def composeNmbcSoundInd(self):
|
||||||
# todo
|
# reference: see wireshark interpreted frame from Ioniq
|
||||||
print("todo: implement composeNmbcSoundInd")
|
self.mytransmitbuffer = bytearray(71)
|
||||||
pass
|
self.cleanTransmitBuffer()
|
||||||
|
# Destination MAC
|
||||||
|
self.fillDestinationMac(MAC_BROADCAST)
|
||||||
|
# Source MAC
|
||||||
|
self.fillSourceMac(self.myMAC)
|
||||||
|
# Protocol
|
||||||
|
self.mytransmitbuffer[12]=0x88 # Protocol HomeplugAV
|
||||||
|
self.mytransmitbuffer[13]=0xE1
|
||||||
|
self.mytransmitbuffer[14]=0x01 # version
|
||||||
|
self.mytransmitbuffer[15]=0x76 # NMBC_SOUND.IND
|
||||||
|
self.mytransmitbuffer[16]=0x60 #
|
||||||
|
self.mytransmitbuffer[17]=0x00 # 2 bytes fragmentation information. 0000 means: unfragmented.
|
||||||
|
self.mytransmitbuffer[18]=0x00 #
|
||||||
|
self.mytransmitbuffer[19]=0x00 # apptype
|
||||||
|
self.mytransmitbuffer[20]=0x00 # sectype
|
||||||
|
self.mytransmitbuffer[21]=0x00 # 21 to 37 sender ID, all 00
|
||||||
|
self.mytransmitbuffer[38]=self.remainingNumberOfSounds # countdown. Remaining number of sounds. Starts with 9 and counts down to 0.
|
||||||
|
self.fillSourceMac(self.myMAC, 39) # 39 to 46: runid, filled with MAC of PEV and two bytes 00 00
|
||||||
|
self.mytransmitbuffer[47]=0x00 # 47 to 54: reserved, all 00
|
||||||
|
# 55 to 70: random number. All 0xff in the ioniq message.
|
||||||
|
for i in range(55, 71):
|
||||||
|
self.mytransmitbuffer[i]=0xFF
|
||||||
|
|
||||||
def composeAttenCharInd(self):
|
def composeAttenCharInd(self):
|
||||||
self.mytransmitbuffer = bytearray(129)
|
self.mytransmitbuffer = bytearray(129)
|
||||||
|
@ -336,6 +377,57 @@ class pyPlcHomeplug():
|
||||||
self.mytransmitbuffer[127]=0x13
|
self.mytransmitbuffer[127]=0x13
|
||||||
self.mytransmitbuffer[128]=0x19
|
self.mytransmitbuffer[128]=0x19
|
||||||
|
|
||||||
|
def composeAttenCharRsp(self):
|
||||||
|
# reference: see wireshark interpreted frame from Ioniq
|
||||||
|
self.mytransmitbuffer = bytearray(70)
|
||||||
|
self.cleanTransmitBuffer()
|
||||||
|
# Destination MAC
|
||||||
|
self.fillDestinationMac(self.evseMac)
|
||||||
|
# Source MAC
|
||||||
|
self.fillSourceMac(self.myMAC)
|
||||||
|
# Protocol
|
||||||
|
self.mytransmitbuffer[12]=0x88 # Protocol HomeplugAV
|
||||||
|
self.mytransmitbuffer[13]=0xE1
|
||||||
|
self.mytransmitbuffer[14]=0x01 # version
|
||||||
|
self.mytransmitbuffer[15]=0x6F # ATTEN_CHAR.RSP
|
||||||
|
self.mytransmitbuffer[16]=0x60 #
|
||||||
|
self.mytransmitbuffer[17]=0x00 # 2 bytes fragmentation information. 0000 means: unfragmented.
|
||||||
|
self.mytransmitbuffer[18]=0x00 #
|
||||||
|
self.mytransmitbuffer[19]=0x00 # apptype
|
||||||
|
self.mytransmitbuffer[20]=0x00 # sectype
|
||||||
|
self.fillSourceMac(self.myMAC, 21) # 21 to 26: source MAC
|
||||||
|
self.fillDestinationMac(self.myMAC, 27) # 27 to 34: runid. The PEV mac, plus 00 00.
|
||||||
|
# 35 to 51: source_id, all 00
|
||||||
|
# 52 to 68: resp_id, all 00
|
||||||
|
# 69: result. 0 is ok
|
||||||
|
|
||||||
|
def composeSlacMatchReq(self):
|
||||||
|
# reference: see wireshark interpreted frame from Ioniq
|
||||||
|
self.mytransmitbuffer = bytearray(85)
|
||||||
|
self.cleanTransmitBuffer()
|
||||||
|
# Destination MAC
|
||||||
|
self.fillDestinationMac(self.evseMac)
|
||||||
|
# Source MAC
|
||||||
|
self.fillSourceMac(self.myMAC)
|
||||||
|
# Protocol
|
||||||
|
self.mytransmitbuffer[12]=0x88 # Protocol HomeplugAV
|
||||||
|
self.mytransmitbuffer[13]=0xE1
|
||||||
|
self.mytransmitbuffer[14]=0x01 # version
|
||||||
|
self.mytransmitbuffer[15]=0x7C # SLAC_MATCH.REQ
|
||||||
|
self.mytransmitbuffer[16]=0x60 #
|
||||||
|
self.mytransmitbuffer[17]=0x00 # 2 bytes fragmentation information. 0000 means: unfragmented.
|
||||||
|
self.mytransmitbuffer[18]=0x00 #
|
||||||
|
self.mytransmitbuffer[19]=0x00 # apptype
|
||||||
|
self.mytransmitbuffer[20]=0x00 # sectype
|
||||||
|
self.mytransmitbuffer[21]=0x3E # 21 to 22: length
|
||||||
|
self.mytransmitbuffer[22]=0x00 #
|
||||||
|
# 23 to 39: pev_id, all 00
|
||||||
|
self.fillSourceMac(self.myMAC, 40) # 40 to 45: PEV MAC
|
||||||
|
# 46 to 62: evse_id, all 00
|
||||||
|
self.fillDestinationMac(self.evseMac, 63) # 63 to 68: EVSE MAC
|
||||||
|
self.fillSourceMac(self.myMAC, 63) # 69 to 76: runid. The PEV mac, plus 00 00.
|
||||||
|
# 77 to 84: reserved, all 00
|
||||||
|
|
||||||
def composeSlacMatchCnf(self):
|
def composeSlacMatchCnf(self):
|
||||||
self.mytransmitbuffer = bytearray(109)
|
self.mytransmitbuffer = bytearray(109)
|
||||||
self.cleanTransmitBuffer()
|
self.cleanTransmitBuffer()
|
||||||
|
@ -366,75 +458,6 @@ class pyPlcHomeplug():
|
||||||
self.setNmkAt(93) # 93 to 108 NMK. We can freely choose this. Normally we should use a random number.
|
self.setNmkAt(93) # 93 to 108 NMK. We can freely choose this. Normally we should use a random number.
|
||||||
|
|
||||||
|
|
||||||
def runPevSequencer(self):
|
|
||||||
# in PEV mode, initiate the SLAC sequence
|
|
||||||
# Todo: Timing between the states, and timeout handling to be implemented
|
|
||||||
if (self.iAmPev==1):
|
|
||||||
if (self.pevSequenceState==0): # waiting for start condition
|
|
||||||
# In real life we would check whether we see 5% PWM on the pilot line.
|
|
||||||
# Then we would maybe wait a little bit until the homeplug modems are awake.
|
|
||||||
# Now sending the first packet for the SLAC sequence:
|
|
||||||
self.composeSlacParamReq()
|
|
||||||
self.addToTrace("transmitting SLAC_PARAM.REQ...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 1
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==1): # waiting for SLAC_PARAM.CNF
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==2): # received SLAC_PARAM.CNF
|
|
||||||
# todo: transmit the START_ATTEN_CHAR.IND 3 times
|
|
||||||
self.composeStartAttenCharInd()
|
|
||||||
self.addToTrace("transmitting START_ATTEN_CHAR.IND...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 3
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==3):
|
|
||||||
# todo: transmit the START_ATTEN_CHAR.IND 3 times
|
|
||||||
self.composeStartAttenCharInd()
|
|
||||||
self.addToTrace("transmitting START_ATTEN_CHAR.IND...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 4
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==4):
|
|
||||||
# todo: transmit the START_ATTEN_CHAR.IND 3 times
|
|
||||||
self.composeStartAttenCharInd()
|
|
||||||
self.addToTrace("transmitting START_ATTEN_CHAR.IND...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 5
|
|
||||||
self.SoundCountDown=10
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==5): # START_ATTEN_CHAR.IND are finished. Now we send 10 MNBC_SOUND.IND
|
|
||||||
self.composeNmbcSoundInd()
|
|
||||||
self.addToTrace("transmitting MNBC_SOUND.IND...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
if (self.SoundCountDown>0):
|
|
||||||
self.SoundCountDown-=1
|
|
||||||
else:
|
|
||||||
self.pevSequenceState = 6
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==6): # waiting for ATTEN_CHAR.IND
|
|
||||||
# todo: it is possible that we receive this message from multiple chargers. We need
|
|
||||||
# to select the charger with the loudest reported signals.
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==7): # ATTEN_CHAR.IND was received and the nearest charger decided
|
|
||||||
self.composeAttenCharRsp()
|
|
||||||
self.addToTrace("transmitting ATTEN_CHAR.RSP...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 8
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==8): # ATTEN_CHAR.RSP was transmitted. Next is SLAC_MATCH.REQ
|
|
||||||
self.composeSlacMatchReq()
|
|
||||||
self.addToTrace("transmitting SLAC_MATCH.REQ...")
|
|
||||||
self.transmit(self.mytransmitbuffer)
|
|
||||||
self.pevSequenceState = 9
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==9): # waiting for SLAC_MATCH.CNF
|
|
||||||
return
|
|
||||||
if (self.pevSequenceState==10): # SLAC is finished, KEY is set, AVLN is established.
|
|
||||||
# todo: inform the higher-level state machine, that now it can start the SDP
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sendTestFrame(self, selection):
|
def sendTestFrame(self, selection):
|
||||||
if (selection=="1"):
|
if (selection=="1"):
|
||||||
|
@ -468,6 +491,7 @@ class pyPlcHomeplug():
|
||||||
def evaluateGetKeyCnf(self):
|
def evaluateGetKeyCnf(self):
|
||||||
# The getkey response contains the Network ID (NID), even if the request was rejected. We store the NID,
|
# The getkey response contains the Network ID (NID), even if the request was rejected. We store the NID,
|
||||||
# to have it available for the next request.
|
# to have it available for the next request.
|
||||||
|
self.addToTrace("received GET_KEY.CNF")
|
||||||
s = ""
|
s = ""
|
||||||
for i in range(0, 7): # NID has 7 bytes
|
for i in range(0, 7): # NID has 7 bytes
|
||||||
self.NID[i] = self.myreceivebuffer[29+i]
|
self.NID[i] = self.myreceivebuffer[29+i]
|
||||||
|
@ -478,16 +502,36 @@ class pyPlcHomeplug():
|
||||||
# The Setkey confirmation
|
# The Setkey confirmation
|
||||||
# In spec, the result 0 means "success". But in reality, the 0 means: did not work. When it works,
|
# In spec, the result 0 means "success". But in reality, the 0 means: did not work. When it works,
|
||||||
# then the LEDs are blinking (device is restarting), and the response is 1.
|
# then the LEDs are blinking (device is restarting), and the response is 1.
|
||||||
|
self.addToTrace("received SET_KEY.CNF")
|
||||||
result = self.myreceivebuffer[19]
|
result = self.myreceivebuffer[19]
|
||||||
if (result == 0):
|
if (result == 0):
|
||||||
self.addToTrace("SetKeyCnf says 0, this is a bad sign")
|
self.addToTrace("SetKeyCnf says 0, this is a bad sign")
|
||||||
else:
|
else:
|
||||||
self.addToTrace("SetKeyCnf says " + str(result) + ", this is formally 'rejected', but indeed ok.")
|
self.addToTrace("SetKeyCnf says " + str(result) + ", this is formally 'rejected', but indeed ok.")
|
||||||
|
|
||||||
|
def evaluateGetSwCnf(self):
|
||||||
|
# The GET_SW confirmation. This contains the software version of the homeplug modem.
|
||||||
|
# Reference: see wireshark interpreted frame from TPlink, Ioniq and Alpitronic charger
|
||||||
|
self.addToTrace("received GET_SW.CNF")
|
||||||
|
self.numberOfSoftwareVersionResponses+=1
|
||||||
|
sourceMac=bytearray(6)
|
||||||
|
for i in range(0, 6):
|
||||||
|
sourceMac[i] = self.myreceivebuffer[6+i]
|
||||||
|
strMac=prettyMac(sourceMac)
|
||||||
|
verLen = self.myreceivebuffer[22]
|
||||||
|
strVersion = ""
|
||||||
|
if ((verLen>0) and (verLen<0x30)):
|
||||||
|
for i in range(0, verLen):
|
||||||
|
x = self.myreceivebuffer[23+i]
|
||||||
|
if (x<0x20):
|
||||||
|
x=0x20 # make unprintable character to space.
|
||||||
|
strVersion+=chr(x) # convert ASCII code to string
|
||||||
|
self.addToTrace("For " + strMac + " the software version is " + strVersion)
|
||||||
|
|
||||||
def evaluateSlacParamReq(self):
|
def evaluateSlacParamReq(self):
|
||||||
# We received a SLAC_PARAM request from the PEV. This is the initiation of a SLAC procedure.
|
# We received a SLAC_PARAM request from the PEV. This is the initiation of a SLAC procedure.
|
||||||
# We extract the pev MAC from it.
|
# We extract the pev MAC from it.
|
||||||
|
self.addToTrace("received SLAC_PARAM.REQ")
|
||||||
for i in range(0, 6):
|
for i in range(0, 6):
|
||||||
self.pevMac[i] = self.myreceivebuffer[6+i]
|
self.pevMac[i] = self.myreceivebuffer[6+i]
|
||||||
self.addressManager.setPevMac(self.pevMac)
|
self.addressManager.setPevMac(self.pevMac)
|
||||||
|
@ -495,11 +539,12 @@ class pyPlcHomeplug():
|
||||||
# If we want to emulate an EVSE, we want to answer.
|
# If we want to emulate an EVSE, we want to answer.
|
||||||
if (self.iAmEvse==1):
|
if (self.iAmEvse==1):
|
||||||
self.composeSlacParamCnf()
|
self.composeSlacParamCnf()
|
||||||
self.addToTrace("transmitting CM_SLAC_PARAM.CNF")
|
self.addToTrace("[EVSE] transmitting CM_SLAC_PARAM.CNF")
|
||||||
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
||||||
|
|
||||||
def evaluateSlacParamCnf(self):
|
def evaluateSlacParamCnf(self):
|
||||||
# As PEV, we receive the first response from the charger.
|
# As PEV, we receive the first response from the charger.
|
||||||
|
self.addToTrace("received SLAC_PARAM.CNF")
|
||||||
if (self.iAmPev==1):
|
if (self.iAmPev==1):
|
||||||
if (self.pevSequenceState==1): # we were waiting for the SlacParamCnf
|
if (self.pevSequenceState==1): # we were waiting for the SlacParamCnf
|
||||||
self.pevSequenceState=2 # enter next state. Will be handled in the cyclic runPevSequencer
|
self.pevSequenceState=2 # enter next state. Will be handled in the cyclic runPevSequencer
|
||||||
|
@ -508,19 +553,35 @@ class pyPlcHomeplug():
|
||||||
# We received MNBC_SOUND.IND from the PEV. Normally this happens 10times, with a countdown (remaining number of sounds)
|
# We received MNBC_SOUND.IND from the PEV. Normally this happens 10times, with a countdown (remaining number of sounds)
|
||||||
# running from 9 to 0. If the countdown is 0, this is the last message. In case we are the EVSE, we need
|
# running from 9 to 0. If the countdown is 0, this is the last message. In case we are the EVSE, we need
|
||||||
# to answer with a ATTEN_CHAR.IND, which normally contains the attenuation for 10 sounds, 58 groups.
|
# to answer with a ATTEN_CHAR.IND, which normally contains the attenuation for 10 sounds, 58 groups.
|
||||||
|
self.addToTrace("received MNBC_SOUND.IND")
|
||||||
if (self.iAmEvse==1):
|
if (self.iAmEvse==1):
|
||||||
countdown = self.myreceivebuffer[38]
|
countdown = self.myreceivebuffer[38]
|
||||||
if (countdown == 0):
|
if (countdown == 0):
|
||||||
self.composeAttenCharInd()
|
self.composeAttenCharInd()
|
||||||
self.addToTrace("transmitting ATTEN_CHAR.IND")
|
self.addToTrace("[EVSE] transmitting ATTEN_CHAR.IND")
|
||||||
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
||||||
|
|
||||||
|
def evaluateAttenCharInd(self):
|
||||||
|
self.addToTrace("received ATTEN_CHAR.IND")
|
||||||
|
if (self.iAmPev==1):
|
||||||
|
self.addToTrace("[PEVSLAC] received AttenCharInd in state " + str(self.pevSequenceState))
|
||||||
|
if (self.pevSequenceState==6): # we were waiting for the AttenCharInd
|
||||||
|
# todo: Handle the case when we receive multiple responses from different chargers.
|
||||||
|
# Wait a certain time, and compare the attenuation profiles. Decide for the nearest charger.
|
||||||
|
# Take the MAC of the charger from the frame, and store it for later use.
|
||||||
|
for i in range(0, 6):
|
||||||
|
self.evseMac[i] = self.myreceivebuffer[6+i] # source MAC starts at offset 6
|
||||||
|
self.addressManager.setEvseMac(self.evseMac)
|
||||||
|
self.pevSequenceState=7 # enter next state. Will be handled in the cyclic runPevSequencer
|
||||||
|
|
||||||
|
|
||||||
def evaluateSlacMatchReq(self):
|
def evaluateSlacMatchReq(self):
|
||||||
# We received SLAC_MATCH.REQ from the PEV.
|
# We received SLAC_MATCH.REQ from the PEV.
|
||||||
# If we are EVSE, we send the response.
|
# If we are EVSE, we send the response.
|
||||||
|
self.addToTrace("received SLAC_MATCH.REQ")
|
||||||
if (self.iAmEvse==1):
|
if (self.iAmEvse==1):
|
||||||
self.composeSlacMatchCnf()
|
self.composeSlacMatchCnf()
|
||||||
self.addToTrace("transmitting SLAC_MATCH.CNF")
|
self.addToTrace("[EVSE] transmitting SLAC_MATCH.CNF")
|
||||||
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
||||||
|
|
||||||
|
|
||||||
|
@ -534,24 +595,27 @@ class pyPlcHomeplug():
|
||||||
# The SET_KEY was already done at startup.
|
# The SET_KEY was already done at startup.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
self.addToTrace("received SLAC_MATCH.CNF")
|
||||||
s = ""
|
s = ""
|
||||||
for i in range(0, 7): # NID has 7 bytes
|
for i in range(0, 7): # NID has 7 bytes
|
||||||
self.NID[i] = self.myreceivebuffer[85+i]
|
self.NID[i] = self.myreceivebuffer[85+i]
|
||||||
s=s+hex(self.NID[i])+ " "
|
s=s+hex(self.NID[i])+ " "
|
||||||
print("From SlacMatchCnf, got network ID (NID) " + s)
|
self.addToTrace("From SlacMatchCnf, got network ID (NID) " + s)
|
||||||
s = ""
|
s = ""
|
||||||
for i in range(0, 16):
|
for i in range(0, 16):
|
||||||
self.NMK[i] = self.myreceivebuffer[93+i]
|
self.NMK[i] = self.myreceivebuffer[93+i]
|
||||||
s=s+hex(self.NMK[i])+ " "
|
s=s+hex(self.NMK[i])+ " "
|
||||||
print("From SlacMatchCnf, got network membership key (NMK) " + s)
|
self.addToTrace("From SlacMatchCnf, got network membership key (NMK) " + s)
|
||||||
# use the extracted NMK and NID to set the key in the adaptor:
|
# use the extracted NMK and NID to set the key in the adaptor:
|
||||||
self.composeSetKey(0)
|
self.composeSetKey(0)
|
||||||
self.addToTrace("transmitting CM_SET_KEY.REQ")
|
self.addToTrace("transmitting CM_SET_KEY.REQ")
|
||||||
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
self.sniffer.sendpacket(bytes(self.mytransmitbuffer))
|
||||||
|
if (self.pevSequenceState==9): # we were waiting for finishing the SLAC_MATCH.CNF and SET_KEY.REQ
|
||||||
|
self.pevSequenceState=10
|
||||||
|
|
||||||
def evaluateReceivedHomeplugPacket(self):
|
def evaluateReceivedHomeplugPacket(self):
|
||||||
mmt = self.getManagementMessageType()
|
mmt = self.getManagementMessageType()
|
||||||
print(hex(mmt))
|
# print(hex(mmt))
|
||||||
if (mmt == CM_GET_KEY + MMTYPE_CNF):
|
if (mmt == CM_GET_KEY + MMTYPE_CNF):
|
||||||
self.evaluateGetKeyCnf()
|
self.evaluateGetKeyCnf()
|
||||||
if (mmt == CM_SLAC_MATCH + MMTYPE_REQ):
|
if (mmt == CM_SLAC_MATCH + MMTYPE_REQ):
|
||||||
|
@ -564,8 +628,142 @@ class pyPlcHomeplug():
|
||||||
self.evaluateSlacParamCnf()
|
self.evaluateSlacParamCnf()
|
||||||
if (mmt == CM_MNBC_SOUND + MMTYPE_IND):
|
if (mmt == CM_MNBC_SOUND + MMTYPE_IND):
|
||||||
self.evaluateMnbcSoundInd()
|
self.evaluateMnbcSoundInd()
|
||||||
|
if (mmt == CM_ATTEN_CHAR + MMTYPE_IND):
|
||||||
|
self.evaluateAttenCharInd()
|
||||||
if (mmt == CM_SET_KEY + MMTYPE_CNF):
|
if (mmt == CM_SET_KEY + MMTYPE_CNF):
|
||||||
self.evaluateSetKeyCnf()
|
self.evaluateSetKeyCnf()
|
||||||
|
if (mmt == CM_GET_SW + MMTYPE_CNF):
|
||||||
|
self.evaluateGetSwCnf()
|
||||||
|
|
||||||
|
|
||||||
|
def enterState(self, n):
|
||||||
|
print("[PEVSLAC] from " + str(self.pevSequenceState) + " entering " + str(n))
|
||||||
|
self.pevSequenceState = n
|
||||||
|
self.pevSequenceCyclesInState = 0
|
||||||
|
|
||||||
|
def isTooLong(self):
|
||||||
|
# The timeout handling function.
|
||||||
|
return (self.pevSequenceCyclesInState > 50)
|
||||||
|
|
||||||
|
def runPevSequencer(self):
|
||||||
|
# in PEV mode, initiate the SLAC sequence
|
||||||
|
# Todo: Timing between the states, and timeout handling to be implemented
|
||||||
|
if (self.iAmPev==1):
|
||||||
|
if (self.pevSequenceState==0): # waiting for start condition
|
||||||
|
# In real life we would check whether we see 5% PWM on the pilot line.
|
||||||
|
# Then we would maybe wait a little bit until the homeplug modems are awake.
|
||||||
|
# Now sending the first packet for the SLAC sequence:
|
||||||
|
self.composeSlacParamReq()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting SLAC_PARAM.REQ...")
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(1)
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==1): # waiting for SLAC_PARAM.CNF
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==2): # received SLAC_PARAM.CNF
|
||||||
|
self.composeStartAttenCharInd()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting START_ATTEN_CHAR.IND...")
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(3)
|
||||||
|
self.pevSequenceDelayCycles = 0
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==3):
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
self.composeStartAttenCharInd()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting START_ATTEN_CHAR.IND...") # original from ioniq is 20ms after the first
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(4)
|
||||||
|
self.pevSequenceDelayCycles = 0
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==4):
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
self.composeStartAttenCharInd()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting START_ATTEN_CHAR.IND...") # original from ioniq is 20ms after the second
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(5)
|
||||||
|
self.pevSequenceDelayCycles = 1
|
||||||
|
self.remainingNumberOfSounds = 10
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==5): # START_ATTEN_CHAR.IND are finished. Now we send 10 MNBC_SOUND.IND
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
if (self.remainingNumberOfSounds>0):
|
||||||
|
self.remainingNumberOfSounds-=1
|
||||||
|
self.composeNmbcSoundInd()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting MNBC_SOUND.IND...") # original from ioniq is 40ms after the last START_ATTEN_CHAR.IND
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
if (self.remainingNumberOfSounds==0):
|
||||||
|
self.enterState(6) # move fast to the next state, so that a fast response is catched in the correct state
|
||||||
|
self.pevSequenceDelayCycles = 0 # original from ioniq is 20ms between the messages
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==6): # waiting for ATTEN_CHAR.IND
|
||||||
|
# todo: it is possible that we receive this message from multiple chargers. We need
|
||||||
|
# to select the charger with the loudest reported signals.
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==7): # ATTEN_CHAR.IND was received and the nearest charger decided
|
||||||
|
self.composeAttenCharRsp()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting ATTEN_CHAR.RSP...")
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(8)
|
||||||
|
self.pevSequenceDelayCycles = 15 # original from ioniq is 860ms from ATTEN_CHAR.RSP to SLAC_MATCH.REQ
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==8): # ATTEN_CHAR.RSP was transmitted. Next is SLAC_MATCH.REQ
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
self.composeSlacMatchReq()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting SLAC_MATCH.REQ...")
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.enterState(9)
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==9): # waiting for SLAC_MATCH.CNF
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==10): # SLAC is finished, SET_KEY.REQ is transmitted. Wait some time, until
|
||||||
|
# the homeplug modem made the reset and is ready with the new key.
|
||||||
|
self.addToTrace("[PEVSLAC] waiting until homeplug modem starts up with new key...")
|
||||||
|
self.pevSequenceDelayCycles = 200
|
||||||
|
self.enterState(11)
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==11):
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
# modem should be ready with new key. AVLN should be established.
|
||||||
|
# To check this, we broadcast a software version request. All modems in the network should respond.
|
||||||
|
self.numberOfSoftwareVersionResponses = 0
|
||||||
|
self.composeGetSwReq()
|
||||||
|
self.addToTrace("[PEVSLAC] transmitting GetSwReq...")
|
||||||
|
self.transmit(self.mytransmitbuffer)
|
||||||
|
self.pevSequenceDelayCycles = 20
|
||||||
|
self.enterState(12)
|
||||||
|
if (self.pevSequenceState==12):
|
||||||
|
if (self.pevSequenceDelayCycles>0):
|
||||||
|
self.pevSequenceDelayCycles-=1
|
||||||
|
return
|
||||||
|
# we should have received a software version response from at least two modems.
|
||||||
|
print("[PEVSLAC] Number of modems in the AVLN: " + str(self.numberOfSoftwareVersionResponses))
|
||||||
|
if (self.numberOfSoftwareVersionResponses<2):
|
||||||
|
print("[PEVSLAC] ERROR: There should be at least two modems, one from car and one from charger.")
|
||||||
|
self.callbackAvlnEstablished(0)
|
||||||
|
self.enterState(0)
|
||||||
|
else:
|
||||||
|
# inform the higher-level state machine, that now it can start the SDP / IPv6 communication
|
||||||
|
self.callbackAvlnEstablished(1)
|
||||||
|
self.enterState(13) # Final state is reached
|
||||||
|
return
|
||||||
|
if (self.pevSequenceState==13): # AVLN is established. Nothing more to do, just wait until unplugging.
|
||||||
|
# Todo: if (self.isUnplugged()): self.pevSequenceState=0
|
||||||
|
# Or we just check the connection cyclically by sending software version requests...
|
||||||
|
self.pevSequenceDelayCycles = 500
|
||||||
|
self.enterState(11)
|
||||||
|
return
|
||||||
|
# invalid state is reached. As robustness measure, go to initial state.
|
||||||
|
self.enterState(0)
|
||||||
|
|
||||||
|
|
||||||
def findEthernetAdaptor(self):
|
def findEthernetAdaptor(self):
|
||||||
|
@ -594,13 +792,15 @@ class pyPlcHomeplug():
|
||||||
self.ipv6.enterListenMode()
|
self.ipv6.enterListenMode()
|
||||||
self.showStatus("LISTEN mode", "mode")
|
self.showStatus("LISTEN mode", "mode")
|
||||||
|
|
||||||
def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, mode=C_LISTEN_MODE, addrMan=None):
|
def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, mode=C_LISTEN_MODE, addrMan=None, callbackAvlnEstablished=None):
|
||||||
self.mytransmitbuffer = bytearray("Hallo das ist ein Test", 'UTF-8')
|
self.mytransmitbuffer = bytearray("Hallo das ist ein Test", 'UTF-8')
|
||||||
self.nPacketsReceived = 0
|
self.nPacketsReceived = 0
|
||||||
self.callbackAddToTrace = callbackAddToTrace
|
self.callbackAddToTrace = callbackAddToTrace
|
||||||
self.callbackShowStatus = callbackShowStatus
|
self.callbackShowStatus = callbackShowStatus
|
||||||
|
self.callbackAvlnEstablished = callbackAvlnEstablished
|
||||||
self.addressManager = addrMan
|
self.addressManager = addrMan
|
||||||
self.pevSequenceState = 0
|
self.pevSequenceState = 0
|
||||||
|
self.numberOfSoftwareVersionResponses = 0
|
||||||
#self.sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)
|
#self.sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)
|
||||||
# eth3 means: Third entry from back, in the list of interfaces, which is provided by pcap.findalldevs.
|
# eth3 means: Third entry from back, in the list of interfaces, which is provided by pcap.findalldevs.
|
||||||
# Improvement necessary: select the interface based on the name.
|
# Improvement necessary: select the interface based on the name.
|
||||||
|
@ -622,6 +822,7 @@ class pyPlcHomeplug():
|
||||||
self.NMK = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ] # a default network key
|
self.NMK = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ] # a default network key
|
||||||
self.NID = [ 1, 2, 3, 4, 5, 6, 7 ] # a default network ID
|
self.NID = [ 1, 2, 3, 4, 5, 6, 7 ] # a default network ID
|
||||||
self.pevMac = [0x55, 0x56, 0x57, 0x58, 0x59, 0x5A ] # a default pev MAC. Will be overwritten later.
|
self.pevMac = [0x55, 0x56, 0x57, 0x58, 0x59, 0x5A ] # a default pev MAC. Will be overwritten later.
|
||||||
|
self.evseMac = [0x55, 0x56, 0x57, 0xAA, 0xAA, 0xAA ] # a default evse MAC. Will be overwritten later.
|
||||||
self.myMAC = self.addressManager.getLocalMacAddress()
|
self.myMAC = self.addressManager.getLocalMacAddress()
|
||||||
self.runningCounter=0
|
self.runningCounter=0
|
||||||
self.ipv6 = pyPlcIpv6.ipv6handler(self.transmit, self.addressManager)
|
self.ipv6 = pyPlcIpv6.ipv6handler(self.transmit, self.addressManager)
|
||||||
|
|
|
@ -21,7 +21,8 @@ class pyPlcWorker():
|
||||||
self.addressManager.findLinkLocalIpv6Address()
|
self.addressManager.findLinkLocalIpv6Address()
|
||||||
self.callbackAddToTrace = callbackAddToTrace
|
self.callbackAddToTrace = callbackAddToTrace
|
||||||
self.callbackShowStatus = callbackShowStatus
|
self.callbackShowStatus = callbackShowStatus
|
||||||
self.hp = pyPlcHomeplug.pyPlcHomeplug(self.callbackAddToTrace, self.callbackShowStatus, self.mode, self.addressManager)
|
self.oldAvlnStatus = 0
|
||||||
|
self.hp = pyPlcHomeplug.pyPlcHomeplug(self.callbackAddToTrace, self.callbackShowStatus, self.mode, self.addressManager, self.callbackAvlnEstablished)
|
||||||
if (self.mode == C_EVSE_MODE):
|
if (self.mode == C_EVSE_MODE):
|
||||||
self.evse = fsmEvse.fsmEvse()
|
self.evse = fsmEvse.fsmEvse()
|
||||||
if (self.mode == C_PEV_MODE):
|
if (self.mode == C_PEV_MODE):
|
||||||
|
@ -32,6 +33,18 @@ class pyPlcWorker():
|
||||||
|
|
||||||
def showStatus(self, s, selection = ""):
|
def showStatus(self, s, selection = ""):
|
||||||
self.callbackShowStatus(s, selection)
|
self.callbackShowStatus(s, selection)
|
||||||
|
|
||||||
|
def callbackAvlnEstablished(self, status):
|
||||||
|
if (status==1):
|
||||||
|
print("[PLCWORKER] AVLN is formed")
|
||||||
|
if (self.oldAvlnStatus==0):
|
||||||
|
self.oldAvlnStatus = 1
|
||||||
|
if (self.mode == C_PEV_MODE):
|
||||||
|
self.pev.reInit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("[PLCWORKER] no AVLN")
|
||||||
|
self.oldAvlnStatus = 0
|
||||||
|
|
||||||
def mainfunction(self):
|
def mainfunction(self):
|
||||||
self.nMainFunctionCalls+=1
|
self.nMainFunctionCalls+=1
|
||||||
|
|
Loading…
Reference in a new issue