2022-12-02 20:25:20 +00:00
|
|
|
|
|
|
|
# For serial (including USB-to-serial) interfaces:
|
|
|
|
# https://pyserial.readthedocs.io/en/latest/pyserial.html
|
|
|
|
# Install pyserial library:
|
|
|
|
# python -m pip install pyserial
|
|
|
|
# List ports:
|
|
|
|
# python -m serial.tools.list_ports
|
|
|
|
|
|
|
|
import serial # the pyserial
|
|
|
|
from serial.tools.list_ports import comports
|
2023-06-30 08:35:41 +00:00
|
|
|
from time import sleep, time
|
2023-04-15 18:35:51 +00:00
|
|
|
from configmodule import getConfigValue, getConfigValueBool
|
2023-05-10 18:56:48 +00:00
|
|
|
import sys # For exit_on_session_end hack
|
2023-04-15 18:35:51 +00:00
|
|
|
|
2023-12-12 11:23:49 +00:00
|
|
|
PinCp = "P9_41"
|
|
|
|
PinPowerRelay = "P9_17"
|
2023-06-30 08:35:41 +00:00
|
|
|
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
# If we use MQTT as a hardware interface import it here
|
|
|
|
import paho.mqtt.client as mqtt
|
|
|
|
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="beaglebone"):
|
|
|
|
# In case we run on beaglebone, we want to use GPIO ports.
|
|
|
|
import Adafruit_BBIO.GPIO as GPIO
|
2022-12-02 20:25:20 +00:00
|
|
|
|
2023-06-20 10:43:03 +00:00
|
|
|
if (getConfigValue("charge_parameter_backend")=="chademo"):
|
2023-06-22 18:29:44 +00:00
|
|
|
# In case we use the CHAdeMO backend, we want to use CAN
|
2023-06-20 10:43:03 +00:00
|
|
|
import can
|
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
class hardwareInterface():
|
2023-04-15 18:35:51 +00:00
|
|
|
def needsSerial(self):
|
|
|
|
# Find out, whether we need a serial port. This depends on several configuration items.
|
|
|
|
if (getConfigValueBool("display_via_serial")):
|
|
|
|
return True # a display is expected to be connected to serial port.
|
|
|
|
if (getConfigValue("digital_output_device")=="dieter"):
|
|
|
|
return True # a "dieter" output device is expected to be connected on serial port.
|
|
|
|
if (getConfigValue("analog_input_device")=="dieter"):
|
|
|
|
return True # a "dieter" input device is expected to be connected on serial port.
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
return True
|
|
|
|
if (getConfigValue("analog_input_device")=="celeron55device"):
|
|
|
|
return True
|
2023-04-15 18:35:51 +00:00
|
|
|
return False # non of the functionalities need a serial port.
|
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
def findSerialPort(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
baud = int(getConfigValue("serial_baud"))
|
|
|
|
if (getConfigValue("serial_port")!="auto"):
|
|
|
|
port = getConfigValue("serial_port")
|
|
|
|
try:
|
|
|
|
self.addToTrace("Using serial port " + port)
|
|
|
|
self.ser = serial.Serial(port, baud, timeout=0)
|
|
|
|
self.isSerialInterfaceOk = True
|
|
|
|
except:
|
|
|
|
if (self.needsSerial()):
|
|
|
|
self.addToTrace("ERROR: Could not open serial port.")
|
|
|
|
else:
|
|
|
|
self.addToTrace("Could not open serial port, but also do not need it. Ok.")
|
|
|
|
self.ser = None
|
|
|
|
self.isSerialInterfaceOk = False
|
|
|
|
return
|
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
ports = []
|
2023-05-10 18:56:48 +00:00
|
|
|
self.addToTrace('Auto detection of serial ports. Available serial ports:')
|
2022-12-06 17:35:17 +00:00
|
|
|
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
2023-01-24 16:52:23 +00:00
|
|
|
if (port=="/dev/ttyAMA0"):
|
|
|
|
self.addToTrace("ignoring /dev/ttyAMA0, because this is not an USB serial port")
|
|
|
|
else:
|
|
|
|
self.addToTrace('{:2}: {:20} {!r}'.format(n, port, desc))
|
|
|
|
ports.append(port)
|
2022-12-06 17:35:17 +00:00
|
|
|
if (len(ports)<1):
|
2023-04-15 18:35:51 +00:00
|
|
|
if (self.needsSerial()):
|
|
|
|
self.addToTrace("ERROR: No serial ports found. No hardware interaction possible.")
|
|
|
|
self.ser = None
|
|
|
|
self.isSerialInterfaceOk = False
|
|
|
|
else:
|
|
|
|
self.addToTrace("We found no serial port, but also do not need it. No problem.")
|
|
|
|
self.ser = None
|
|
|
|
self.isSerialInterfaceOk = False
|
2022-12-06 17:35:17 +00:00
|
|
|
else:
|
|
|
|
self.addToTrace("ok, we take the first port, " + ports[0])
|
|
|
|
try:
|
2023-05-10 18:56:48 +00:00
|
|
|
self.ser = serial.Serial(ports[0], baud, timeout=0)
|
2023-04-15 18:35:51 +00:00
|
|
|
self.isSerialInterfaceOk = True
|
2022-12-06 17:35:17 +00:00
|
|
|
except:
|
|
|
|
self.addToTrace("ERROR: Could not open serial port.")
|
|
|
|
self.ser = None
|
2023-04-15 18:35:51 +00:00
|
|
|
self.isSerialInterfaceOk = False
|
2022-12-06 17:35:17 +00:00
|
|
|
|
|
|
|
def addToTrace(self, s):
|
|
|
|
self.callbackAddToTrace("[HARDWAREINTERFACE] " + s)
|
|
|
|
|
2024-11-04 14:16:08 +00:00
|
|
|
def displayState(self, state):
|
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/fsm_state", state)
|
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
def setStateB(self):
|
|
|
|
self.addToTrace("Setting CP line into state B.")
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="beaglebone"):
|
2023-06-30 08:35:41 +00:00
|
|
|
GPIO.output(PinCp, GPIO.LOW)
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("cp=0\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/cpstate", "B")
|
2022-12-08 23:22:18 +00:00
|
|
|
self.outvalue &= ~1
|
2022-12-06 17:35:17 +00:00
|
|
|
|
|
|
|
def setStateC(self):
|
|
|
|
self.addToTrace("Setting CP line into state C.")
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="beaglebone"):
|
2023-06-30 08:35:41 +00:00
|
|
|
GPIO.output(PinCp, GPIO.HIGH)
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("cp=1\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/cpstate", "C")
|
2022-12-08 23:22:18 +00:00
|
|
|
self.outvalue |= 1
|
|
|
|
|
|
|
|
def setPowerRelayOn(self):
|
|
|
|
self.addToTrace("Switching PowerRelay ON.")
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="beaglebone"):
|
2023-06-30 08:35:41 +00:00
|
|
|
GPIO.output(PinPowerRelay, GPIO.HIGH)
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("contactor=1\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/relay_request", "on")
|
2022-12-08 23:22:18 +00:00
|
|
|
self.outvalue |= 2
|
|
|
|
|
|
|
|
def setPowerRelayOff(self):
|
|
|
|
self.addToTrace("Switching PowerRelay OFF.")
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="beaglebone"):
|
2023-06-30 08:35:41 +00:00
|
|
|
GPIO.output(PinPowerRelay, GPIO.LOW)
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("contactor=0\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/relay_request", "off")
|
2022-12-08 23:22:18 +00:00
|
|
|
self.outvalue &= ~2
|
2022-12-16 12:03:36 +00:00
|
|
|
|
|
|
|
def setRelay2On(self):
|
2022-12-19 17:09:39 +00:00
|
|
|
self.addToTrace("Switching Relay2 ON.")
|
2022-12-16 12:03:36 +00:00
|
|
|
self.outvalue |= 4
|
|
|
|
|
|
|
|
def setRelay2Off(self):
|
2022-12-19 17:09:39 +00:00
|
|
|
self.addToTrace("Switching Relay2 OFF.")
|
2022-12-16 12:03:36 +00:00
|
|
|
self.outvalue &= ~4
|
2022-12-08 23:22:18 +00:00
|
|
|
|
2023-05-03 19:25:01 +00:00
|
|
|
def getPowerRelayConfirmation(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
return self.contactor_confirmed
|
2023-05-03 19:25:01 +00:00
|
|
|
return 1 # todo: self.contactor_confirmed
|
2023-05-04 17:08:58 +00:00
|
|
|
|
|
|
|
def triggerConnectorLocking(self):
|
|
|
|
self.addToTrace("Locking the connector")
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("lock\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/lock_request", "lock")
|
2023-05-04 17:08:58 +00:00
|
|
|
# todo control the lock motor into lock direction until the end (time based or current based stopping?)
|
|
|
|
|
|
|
|
def triggerConnectorUnlocking(self):
|
2024-11-04 14:16:08 +00:00
|
|
|
self.addToTrace("Unlocking the connector")
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.ser.write(bytes("unlock\n", "utf-8"))
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/lock_request", "unlock")
|
2023-05-04 17:08:58 +00:00
|
|
|
# todo control the lock motor into unlock direction until the end (time based or current based stopping?)
|
|
|
|
|
|
|
|
def isConnectorLocked(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
# TODO: Read the lock= value from the hardware so that this works
|
|
|
|
#if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
# return self.lock_confirmed
|
2023-05-04 17:08:58 +00:00
|
|
|
return 1 # todo: use the real connector lock feedback
|
2023-06-20 12:12:21 +00:00
|
|
|
|
|
|
|
def setChargerParameters(self, maxVoltage, maxCurrent):
|
2024-11-04 14:16:08 +00:00
|
|
|
self.addToTrace("Setting charger parameters maxVoltage=%d V, maxCurrent=%d A" % (maxVoltage, maxCurrent))
|
2023-06-20 12:12:21 +00:00
|
|
|
self.maxChargerVoltage = int(maxVoltage)
|
|
|
|
self.maxChargerCurrent = int(maxCurrent)
|
2024-11-04 14:16:08 +00:00
|
|
|
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))
|
2023-06-20 12:12:21 +00:00
|
|
|
|
|
|
|
def setChargerVoltageAndCurrent(self, voltageNow, currentNow):
|
2024-11-04 14:16:08 +00:00
|
|
|
self.addToTrace("Setting charger present values Voltage=%d V, Current=%d A" % (voltageNow, currentNow))
|
2023-06-20 12:12:21 +00:00
|
|
|
self.chargerVoltage = int(voltageNow)
|
|
|
|
self.chargerCurrent = int(currentNow)
|
2024-11-04 14:23:18 +00:00
|
|
|
|
2024-11-04 14:16:08 +00:00
|
|
|
if getConfigValue("charge_parameter_backend") == "mqtt":
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_voltage", voltageNow)
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/charger_current", currentNow)
|
2024-05-26 19:47:02 +00:00
|
|
|
|
2024-11-14 18:54:19 +00:00
|
|
|
def setPowerSupplyVoltageAndCurrent(self, targetVoltage, targetCurrent, soc):
|
2024-05-26 19:47:02 +00:00
|
|
|
# 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)
|
2024-11-04 14:23:18 +00:00
|
|
|
#here we can publish the voltage and current requests received from the PEV side
|
|
|
|
if getConfigValue("charge_parameter_backend") == "mqtt":
|
2024-11-04 14:28:34 +00:00
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_voltage", str(targetVoltage))
|
2024-11-14 18:54:19 +00:00
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/pev_current", str(targetCurrent))
|
|
|
|
self.mqttclient.publish(getConfigValue("mqtt_topic") + "/soc", str(soc))
|
2023-05-03 19:25:01 +00:00
|
|
|
|
2022-12-08 23:22:18 +00:00
|
|
|
def getInletVoltage(self):
|
2023-02-27 09:53:42 +00:00
|
|
|
# uncomment this line, to take the simulated inlet voltage instead of the really measured
|
|
|
|
# self.inletVoltage = self.simulatedInletVoltage
|
2022-12-08 23:22:18 +00:00
|
|
|
return self.inletVoltage
|
|
|
|
|
|
|
|
def getAccuVoltage(self):
|
2024-11-04 14:16:08 +00:00
|
|
|
if getConfigValue("charge_parameter_backend") in ["chademo", "mqtt", "celeron55device"]:
|
|
|
|
return self.accuVoltage
|
2022-12-08 23:22:18 +00:00
|
|
|
#todo: get real measured voltage from the accu
|
|
|
|
self.accuVoltage = 230
|
|
|
|
return self.accuVoltage
|
|
|
|
|
|
|
|
def getAccuMaxCurrent(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
# The overall current limit is currently hardcoded in
|
|
|
|
# OpenV2Gx/src/test/main_commandlineinterface.c
|
|
|
|
EVMaximumCurrentLimit = 250
|
|
|
|
if self.accuMaxCurrent >= EVMaximumCurrentLimit:
|
|
|
|
return EVMaximumCurrentLimit
|
|
|
|
return self.accuMaxCurrent
|
2024-11-04 14:16:08 +00:00
|
|
|
elif getConfigValue("charge_parameter_backend") in ["chademo", "mqtt"]:
|
|
|
|
return self.accuMaxCurrent #set by CAN or MQTT
|
2022-12-08 23:22:18 +00:00
|
|
|
#todo: get max charging current from the BMS
|
|
|
|
self.accuMaxCurrent = 10
|
|
|
|
return self.accuMaxCurrent
|
|
|
|
|
2022-12-09 13:23:32 +00:00
|
|
|
def getAccuMaxVoltage(self):
|
2024-11-04 14:16:08 +00:00
|
|
|
if getConfigValue("charge_parameter_backend") in ["chademo", "mqtt"]:
|
|
|
|
return self.accuMaxVoltage #set by CAN or MQTT
|
2023-06-20 20:45:11 +00:00
|
|
|
elif getConfigValue("charge_target_voltage"):
|
|
|
|
self.accuMaxVoltage = getConfigValue("charge_target_voltage")
|
2023-05-10 18:56:48 +00:00
|
|
|
else:
|
|
|
|
#todo: get max charging voltage from the BMS
|
|
|
|
self.accuMaxVoltage = 230
|
2022-12-09 13:23:32 +00:00
|
|
|
return self.accuMaxVoltage
|
|
|
|
|
2022-12-08 23:22:18 +00:00
|
|
|
def getIsAccuFull(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.IsAccuFull = (self.soc_percent >= 98)
|
|
|
|
else:
|
|
|
|
#todo: get "full" indication from the BMS
|
|
|
|
self.IsAccuFull = (self.simulatedSoc >= 98)
|
2022-12-08 23:22:18 +00:00
|
|
|
return self.IsAccuFull
|
2022-12-09 13:23:32 +00:00
|
|
|
|
|
|
|
def getSoc(self):
|
2023-05-10 18:56:48 +00:00
|
|
|
if self.callbackShowStatus:
|
|
|
|
self.callbackShowStatus(format(self.soc_percent,".1f"), "soc")
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device") in ["celeron55device", "mqtt"]):
|
2023-05-10 18:56:48 +00:00
|
|
|
return self.soc_percent
|
2022-12-09 13:23:32 +00:00
|
|
|
#todo: get SOC from the BMS
|
2022-12-19 17:09:39 +00:00
|
|
|
self.callbackShowStatus(format(self.simulatedSoc,".1f"), "soc")
|
2022-12-09 13:23:32 +00:00
|
|
|
return self.simulatedSoc
|
2023-04-15 18:35:51 +00:00
|
|
|
|
2024-07-23 19:00:58 +00:00
|
|
|
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.
|
|
|
|
# Discussing here: https://github.com/uhi22/pyPLC/issues/28#issuecomment-2230656379
|
|
|
|
# For testing purposes, we just use a counter to decide that we return
|
|
|
|
# once "ongoing" and then "finished".
|
|
|
|
if (self.demoAuthenticationCounter<1):
|
|
|
|
self.demoAuthenticationCounter += 1
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2023-04-15 18:35:51 +00:00
|
|
|
def initPorts(self):
|
2023-06-20 10:43:03 +00:00
|
|
|
if (getConfigValue("charge_parameter_backend") == "chademo"):
|
|
|
|
filters = [
|
|
|
|
{"can_id": 0x100, "can_mask": 0x7FF, "extended": False},
|
|
|
|
{"can_id": 0x101, "can_mask": 0x7FF, "extended": False},
|
|
|
|
{"can_id": 0x102, "can_mask": 0x7FF, "extended": False}]
|
|
|
|
self.canbus = can.interface.Bus(bustype='socketcan', channel="can0", can_filters = filters)
|
|
|
|
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("digital_output_device") == "beaglebone"):
|
|
|
|
# Port configuration according to https://github.com/jsphuebner/pyPLC/commit/475f7fe9f3a67da3d4bd9e6e16dfb668d0ddb1d6
|
2023-06-30 08:35:41 +00:00
|
|
|
GPIO.setup(PinPowerRelay, GPIO.OUT) #output for port relays
|
|
|
|
GPIO.setup(PinCp, GPIO.OUT) #output for CP
|
2024-11-04 14:16:08 +00:00
|
|
|
|
|
|
|
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)
|
2022-12-06 17:35:17 +00:00
|
|
|
|
2024-05-26 19:47:02 +00:00
|
|
|
def __init__(self, callbackAddToTrace=None, callbackShowStatus=None, homeplughandler=None):
|
2022-12-06 17:35:17 +00:00
|
|
|
self.callbackAddToTrace = callbackAddToTrace
|
2022-12-19 13:48:23 +00:00
|
|
|
self.callbackShowStatus = callbackShowStatus
|
2024-05-26 19:47:02 +00:00
|
|
|
self.homeplughandler = homeplughandler
|
2023-05-10 18:56:48 +00:00
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
self.loopcounter = 0
|
|
|
|
self.outvalue = 0
|
2022-12-08 23:22:18 +00:00
|
|
|
self.simulatedSoc = 20.0 # percent
|
2024-07-23 19:00:58 +00:00
|
|
|
self.demoAuthenticationCounter = 0
|
2023-05-10 18:56:48 +00:00
|
|
|
|
2022-12-08 23:22:18 +00:00
|
|
|
self.inletVoltage = 0.0 # volts
|
2023-05-10 18:56:48 +00:00
|
|
|
self.accuVoltage = 0.0
|
|
|
|
self.lock_confirmed = False # Confirmation from hardware
|
|
|
|
self.cp_pwm = 0.0
|
|
|
|
self.soc_percent = 0.0
|
2023-06-20 10:43:03 +00:00
|
|
|
self.capacity = 0.0
|
|
|
|
self.accuMaxVoltage = 0.0
|
2023-05-10 18:56:48 +00:00
|
|
|
self.accuMaxCurrent = 0.0
|
|
|
|
self.contactor_confirmed = False # Confirmation from hardware
|
|
|
|
self.plugged_in = None # None means "not known yet"
|
2023-06-30 08:35:41 +00:00
|
|
|
self.lastReceptionTime = 0
|
2023-05-10 18:56:48 +00:00
|
|
|
|
2023-06-20 20:45:11 +00:00
|
|
|
self.maxChargerVoltage = 0
|
2023-06-20 12:12:21 +00:00
|
|
|
self.maxChargerCurrent = 10
|
|
|
|
self.chargerVoltage = 0
|
|
|
|
self.chargerCurrent = 0
|
|
|
|
|
2023-05-10 18:56:48 +00:00
|
|
|
self.logged_inlet_voltage = None
|
|
|
|
self.logged_dc_link_voltage = None
|
|
|
|
self.logged_cp_pwm = None
|
|
|
|
self.logged_max_charge_a = None
|
|
|
|
self.logged_soc_percent = None
|
|
|
|
self.logged_contactor_confirmed = None
|
|
|
|
self.logged_plugged_in = None
|
|
|
|
|
2022-12-19 13:48:23 +00:00
|
|
|
self.rxbuffer = ""
|
2023-05-10 18:56:48 +00:00
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
self.findSerialPort()
|
2023-04-15 18:35:51 +00:00
|
|
|
self.initPorts()
|
2022-12-10 16:08:51 +00:00
|
|
|
|
|
|
|
def resetSimulation(self):
|
|
|
|
self.simulatedInletVoltage = 0.0 # volts
|
|
|
|
self.simulatedSoc = 20.0 # percent
|
2024-07-23 19:00:58 +00:00
|
|
|
self.demoAuthenticationCounter = 0
|
2022-12-10 16:08:51 +00:00
|
|
|
|
|
|
|
def simulatePreCharge(self):
|
|
|
|
if (self.simulatedInletVoltage<230):
|
|
|
|
self.simulatedInletVoltage = self.simulatedInletVoltage + 1.0 # simulate increasing voltage during PreCharge
|
2022-12-06 17:35:17 +00:00
|
|
|
|
|
|
|
def close(self):
|
2023-04-15 18:35:51 +00:00
|
|
|
if (self.isSerialInterfaceOk):
|
2022-12-06 17:35:17 +00:00
|
|
|
self.ser.close()
|
2023-05-10 18:56:48 +00:00
|
|
|
|
|
|
|
def evaluateReceivedData_dieter(self, s):
|
2022-12-19 13:48:23 +00:00
|
|
|
self.rxbuffer += s
|
|
|
|
x=self.rxbuffer.find("A0=")
|
|
|
|
if (x>=0):
|
|
|
|
s = self.rxbuffer[x+3:x+7]
|
|
|
|
if (len(s)==4):
|
|
|
|
try:
|
2023-02-27 09:53:42 +00:00
|
|
|
self.inletVoltage = int(s) / 1024.0 * 1.08 * (6250) / (4.7+4.7)
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValue("analog_input_device")=="dieter"):
|
|
|
|
self.callbackShowStatus(format(self.inletVoltage,".1f"), "uInlet")
|
2022-12-19 13:48:23 +00:00
|
|
|
except:
|
|
|
|
# keep last known value, if nothing new valid was received.
|
|
|
|
pass
|
|
|
|
#self.addToTrace("RX data ok " + s)
|
|
|
|
self.rxbuffer = self.rxbuffer[x+3:] # consume the receive buffer entry
|
2023-02-01 18:20:35 +00:00
|
|
|
|
2023-05-10 18:56:48 +00:00
|
|
|
def evaluateReceivedData_celeron55device(self, s):
|
|
|
|
self.rxbuffer += s
|
|
|
|
while True:
|
|
|
|
x = self.rxbuffer.find("\n")
|
|
|
|
if x < 0:
|
|
|
|
break
|
|
|
|
line = self.rxbuffer[0:x].strip()
|
|
|
|
self.rxbuffer = self.rxbuffer[x+1:]
|
|
|
|
#self.addToTrace("Received line: \""+line+"\"")
|
|
|
|
if line.startswith("inlet_v="):
|
|
|
|
self.inletVoltage = int(line[8:])
|
|
|
|
if self.logged_inlet_voltage != self.inletVoltage:
|
|
|
|
self.logged_inlet_voltage = self.inletVoltage
|
|
|
|
self.addToTrace("<< inlet_voltage="+str(self.inletVoltage))
|
|
|
|
if self.callbackShowStatus:
|
|
|
|
self.callbackShowStatus(format(self.inletVoltage,".1f"), "uInlet")
|
|
|
|
elif line.startswith("dc_link_v="):
|
|
|
|
self.accuVoltage = int(line[10:])
|
|
|
|
if self.logged_dc_link_voltage != self.accuVoltage:
|
|
|
|
self.logged_dc_link_voltage = self.accuVoltage
|
|
|
|
self.addToTrace("<< dc_link_voltage="+str(self.accuVoltage))
|
|
|
|
elif line.startswith("cp_pwm="):
|
|
|
|
self.cp_pwm = int(line[7:])
|
|
|
|
if self.logged_cp_pwm != self.cp_pwm:
|
|
|
|
self.logged_cp_pwm = self.cp_pwm
|
|
|
|
self.addToTrace("<< cp_pwm="+str(self.cp_pwm))
|
|
|
|
elif line.startswith("cp_output_state="):
|
|
|
|
state = int(line[len("cp_output_state="):])
|
|
|
|
if bool(state) == ((self.outvalue & 1)!=0):
|
|
|
|
self.addToTrace("<< CP state confirmed")
|
|
|
|
else:
|
|
|
|
self.addToTrace("<< CP state MISMATCH")
|
|
|
|
elif line.startswith("ccs_contactor_wanted_closed="):
|
|
|
|
state = int(line[len("ccs_contactor_wanted_closed="):])
|
|
|
|
if bool(state) == ((self.outvalue & 2)!=0):
|
|
|
|
self.addToTrace("<< Contactor request confirmed")
|
|
|
|
else:
|
|
|
|
self.addToTrace("<< Contactor request MISMATCH")
|
|
|
|
elif line.startswith("max_charge_a="):
|
|
|
|
self.accuMaxCurrent = int(line[13:])
|
|
|
|
if self.logged_max_charge_a != self.accuMaxCurrent:
|
|
|
|
self.logged_max_charge_a = self.accuMaxCurrent
|
|
|
|
self.addToTrace("<< max_charge_a="+str(self.accuMaxCurrent))
|
|
|
|
elif line.startswith("soc_percent="):
|
|
|
|
self.soc_percent = int(line[12:])
|
|
|
|
if self.logged_soc_percent != self.soc_percent:
|
|
|
|
self.logged_soc_percent = self.soc_percent
|
|
|
|
self.addToTrace("<< soc_percent="+str(self.soc_percent))
|
|
|
|
elif line.startswith("contactor_confirmed="):
|
|
|
|
self.contactor_confirmed = bool(int(line[20:]))
|
|
|
|
if self.logged_contactor_confirmed != self.contactor_confirmed:
|
|
|
|
self.logged_contactor_confirmed = self.contactor_confirmed
|
|
|
|
self.addToTrace("<< contactor_confirmed="+str(self.contactor_confirmed))
|
|
|
|
elif line.startswith("plugged_in="):
|
|
|
|
self.plugged_in = bool(int(line[11:]))
|
|
|
|
if self.logged_plugged_in != self.plugged_in:
|
|
|
|
self.logged_plugged_in = self.plugged_in
|
|
|
|
self.addToTrace("<< plugged_in="+str(self.plugged_in))
|
|
|
|
else:
|
|
|
|
self.addToTrace("Received unknown line: \""+line+"\"")
|
|
|
|
|
2023-02-01 18:20:35 +00:00
|
|
|
def showOnDisplay(self, s1, s2, s3):
|
|
|
|
# show the given string s on the display which is connected to the serial port
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValueBool("display_via_serial") and self.isSerialInterfaceOk):
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
s = "disp0=" + s1 + "\n" + "disp1=" + s2 + "\n" + "disp2=" + s3 + "\n"
|
|
|
|
self.ser.write(bytes(s, "utf-8"))
|
|
|
|
else:
|
|
|
|
s = "lc" + s1 + "\n" + "lc" + s2 + "\n" + "lc" + s3 + "\n"
|
|
|
|
self.ser.write(bytes(s, "utf-8"))
|
2023-02-01 18:20:35 +00:00
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
def mainfunction(self):
|
2023-04-15 18:35:51 +00:00
|
|
|
if (getConfigValueBool("soc_simulation")):
|
|
|
|
if (self.simulatedSoc<100):
|
|
|
|
if ((self.outvalue & 2)!=0):
|
|
|
|
# while the relay is closed, simulate increasing SOC
|
2023-05-16 07:29:42 +00:00
|
|
|
deltaSoc = 0.5 # how fast the simulated SOC shall rise.
|
|
|
|
# Examples:
|
|
|
|
# 0.01 charging needs some minutes, good for light bulb tests
|
|
|
|
# 0.5 charging needs ~8s, good for automatic test case runs.
|
|
|
|
self.simulatedSoc = self.simulatedSoc + deltaSoc
|
2022-12-19 17:09:39 +00:00
|
|
|
|
2023-06-20 10:43:03 +00:00
|
|
|
if (getConfigValue("charge_parameter_backend")=="chademo"):
|
|
|
|
self.mainfunction_chademo()
|
|
|
|
|
2023-05-10 18:56:48 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="dieter"):
|
|
|
|
self.mainfunction_dieter()
|
|
|
|
|
|
|
|
if (getConfigValue("digital_output_device")=="celeron55device"):
|
|
|
|
self.mainfunction_celeron55device()
|
|
|
|
|
2024-11-04 14:16:08 +00:00
|
|
|
if (getConfigValue("digital_output_device")=="mqtt"):
|
|
|
|
self.mainfunction_mqtt()
|
|
|
|
|
2023-05-10 18:56:48 +00:00
|
|
|
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.
|
|
|
|
if (self.plugged_in is not None and self.plugged_in == False and
|
|
|
|
self.inletVoltage < 50):
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
def mainfunction_dieter(self):
|
2022-12-06 17:35:17 +00:00
|
|
|
self.loopcounter+=1
|
2023-04-15 18:35:51 +00:00
|
|
|
if (self.isSerialInterfaceOk):
|
2022-12-06 17:35:17 +00:00
|
|
|
if (self.loopcounter>15):
|
|
|
|
self.loopcounter=0
|
|
|
|
# self.ser.write(b'hello world\n')
|
2022-12-12 07:26:18 +00:00
|
|
|
s = "000" + str(self.outvalue)
|
|
|
|
self.ser.write(bytes("do"+s+"\n", "utf-8")) # set outputs of dieter, see https://github.com/uhi22/dieter
|
2022-12-06 17:35:17 +00:00
|
|
|
s = self.ser.read(100)
|
|
|
|
if (len(s)>0):
|
2022-12-19 13:48:23 +00:00
|
|
|
try:
|
|
|
|
s = str(s, 'utf-8').strip()
|
|
|
|
except:
|
|
|
|
s = "" # for the case we received corrupted data (not convertable as utf-8)
|
|
|
|
self.addToTrace(str(len(s)) + " bytes received: " + s)
|
2023-05-10 18:56:48 +00:00
|
|
|
self.evaluateReceivedData_dieter(s)
|
|
|
|
|
|
|
|
def mainfunction_celeron55device(self):
|
|
|
|
if (self.isSerialInterfaceOk):
|
|
|
|
s = self.ser.read(100)
|
|
|
|
if (len(s)>0):
|
|
|
|
try:
|
|
|
|
s = str(s, 'utf-8')
|
|
|
|
except:
|
|
|
|
s = "" # for the case we received corrupted data (not convertable as utf-8)
|
|
|
|
#self.addToTrace(str(len(s)) + " bytes received: " + s)
|
|
|
|
self.evaluateReceivedData_celeron55device(s)
|
2023-06-20 10:43:03 +00:00
|
|
|
|
|
|
|
def mainfunction_chademo(self):
|
|
|
|
message = self.canbus.recv(0)
|
|
|
|
|
|
|
|
if message:
|
|
|
|
if message.arbitration_id == 0x100:
|
|
|
|
vtg = (message.data[1] << 8) + message.data[0]
|
|
|
|
if self.accuVoltage != vtg:
|
|
|
|
self.addToTrace("CHAdeMO: Set battery voltage to %d V" % vtg)
|
|
|
|
self.accuVoltage = vtg
|
|
|
|
if self.capacity != message.data[6]:
|
|
|
|
self.addToTrace("CHAdeMO: Set capacity to %d" % message.data[6])
|
|
|
|
self.capacity = message.data[6]
|
|
|
|
|
2023-06-20 12:12:21 +00:00
|
|
|
msg = can.Message(arbitration_id=0x108, data=[ 0, self.maxChargerVoltage & 0xFF, self.maxChargerVoltage >> 8, self.maxChargerCurrent, 0, 0, 0, 0], is_extended_id=False)
|
2023-06-20 10:43:03 +00:00
|
|
|
self.canbus.send(msg)
|
2023-06-20 12:12:21 +00:00
|
|
|
#Report unspecified version 10, this makes our custom implementation send the momentary
|
2023-06-20 10:43:03 +00:00
|
|
|
#battery voltage in 0x100 bytes 0 and 1
|
2023-06-20 20:45:11 +00:00
|
|
|
status = 4 if self.maxChargerVoltage > 0 else 0 #report connector locked
|
2023-06-20 12:12:21 +00:00
|
|
|
msg = can.Message(arbitration_id=0x109, data=[ 10, self.chargerVoltage & 0xFF, self.chargerVoltage >> 8, self.chargerCurrent, 0, status, 0, 0], is_extended_id=False)
|
2023-06-20 10:43:03 +00:00
|
|
|
self.canbus.send(msg)
|
|
|
|
|
|
|
|
if message.arbitration_id == 0x102:
|
|
|
|
vtg = (message.data[2] << 8) + message.data[1]
|
|
|
|
if self.accuMaxVoltage != vtg:
|
|
|
|
self.addToTrace("CHAdeMO: Set target voltage to %d V" % vtg)
|
|
|
|
self.accuMaxVoltage = vtg
|
|
|
|
|
|
|
|
if self.accuMaxCurrent != message.data[3]:
|
|
|
|
self.addToTrace("CHAdeMO: Set current request to %d A" % message.data[3])
|
|
|
|
self.accuMaxCurrent = message.data[3]
|
2023-06-30 08:35:41 +00:00
|
|
|
self.lastReceptionTime = time()
|
2023-06-20 10:43:03 +00:00
|
|
|
|
|
|
|
if self.capacity > 0:
|
|
|
|
soc = message.data[6] / self.capacity * 100
|
|
|
|
if self.simulatedSoc != soc:
|
|
|
|
self.addToTrace("CHAdeMO: Set SoC to %d %%" % soc)
|
|
|
|
self.simulatedSoc = soc
|
2023-06-30 08:35:41 +00:00
|
|
|
#if nothing was received for over a second, time out
|
|
|
|
if self.lastReceptionTime < (time() - 1):
|
|
|
|
if self.accuMaxCurrent != 0:
|
|
|
|
self.addToTrace("CHAdeMO: No current limit update for over 1s, setting current to 0")
|
|
|
|
self.accuMaxCurrent = 0
|
2024-11-04 14:16:08 +00:00
|
|
|
|
|
|
|
def mainfunction_mqtt(self):
|
|
|
|
self.mqttclient.loop(timeout=0.1)
|
2022-12-06 17:35:17 +00:00
|
|
|
|
2024-11-04 14:16:08 +00:00
|
|
|
# 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)
|
|
|
|
|
2022-12-06 17:35:17 +00:00
|
|
|
def myPrintfunction(s):
|
|
|
|
print("myprint " + s)
|
|
|
|
|
2022-12-02 20:25:20 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
print("Testing hardwareInterface...")
|
2022-12-06 17:35:17 +00:00
|
|
|
hw = hardwareInterface(myPrintfunction)
|
2022-12-16 12:03:36 +00:00
|
|
|
for i in range(0, 350):
|
2022-12-06 17:35:17 +00:00
|
|
|
hw.mainfunction()
|
2023-02-01 18:20:35 +00:00
|
|
|
if (i==20):
|
|
|
|
hw.showOnDisplay("Hello", "A DEMO", "321.0V")
|
2024-11-04 14:16:08 +00:00
|
|
|
hw.setChargerParameters(500, 125)
|
|
|
|
hw.setChargerVoltageAndCurrent(360, 100)
|
2022-12-16 12:03:36 +00:00
|
|
|
if (i==50):
|
|
|
|
hw.setStateC()
|
2022-12-06 17:35:17 +00:00
|
|
|
if (i==100):
|
2022-12-16 12:03:36 +00:00
|
|
|
hw.setStateB()
|
|
|
|
if (i==150):
|
2022-12-06 17:35:17 +00:00
|
|
|
hw.setStateC()
|
2022-12-16 12:03:36 +00:00
|
|
|
hw.setPowerRelayOn()
|
2023-02-01 18:20:35 +00:00
|
|
|
hw.showOnDisplay("", "..middle..", "")
|
2022-12-06 17:35:17 +00:00
|
|
|
if (i==200):
|
|
|
|
hw.setStateB()
|
2022-12-16 12:03:36 +00:00
|
|
|
hw.setPowerRelayOff()
|
2022-12-06 17:35:17 +00:00
|
|
|
if (i==250):
|
2022-12-16 12:03:36 +00:00
|
|
|
hw.setRelay2On()
|
|
|
|
if (i==300):
|
|
|
|
hw.setRelay2Off()
|
2023-02-01 18:20:35 +00:00
|
|
|
if (i==320):
|
|
|
|
hw.showOnDisplay("This", "...is...", "DONE :-)")
|
2024-11-04 14:16:08 +00:00
|
|
|
sleep(0.01)
|
2022-12-06 17:35:17 +00:00
|
|
|
hw.close()
|
2023-01-24 16:52:23 +00:00
|
|
|
print("finished.")
|