From 73955f8b03f59e35f71303420598668733df0329 Mon Sep 17 00:00:00 2001 From: johannes Date: Tue, 12 Dec 2023 12:23:49 +0100 Subject: [PATCH 1/6] changes to startup script --- fsmPev.py | 1 + hardwareInterface.py | 4 ++-- starter.sh | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/fsmPev.py b/fsmPev.py index 332a417..57f79bf 100644 --- a/fsmPev.py +++ b/fsmPev.py @@ -286,6 +286,7 @@ class fsmPev(): self.sessionId = strSessionId except: self.addToTrace("ERROR: Could not decode the sessionID") + 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 diff --git a/hardwareInterface.py b/hardwareInterface.py index 534fa19..968b1f8 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -12,8 +12,8 @@ from time import sleep, time from configmodule import getConfigValue, getConfigValueBool import sys # For exit_on_session_end hack -PinCp = "P8_18" -PinPowerRelay = "P8_16" +PinCp = "P9_41" +PinPowerRelay = "P9_17" if (getConfigValue("digital_output_device")=="beaglebone"): # In case we run on beaglebone, we want to use GPIO ports. diff --git a/starter.sh b/starter.sh index ee38ce4..2fc5136 100755 --- a/starter.sh +++ b/starter.sh @@ -39,9 +39,11 @@ sysctl net.ipv6.conf.eth0.keep_addr_on_down=1 # Todo: Why this needed? On raspberry, where the NetworkManager is not runnning, this disturbs, because # afterwards the pyPlc does not see the interfaces IPv6 address. # Todo: make this configurable, for the cases we need this. -# ip link set eth0 down +ip link set eth0 down +ip link set can0 down sleep 1 -# ip link set eth0 up +ip link set eth0 up +ip link set can0 up type can restart-ms 100 bitrate 500000 sleep 1 # show the addresses @@ -53,17 +55,25 @@ pwd # create directory for the log files. mkdir -p log # prepare the file names for the log files +pushd log +gzip *.log || true +popd date=$(date "+%Y-%m-%d_%H%M%S") -logfile=./log/"$date"_pevNoGui.log -tcpdump_logfile=./log/"$date"_tcpdump.pcap +#logfile=./log/"$date"_pevNoGui.log +#logfile=./log/pevNoGui.log +index=`cat log/logindex` +logfile=`printf "log/%04d_pevNoGui.log" $index` +tcpdump_logfile=`printf "log/%04d_tcpdump.pcap" $index` +index=$(($index + 1)) +echo $index > log/logindex echo "logfile: $logfile" echo "tcpdump_logfile: $tcpdump_logfile" # start the tcpdump -start_tcpdump "$tcpdump_logfile" +#start_tcpdump "$tcpdump_logfile" -echo "$date" >> "$logfile" +echo "$date" > "$logfile" git log --oneline -1 >> "$logfile" || echo "Not a git repo" >> "$logfile" ip addr >> "$logfile" pwd >> "$logfile" From 18f8ab7aef76b29da3e05d58f861fa66f2ee4f38 Mon Sep 17 00:00:00 2001 From: Bernhard Urban-Forster Date: Tue, 29 Oct 2024 23:11:50 +0100 Subject: [PATCH 2/6] docu: Notes for TPlink TL-PA4010P v2.3 --- doc/hardware.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/hardware.md b/doc/hardware.md index a6a3775..c992141 100644 --- a/doc/hardware.md +++ b/doc/hardware.md @@ -36,7 +36,7 @@ The open-plc-utils report the version MAC-QCA7500-2.8.0.30-01-20190707-CS. Contra: - Even with the modified configuration for pev (according to https://openinverter.org/forum/viewtopic.php?p=55120#p55120), the QCA7500 does not send SLAC parameter requests. This means: Cannot be used as pev. -### TPlink TL-PA4010P +### TPlink TL-PA4010P v5.0 This adaptor was suggested by https://openinverter.org/forum/viewtopic.php?p=37085#p37085 and there, successfully used to establish a communication to the CCS charger. @@ -67,6 +67,12 @@ How to modify: - connect cables to supply the device. Works with 12V, also works with 5V from an USB power bank. - connect cables and circuit (1nF and 150ohms in series) for connecting to the pilot line. +#### TPlink TL-PA4010P v2.3 +![image](https://github.com/user-attachments/assets/c98d100c-f7fc-4e41-a183-1b5eeebd43fa) +![image](https://github.com/user-attachments/assets/6fef8096-4373-42d9-9688-7d284f587940) + +Confirmed to be working as EVSE (needs patching via open-plc-utils) and supplied with 5V. + ## Controller for the PEV Besides the homeplug modem, there are additional parts necessary for a vehicle to perform CCS charging. Two of these are: 1. The inlet voltage measurement. 2. The control of CP state and relays. From 89aa02386545750aadb818d54b77883ca7d11c68 Mon Sep 17 00:00:00 2001 From: johannes Date: Mon, 4 Nov 2024 15:16:08 +0100 Subject: [PATCH 3/6] Added MQTT backend and more usage of hardwareInterface in EVSE mode --- doc/pyPlc.ini.template | 11 +++++ fsmEvse.py | 19 ++++++--- hardwareInterface.py | 94 +++++++++++++++++++++++++++++++++++++----- simulateMqttBackend.py | 26 ++++++++++++ 4 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 simulateMqttBackend.py diff --git a/doc/pyPlc.ini.template b/doc/pyPlc.ini.template index 7630da9..64cb9ab 100644 --- a/doc/pyPlc.ini.template +++ b/doc/pyPlc.ini.template @@ -44,6 +44,10 @@ light_bulb_demo = no # Possible values: yes or no soc_simulation = yes +# Precharge simulation +# In EVSE mode simulate precharging rather than using physical value +evse_simulate_precharge = yes + # Device selection for the digital outputs, for CP state and power relays # Possible options: # dieter: Serial controlled device, which controls the digital outputs. E.g. arduino from https://github.com/uhi22/dieter @@ -52,6 +56,7 @@ soc_simulation = yes digital_output_device = dieter #digital_output_device = beaglebone #digital_output_device = celeron55device +#digital_output_device = mqtt # Device to read the physically measured inlet voltage in PevMode @@ -62,6 +67,7 @@ digital_output_device = dieter #analog_input_device = dieter analog_input_device = none #analog_input_device = celeron55device +#analog_input_device = mqtt # Criteria for ending the PreCharge phase in PevMode @@ -89,6 +95,9 @@ u_delta_max_for_end_of_precharge = 10 # auto serial_port = auto serial_baud = 19200 +# MQTT broker and base topic in case MQTT is used +mqtt_broker = localhost +mqtt_topic = pyPlc # The target voltage used in the CurrentDemandRequest. # This is a value for first try-outs. Better would @@ -121,11 +130,13 @@ udp_syslog_enable = Yes # Set backend for obtaining charging parameters, we start with CHAdeMO CAN for now # Need to make a simulator device and maybe a celeron device? # Possible values: +# mqtt: pyPLC is used as a bridge between an MQTT broker and the car or EVSE # chademo: pyPLC is used as bridge between a CCS charger and a CHAdeMO* car. # Limitations/explanations here: https://openinverter.org/forum/viewtopic.php?p=57894#p57894 and # https://openinverter.org/forum/viewtopic.php?t=1063 (Is it possible to make a CCS to CHAdeMO adapter?) # none: all other use cases charge_parameter_backend = chademo +#charge_parameter_backend = mqtt # REST callback for SoC states. Comment out to disable. Do not leave a trailing slash # This parameter is used in EvseMode, to configure where the data which is retrieved diff --git a/fsmEvse.py b/fsmEvse.py index 17f0466..7cec567 100644 --- a/fsmEvse.py +++ b/fsmEvse.py @@ -27,6 +27,7 @@ class fsmEvse(): def publishStatus(self, s): self.callbackShowStatus(s, "evseState") + self.hardwareInterface.displayState(s) def publishSoCs(self, current_soc: int, full_soc: int = -1, energy_capacity: int = -1, energy_request: int = -1, evccid: str = "", origin: str = ""): if self.callbackSoCStatus is not None: @@ -223,7 +224,11 @@ class fsmEvse(): self.simulatedPresentVoltage += 20 if (self.simulatedPresentVoltage0): diff --git a/hardwareInterface.py b/hardwareInterface.py index 968b1f8..f316b3d 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -15,6 +15,10 @@ import sys # For exit_on_session_end hack PinCp = "P9_41" PinPowerRelay = "P9_17" +if (getConfigValue("digital_output_device")=="mqtt"): + # If we use MQTT as a hardware interface import it here + import paho.mqtt.client as mqtt + if (getConfigValue("digital_output_device")=="beaglebone"): # In case we run on beaglebone, we want to use GPIO ports. import Adafruit_BBIO.GPIO as GPIO @@ -85,12 +89,18 @@ class hardwareInterface(): def addToTrace(self, s): self.callbackAddToTrace("[HARDWAREINTERFACE] " + s) + def displayState(self, state): + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/fsm_state", state) + def setStateB(self): self.addToTrace("Setting CP line into state B.") if (getConfigValue("digital_output_device")=="beaglebone"): GPIO.output(PinCp, GPIO.LOW) if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("cp=0\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/cpstate", "B") self.outvalue &= ~1 def setStateC(self): @@ -99,6 +109,8 @@ class hardwareInterface(): GPIO.output(PinCp, GPIO.HIGH) if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("cp=1\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/cpstate", "C") self.outvalue |= 1 def setPowerRelayOn(self): @@ -107,6 +119,8 @@ class hardwareInterface(): GPIO.output(PinPowerRelay, GPIO.HIGH) if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("contactor=1\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/relay_request", "on") self.outvalue |= 2 def setPowerRelayOff(self): @@ -115,6 +129,8 @@ class hardwareInterface(): GPIO.output(PinPowerRelay, GPIO.LOW) if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("contactor=0\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/relay_request", "off") self.outvalue &= ~2 def setRelay2On(self): @@ -134,12 +150,16 @@ class hardwareInterface(): self.addToTrace("Locking the connector") if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("lock\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/lock_request", "lock") # todo control the lock motor into lock direction until the end (time based or current based stopping?) def triggerConnectorUnlocking(self): - self.addToTrace("Unocking the connector") + self.addToTrace("Unlocking the connector") if (getConfigValue("digital_output_device")=="celeron55device"): self.ser.write(bytes("unlock\n", "utf-8")) + if (getConfigValue("digital_output_device")=="mqtt"): + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/lock_request", "unlock") # todo control the lock motor into unlock direction until the end (time based or current based stopping?) def isConnectorLocked(self): @@ -148,13 +168,27 @@ class hardwareInterface(): # return self.lock_confirmed return 1 # todo: use the real connector lock feedback + def setPevRequest(self, voltage, current): + #here we can publish the voltage and current requests received from the PEV side + if getConfigValue("charge_parameter_backend") == "mqtt": + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(voltage)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(current)) + def setChargerParameters(self, maxVoltage, maxCurrent): + self.addToTrace("Setting charger parameters maxVoltage=%d V, maxCurrent=%d A" % (maxVoltage, maxCurrent)) self.maxChargerVoltage = int(maxVoltage) self.maxChargerCurrent = int(maxCurrent) + if getConfigValue("charge_parameter_backend") == "mqtt": + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_max_voltage", str(maxVoltage)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_max_current", str(maxCurrent)) def setChargerVoltageAndCurrent(self, voltageNow, currentNow): + self.addToTrace("Setting charger present values Voltage=%d V, Current=%d A" % (voltageNow, currentNow)) self.chargerVoltage = int(voltageNow) self.chargerCurrent = int(currentNow) + if getConfigValue("charge_parameter_backend") == "mqtt": + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_voltage", voltageNow) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_current", currentNow) def getInletVoltage(self): # uncomment this line, to take the simulated inlet voltage instead of the really measured @@ -162,10 +196,8 @@ class hardwareInterface(): return self.inletVoltage def getAccuVoltage(self): - if (getConfigValue("digital_output_device")=="celeron55device"): - return self.accuVoltage - elif getConfigValue("charge_parameter_backend")=="chademo": - return self.accuVoltage + if getConfigValue("charge_parameter_backend") in ["chademo", "mqtt", "celeron55device"]: + return self.accuVoltage #todo: get real measured voltage from the accu self.accuVoltage = 230 return self.accuVoltage @@ -178,15 +210,15 @@ class hardwareInterface(): if self.accuMaxCurrent >= EVMaximumCurrentLimit: return EVMaximumCurrentLimit return self.accuMaxCurrent - elif getConfigValue("charge_parameter_backend")=="chademo": - return self.accuMaxCurrent #set by CAN + elif getConfigValue("charge_parameter_backend") in ["chademo", "mqtt"]: + return self.accuMaxCurrent #set by CAN or MQTT #todo: get max charging current from the BMS self.accuMaxCurrent = 10 return self.accuMaxCurrent def getAccuMaxVoltage(self): - if getConfigValue("charge_parameter_backend")=="chademo": - return self.accuMaxVoltage #set by CAN + if getConfigValue("charge_parameter_backend") in ["chademo", "mqtt"]: + return self.accuMaxVoltage #set by CAN or MQTT elif getConfigValue("charge_target_voltage"): self.accuMaxVoltage = getConfigValue("charge_target_voltage") else: @@ -205,7 +237,7 @@ class hardwareInterface(): def getSoc(self): if self.callbackShowStatus: self.callbackShowStatus(format(self.soc_percent,".1f"), "soc") - if (getConfigValue("digital_output_device")=="celeron55device"): + if (getConfigValue("digital_output_device") in ["celeron55device", "mqtt"]): return self.soc_percent #todo: get SOC from the BMS self.callbackShowStatus(format(self.simulatedSoc,".1f"), "soc") @@ -223,6 +255,12 @@ class hardwareInterface(): # Port configuration according to https://github.com/jsphuebner/pyPLC/commit/475f7fe9f3a67da3d4bd9e6e16dfb668d0ddb1d6 GPIO.setup(PinPowerRelay, GPIO.OUT) #output for port relays GPIO.setup(PinCp, GPIO.OUT) #output for CP + + if (getConfigValue("digital_output_device") == "mqtt"): + self.mqttclient = mqtt.Client() + self.mqttclient.on_connect = self.mqtt_on_connect + self.mqttclient.on_message = self.mqtt_on_message + self.mqttclient.connect(getConfigValue("mqtt_broker"), 1883, 60) def __init__(self, callbackAddToTrace=None, callbackShowStatus=None): self.callbackAddToTrace = callbackAddToTrace @@ -381,6 +419,9 @@ class hardwareInterface(): if (getConfigValue("digital_output_device")=="celeron55device"): self.mainfunction_celeron55device() + if (getConfigValue("digital_output_device")=="mqtt"): + self.mainfunction_mqtt() + if getConfigValueBool("exit_on_session_end"): # TODO: This is a hack. Do this in fsmPev instead and publish some # of these values into there if needed. @@ -458,7 +499,36 @@ class hardwareInterface(): if self.accuMaxCurrent != 0: self.addToTrace("CHAdeMO: No current limit update for over 1s, setting current to 0") self.accuMaxCurrent = 0 + + def mainfunction_mqtt(self): + self.mqttclient.loop(timeout=0.1) + # The callback for when the client receives a CONNACK response from the server. + def mqtt_on_connect(self, client, userdata, flags, rc): + self.addToTrace(f"MQTT connected with result code {rc}") + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe(getConfigValue("mqtt_topic") + "/#") + + def mqtt_on_message(self, client, userdata, msg): + if msg.topic == getConfigValue("mqtt_topic") + "/battery_voltage": + self.accuVoltage = float(msg.payload) + self.addToTrace("MQTT: Set battery voltage to %f V" % self.accuVoltage) + elif msg.topic == getConfigValue("mqtt_topic") + "/target_voltage": + self.accuMaxVoltage = float(msg.payload) + self.addToTrace("MQTT: Set target voltage to %f V" % self.accuMaxVoltage) + elif msg.topic == getConfigValue("mqtt_topic") + "/target_current": + self.accuMaxCurrent = float(msg.payload) + self.addToTrace("MQTT: Set current request to %f A" % self.accuMaxCurrent) + elif msg.topic == getConfigValue("mqtt_topic") + "/soc": + self.simulatedSoc = float(msg.payload) + self.soc_percent = self.simulatedSoc + self.addToTrace("MQTT: Set SoC to %f %%" % self.simulatedSoc) + elif msg.topic == getConfigValue("mqtt_topic") + "/inlet_voltage": + self.inletVoltage = float(msg.payload) + self.addToTrace("MQTT: Set inlet voltage to %f V" % self.inletVoltage) + def myPrintfunction(s): print("myprint " + s) @@ -469,6 +539,8 @@ if __name__ == "__main__": hw.mainfunction() if (i==20): hw.showOnDisplay("Hello", "A DEMO", "321.0V") + hw.setChargerParameters(500, 125) + hw.setChargerVoltageAndCurrent(360, 100) if (i==50): hw.setStateC() if (i==100): @@ -486,6 +558,6 @@ if __name__ == "__main__": hw.setRelay2Off() if (i==320): hw.showOnDisplay("This", "...is...", "DONE :-)") - sleep(0.03) + sleep(0.01) hw.close() print("finished.") diff --git a/simulateMqttBackend.py b/simulateMqttBackend.py new file mode 100644 index 0000000..9639e89 --- /dev/null +++ b/simulateMqttBackend.py @@ -0,0 +1,26 @@ +import paho.mqtt.client as mqtt +from configmodule import getConfigValue, getConfigValueBool + +def mqtt_on_connect(client, userdata, flags, rc): + client.subscribe(getConfigValue("mqtt_topic") + "/#") + +def mqtt_on_message(client, userdata, msg): + global simulatedInletVoltage + if msg.topic == getConfigValue("mqtt_topic") + "/fsm_state": + if "CableCheck" in msg.payload.decode("utf-8"): + simulatedInletVoltage = 0 + if "PreCharging" in msg.payload.decode("utf-8"): + client.publish(getConfigValue("mqtt_topic") + "/inlet_voltage", simulatedInletVoltage) + simulatedInletVoltage = simulatedInletVoltage + 15 + elif msg.topic == getConfigValue("mqtt_topic") + "/pev_voltage": + client.publish(getConfigValue("mqtt_topic") + "/inlet_voltage", msg.payload) + elif msg.topic == getConfigValue("mqtt_topic") + "/pev_current": + client.publish(getConfigValue("mqtt_topic") + "/target_current", msg.payload) + +simulatedInletVoltage = 0 +mqttclient = mqtt.Client() +mqttclient.on_connect = mqtt_on_connect +mqttclient.on_message = mqtt_on_message +mqttclient.connect(getConfigValue("mqtt_broker"), 1883, 60) + +mqttclient.loop_forever() From 48ba04f2ded300495e5639f5c9c96f0fab568d59 Mon Sep 17 00:00:00 2001 From: johannes Date: Mon, 4 Nov 2024 15:28:34 +0100 Subject: [PATCH 4/6] Corrected typo --- hardwareInterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardwareInterface.py b/hardwareInterface.py index 5f4b7ed..d8a296b 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -190,8 +190,8 @@ class hardwareInterface(): self.homeplughandler.sendSpecialMessageToControlThePowerSupply(targetVoltage, targetCurrent) #here we can publish the voltage and current requests received from the PEV side if getConfigValue("charge_parameter_backend") == "mqtt": - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(voltage)) - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(current)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(targetVoltage)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(targetVoltage)) def getInletVoltage(self): # uncomment this line, to take the simulated inlet voltage instead of the really measured From a89d097948457b041d31b1ee1dfeb81239338ed7 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 14 Nov 2024 19:54:19 +0100 Subject: [PATCH 5/6] Bodged in vehicle SoC --- fsmEvse.py | 4 ++-- hardwareInterface.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fsmEvse.py b/fsmEvse.py index 042ee6e..6038758 100644 --- a/fsmEvse.py +++ b/fsmEvse.py @@ -270,7 +270,7 @@ class fsmEvse(): strPresentVoltage = str(self.hardwareInterface.getInletVoltage()) # in case we control a real power supply: give the precharge target to it - self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, 1) + self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, 1, 0) self.callbackShowStatus(strPresentVoltage, "EVSEPresentVoltage") msg = addV2GTPHeader(exiEncode("E"+self.schemaSelection+"g_"+strPresentVoltage)) # EDg for Encode, Din, PreChargeResponse if (testsuite_faultinjection_is_triggered(TC_EVSE_Shutdown_during_PreCharge)): @@ -329,11 +329,11 @@ class fsmEvse(): strEVTargetCurrentMultiplier = jsondict["EVTargetCurrent.Multiplier"] iTarget = combineValueAndMultiplier(strEVTargetCurrentValue, strEVTargetCurrentMultiplier) self.addToTrace("EV wants EVTargetVoltage " + str(uTarget) + " and EVTargetCurrent " + str(iTarget)) - self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, iTarget) 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)) + self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, iTarget, current_soc) self.publishSoCs(current_soc, full_soc, energy_capacity, energy_request, origin="CurrentDemandReq") diff --git a/hardwareInterface.py b/hardwareInterface.py index d8a296b..559d4b8 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -185,13 +185,14 @@ class hardwareInterface(): self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_voltage", voltageNow) self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_current", currentNow) - def setPowerSupplyVoltageAndCurrent(self, targetVoltage, targetCurrent): + def setPowerSupplyVoltageAndCurrent(self, targetVoltage, targetCurrent, soc): # if we are the charger, and have a real power supply which we want to control, we do it here self.homeplughandler.sendSpecialMessageToControlThePowerSupply(targetVoltage, targetCurrent) #here we can publish the voltage and current requests received from the PEV side if getConfigValue("charge_parameter_backend") == "mqtt": self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(targetVoltage)) - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(targetVoltage)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(targetCurrent)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/soc", str(soc)) def getInletVoltage(self): # uncomment this line, to take the simulated inlet voltage instead of the really measured From 2cfa5169b9c4a22071a846fc276e5656952bd459 Mon Sep 17 00:00:00 2001 From: johannes Date: Tue, 19 Nov 2024 21:57:37 +0100 Subject: [PATCH 6/6] Reshuffled topics, see also https://openinverter.org/wiki/PyPLC#pyPLC_MQTT_interface --- fsmEvse.py | 7 +++--- hardwareInterface.py | 55 ++++++++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/fsmEvse.py b/fsmEvse.py index 6038758..1a9d0af 100644 --- a/fsmEvse.py +++ b/fsmEvse.py @@ -30,6 +30,7 @@ class fsmEvse(): self.hardwareInterface.displayState(s) def publishSoCs(self, current_soc: int, full_soc: int = -1, energy_capacity: int = -1, energy_request: int = -1, evccid: str = "", origin: str = ""): + self.hardwareInterface.displaySoc(current_soc) if self.callbackSoCStatus is not None: self.callbackSoCStatus(current_soc, full_soc, energy_capacity, energy_request, self.evccid, origin) @@ -270,7 +271,7 @@ class fsmEvse(): strPresentVoltage = str(self.hardwareInterface.getInletVoltage()) # in case we control a real power supply: give the precharge target to it - self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, 1, 0) + self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, 1) self.callbackShowStatus(strPresentVoltage, "EVSEPresentVoltage") msg = addV2GTPHeader(exiEncode("E"+self.schemaSelection+"g_"+strPresentVoltage)) # EDg for Encode, Din, PreChargeResponse if (testsuite_faultinjection_is_triggered(TC_EVSE_Shutdown_during_PreCharge)): @@ -333,7 +334,7 @@ class fsmEvse(): full_soc = int(jsondict.get("FullSOC", -1)) energy_capacity = int(jsondict.get("EVEnergyCapacity.Value", -1)) energy_request = int(jsondict.get("EVEnergyRequest.Value", -1)) - self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, iTarget, current_soc) + self.hardwareInterface.setPowerSupplyVoltageAndCurrent(uTarget, iTarget) self.publishSoCs(current_soc, full_soc, energy_capacity, energy_request, origin="CurrentDemandReq") @@ -346,7 +347,7 @@ class fsmEvse(): strPresentVoltage = str(self.hardwareInterface.getInletVoltage()) #str(self.simulatedPresentVoltage) self.callbackShowStatus(strPresentVoltage, "EVSEPresentVoltage") strEVSEPresentCurrent = str(self.hardwareInterface.getAccuMaxCurrent()) #"1" # Just as a dummy current - if (self.blChargeStopTrigger == 1): + if (self.blChargeStopTrigger == 1 or self.hardwareInterface.stopRequest()): # 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. diff --git a/hardwareInterface.py b/hardwareInterface.py index 559d4b8..bba1dbe 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -7,6 +7,7 @@ # python -m serial.tools.list_ports import serial # the pyserial +from pyPlcModes import * from serial.tools.list_ports import comports from time import sleep, time from configmodule import getConfigValue, getConfigValueBool @@ -93,6 +94,10 @@ class hardwareInterface(): if (getConfigValue("digital_output_device")=="mqtt"): self.mqttclient.publish(getConfigValue("mqtt_topic") + "/fsm_state", state) + def displaySoc(self, soc): + if getConfigValue("charge_parameter_backend") == "mqtt": + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/soc", str(soc)) + def setStateB(self): self.addToTrace("Setting CP line into state B.") if (getConfigValue("digital_output_device")=="beaglebone"): @@ -185,14 +190,13 @@ class hardwareInterface(): self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_voltage", voltageNow) self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_current", currentNow) - def setPowerSupplyVoltageAndCurrent(self, targetVoltage, targetCurrent, soc): + def setPowerSupplyVoltageAndCurrent(self, targetVoltage, targetCurrent): # if we are the charger, and have a real power supply which we want to control, we do it here self.homeplughandler.sendSpecialMessageToControlThePowerSupply(targetVoltage, targetCurrent) #here we can publish the voltage and current requests received from the PEV side if getConfigValue("charge_parameter_backend") == "mqtt": - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(targetVoltage)) - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(targetCurrent)) - self.mqttclient.publish(getConfigValue("mqtt_topic") + "/soc", str(soc)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/target_voltage", str(targetVoltage)) + self.mqttclient.publish(getConfigValue("mqtt_topic") + "/target_current", str(targetCurrent)) def getInletVoltage(self): # uncomment this line, to take the simulated inlet voltage instead of the really measured @@ -247,6 +251,9 @@ class hardwareInterface(): self.callbackShowStatus(format(self.simulatedSoc,".1f"), "soc") return self.simulatedSoc + def stopRequest(self): + return not self.enabled + def isUserAuthenticated(self): # If the user needs to authorize, fill this function in a way that it returns False as long as # we shall wait for the users authorization, and returns True if the authentication was successfull. @@ -273,20 +280,22 @@ class hardwareInterface(): GPIO.setup(PinCp, GPIO.OUT) #output for CP if (getConfigValue("digital_output_device") == "mqtt"): - self.mqttclient = mqtt.Client() + self.mqttclient = mqtt.Client(client_id="pyplc") self.mqttclient.on_connect = self.mqtt_on_connect self.mqttclient.on_message = self.mqtt_on_message self.mqttclient.connect(getConfigValue("mqtt_broker"), 1883, 60) - def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, homeplughandler=None): + def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, homeplughandler=None, mode=C_EVSE_MODE): self.callbackAddToTrace = callbackAddToTrace self.callbackShowStatus = callbackShowStatus self.homeplughandler = homeplughandler + self.mode = mode self.loopcounter = 0 self.outvalue = 0 self.simulatedSoc = 20.0 # percent self.demoAuthenticationCounter = 0 + self.enabled = True #Charging enabled self.inletVoltage = 0.0 # volts self.accuVoltage = 0.0 @@ -528,25 +537,41 @@ class hardwareInterface(): # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. - client.subscribe(getConfigValue("mqtt_topic") + "/#") + if self.mode == C_EVSE_MODE: + client.subscribe(getConfigValue("mqtt_topic") + "/charger_max_voltage") #todo + client.subscribe(getConfigValue("mqtt_topic") + "/charger_max_current") #todo + client.subscribe(getConfigValue("mqtt_topic") + "/charger_voltage") + client.subscribe(getConfigValue("mqtt_topic") + "/charger_current") + client.subscribe(getConfigValue("mqtt_topic") + "/enabled") + elif self.mode == C_EVSE_MODE: + client.subscribe(getConfigValue("mqtt_topic") + "/battery_voltage") + client.subscribe(getConfigValue("mqtt_topic") + "/target_voltage") + client.subscribe(getConfigValue("mqtt_topic") + "/target_current") + client.subscribe(getConfigValue("mqtt_topic") + "/soc") + client.subscribe(getConfigValue("mqtt_topic") + "/inlet_voltage") def mqtt_on_message(self, client, userdata, msg): - if msg.topic == getConfigValue("mqtt_topic") + "/battery_voltage": + baseTopic = getConfigValue("mqtt_topic") + + if msg.topic == (f"{baseTopic}/battery_voltage"): self.accuVoltage = float(msg.payload) self.addToTrace("MQTT: Set battery voltage to %f V" % self.accuVoltage) - elif msg.topic == getConfigValue("mqtt_topic") + "/target_voltage": + elif msg.topic == (f"{baseTopic}/target_voltage"): self.accuMaxVoltage = float(msg.payload) self.addToTrace("MQTT: Set target voltage to %f V" % self.accuMaxVoltage) - elif msg.topic == getConfigValue("mqtt_topic") + "/target_current": + elif msg.topic == (f"{baseTopic}/target_current") or msg.topic == (f"{baseTopic}/charger_current"): self.accuMaxCurrent = float(msg.payload) - self.addToTrace("MQTT: Set current request to %f A" % self.accuMaxCurrent) - elif msg.topic == getConfigValue("mqtt_topic") + "/soc": + self.addToTrace("MQTT: Set current request to %s A" % self.accuMaxCurrent) + elif msg.topic == (f"{baseTopic}/soc"): self.simulatedSoc = float(msg.payload) self.soc_percent = self.simulatedSoc self.addToTrace("MQTT: Set SoC to %f %%" % self.simulatedSoc) - elif msg.topic == getConfigValue("mqtt_topic") + "/inlet_voltage": - self.inletVoltage = float(msg.payload) - self.addToTrace("MQTT: Set inlet voltage to %f V" % self.inletVoltage) + elif msg.topic == (f"{baseTopic}/inlet_voltage") or msg.topic == (f"{baseTopic}/charger_voltage"): + self.inletVoltage = float(msg.payload) #in EVSE mode this is present charger voltage + self.addToTrace("MQTT: Set present voltage to %f V" % self.inletVoltage) + elif msg.topic == (f"{baseTopic}/enabled"): + self.enabled = bool(int(msg.payload)) + self.addToTrace("MQTT: Setting enable flag to %d" % self.enabled) def myPrintfunction(s): print("myprint " + s)