mirror of
https://github.com/uhi22/pyPLC.git
synced 2024-11-20 01:13:58 +00:00
Added MQTT backend and more usage of hardwareInterface in EVSE mode
This commit is contained in:
parent
ccc8136306
commit
89aa023865
4 changed files with 134 additions and 16 deletions
|
@ -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
|
||||
|
|
19
fsmEvse.py
19
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.simulatedPresentVoltage<uTarget):
|
||||
self.simulatedPresentVoltage += 5
|
||||
strPresentVoltage = str(self.simulatedPresentVoltage) # "345"
|
||||
|
||||
if getConfigValueBool('evse_simulate_precharge'):
|
||||
strPresentVoltage = str(self.simulatedPresentVoltage) # "345"
|
||||
else:
|
||||
strPresentVoltage = str(self.hardwareInterface.getInletVoltage())
|
||||
self.callbackShowStatus(strPresentVoltage, "EVSEPresentVoltage")
|
||||
msg = addV2GTPHeader(exiEncode("EDg_"+strPresentVoltage)) # EDg for Encode, Din, PreChargeResponse
|
||||
if (testsuite_faultinjection_is_triggered(TC_EVSE_Shutdown_during_PreCharge)):
|
||||
|
@ -255,7 +260,11 @@ class fsmEvse():
|
|||
strEVTargetVoltageValue = jsondict["EVTargetVoltage.Value"]
|
||||
strEVTargetVoltageMultiplier = jsondict["EVTargetVoltage.Multiplier"]
|
||||
uTarget = combineValueAndMultiplier(strEVTargetVoltageValue, strEVTargetVoltageMultiplier)
|
||||
self.addToTrace("EV wants EVTargetVoltage " + str(uTarget))
|
||||
strEVTargetCurrentValue = jsondict["EVTargetCurrent.Value"]
|
||||
strEVTargetCurrentMultiplier = jsondict["EVTargetCurrent.Multiplier"]
|
||||
iTarget = combineValueAndMultiplier(strEVTargetCurrentValue, strEVTargetCurrentMultiplier)
|
||||
self.addToTrace("EV wants EVTargetVoltage " + str(uTarget) + " and EVTargetCurrent " + str(iTarget))
|
||||
self.hardwareInterface.setPevRequest(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))
|
||||
|
@ -268,9 +277,9 @@ class fsmEvse():
|
|||
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.
|
||||
strPresentVoltage = str(self.simulatedPresentVoltage)
|
||||
strPresentVoltage = str(self.hardwareInterface.getInletVoltage()) #str(self.simulatedPresentVoltage)
|
||||
self.callbackShowStatus(strPresentVoltage, "EVSEPresentVoltage")
|
||||
strEVSEPresentCurrent = "1" # Just as a dummy current
|
||||
strEVSEPresentCurrent = str(self.hardwareInterface.getAccuMaxCurrent()) #"1" # Just as a dummy current
|
||||
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.")
|
||||
|
@ -378,7 +387,7 @@ class fsmEvse():
|
|||
self.callbackShowStatus = callbackShowStatus
|
||||
self.callbackSoCStatus = callbackSoCStatus
|
||||
#todo self.addressManager = addressManager
|
||||
#todo self.hardwareInterface = hardwareInterface
|
||||
self.hardwareInterface = hardwareInterface
|
||||
self.addToTrace("initializing fsmEvse")
|
||||
self.faultInjectionDelayUntilSocketOpen_s = 0
|
||||
if (self.faultInjectionDelayUntilSocketOpen_s>0):
|
||||
|
|
|
@ -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,9 +196,7 @@ class hardwareInterface():
|
|||
return self.inletVoltage
|
||||
|
||||
def getAccuVoltage(self):
|
||||
if (getConfigValue("digital_output_device")=="celeron55device"):
|
||||
return self.accuVoltage
|
||||
elif getConfigValue("charge_parameter_backend")=="chademo":
|
||||
if getConfigValue("charge_parameter_backend") in ["chademo", "mqtt", "celeron55device"]:
|
||||
return self.accuVoltage
|
||||
#todo: get real measured voltage from the accu
|
||||
self.accuVoltage = 230
|
||||
|
@ -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")
|
||||
|
@ -224,6 +256,12 @@ class hardwareInterface():
|
|||
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
|
||||
self.callbackShowStatus = callbackShowStatus
|
||||
|
@ -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.
|
||||
|
@ -459,6 +500,35 @@ class hardwareInterface():
|
|||
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.")
|
||||
|
|
26
simulateMqttBackend.py
Normal file
26
simulateMqttBackend.py
Normal file
|
@ -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()
|
Loading…
Reference in a new issue