From 658beda580d77d665c0379282097b8bf83b93ed7 Mon Sep 17 00:00:00 2001 From: uhi22 Date: Sat, 15 Apr 2023 20:35:51 +0200 Subject: [PATCH] feature: added ini file. Prepared GPIO for beaglebone. --- addressManager.py | 3 +- configmodule.py | 22 ++++++++++++++ fsmPev.py | 14 ++++----- hardwareInterface.py | 69 ++++++++++++++++++++++++++++++++++---------- pyPlc.ini | 55 +++++++++++++++++++++++++++++++++++ pyPlc.py | 5 ++++ 6 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 configmodule.py create mode 100644 pyPlc.ini diff --git a/addressManager.py b/addressManager.py index 1e84d91..7369a02 100644 --- a/addressManager.py +++ b/addressManager.py @@ -85,7 +85,8 @@ class addressManager(): # 2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 # 3: wlan0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 # print("This is a heading") - if (line.find(": eth")>0): + sFind = ": " + getConfigValue("eth_interface") # e.g. "eth0" + if (line.find(sFind)>0): # print("This is the heading for the ethernet.") blInTheEthernetChapter = 1 # we are in the ethernet chapter else: diff --git a/configmodule.py b/configmodule.py new file mode 100644 index 0000000..9f8f958 --- /dev/null +++ b/configmodule.py @@ -0,0 +1,22 @@ + + +# See https://docs.python.org/3/library/configparser.html + +import configparser + +config = configparser.ConfigParser() +config.read('pyPlc.ini') + +def getConfigValue(s): + return config['general'][s] + +def getConfigValueBool(s): + return config.getboolean('general', s) + +if __name__ == "__main__": + print("Testing configmodule...") + print(str(config.sections())) + print(config['general']['mode']) + for key in config['general']: + print(key + " has value " + config['general'][key]) + print(config.getboolean('general', 'display_via_serial')) \ No newline at end of file diff --git a/fsmPev.py b/fsmPev.py index 7a10454..143be06 100644 --- a/fsmPev.py +++ b/fsmPev.py @@ -10,8 +10,8 @@ from datetime import datetime from helpers import prettyHexMessage, compactHexMessage, combineValueAndMultiplier from exiConnector import * # for EXI data handling/converting import json +from configmodule import getConfigValue, getConfigValueBool -PARAM_U_DELTA_MAX_FOR_END_OF_PRECHARGE = 10 # volts between inlet and accu, to change from PreCharge to PowerDelivery stateNotYetInitialized = 0 stateConnecting = 1 @@ -393,7 +393,7 @@ class fsmPev(): except: self.addToTrace("ERROR: Could not decode the PreChargeResponse") self.addToTrace("PreChargeResponse received.") - if (self.USE_EVSEPRESENTVOLTAGE_FOR_PRECHARGE_END): + if (getConfigValueBool("use_evsepresentvoltage_for_precharge_end")): # We want to use the EVSEPresentVoltage, which was reported by the charger, as end-criteria for the precharging. s = "EVSEPresentVoltage " + str(u) + "V, " else: @@ -402,7 +402,7 @@ class fsmPev(): s = "U_Inlet " + str(u) + "V, " s= s + "U_Accu " + str(self.hardwareInterface.getAccuVoltage()) + "V" self.addToTrace(s) - if (abs(u-self.hardwareInterface.getAccuVoltage()) < PARAM_U_DELTA_MAX_FOR_END_OF_PRECHARGE): + if (abs(u-self.hardwareInterface.getAccuVoltage()) < float(getConfigValue("u_delta_max_for_end_of_precharge"))): self.addToTrace("Difference between accu voltage and inlet voltage is small. Sending PowerDeliveryReq.") self.publishStatus("PreCharge done") if (self.isLightBulbDemo): @@ -471,7 +471,7 @@ class fsmPev(): self.callbackShowStatus(format(u,".1f"), "EVSEPresentVoltage") except: self.addToTrace("ERROR: Could not decode the PreChargeResponse") - if (self.USE_PHYSICAL_INLET_VOLTAGE_DURING_CHARGELOOP): + if (getConfigValueBool("use_physical_inlet_voltage_during_chargeloop")): # Instead of using the voltage which is reported by the charger, use the physically measured. u = self.hardwareInterface.getInletVoltage() # as long as the accu is not full and no stop-demand from the user, we continue charging @@ -606,14 +606,10 @@ class fsmPev(): self.cyclesInState = 0 self.DelayCycles = 0 self.rxData = [] - self.isLightBulbDemo = True + self.isLightBulbDemo = getConfigValueBool("light_bulb_demo") self.isBulbOn = False self.cyclesLightBulbDelay = 0 self.isUserStopRequest = False - self.USE_EVSEPRESENTVOLTAGE_FOR_PRECHARGE_END = 1 # to configure, which criteria is used for end of PreCharge - self.USE_PHYSICAL_INLET_VOLTAGE_DURING_CHARGELOOP = 0 # to configure, whether to display the measured or charger-reported - # voltage during the charging loop - # we do NOT call the reInit, because we want to wait with the connection until external trigger comes def __del__(self): diff --git a/hardwareInterface.py b/hardwareInterface.py index b26cabe..2a7e172 100644 --- a/hardwareInterface.py +++ b/hardwareInterface.py @@ -9,8 +9,23 @@ import serial # the pyserial from serial.tools.list_ports import comports from time import sleep +from configmodule import getConfigValue, getConfigValueBool + +if (getConfigValue("digital_output_device")=="beaglebone"): + # In case we run on beaglebone, we want to use GPIO ports. + import Adafruit_BBIO.GPIO as GPIO class hardwareInterface(): + 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. + return False # non of the functionalities need a serial port. + def findSerialPort(self): ports = [] self.addToTrace('Available serial ports:') @@ -21,36 +36,49 @@ class hardwareInterface(): self.addToTrace('{:2}: {:20} {!r}'.format(n, port, desc)) ports.append(port) if (len(ports)<1): - self.addToTrace("ERROR: No serial ports found. No hardware interaction possible.") - self.ser = None - self.isInterfaceOk = False + 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 else: self.addToTrace("ok, we take the first port, " + ports[0]) try: self.ser = serial.Serial(ports[0], 19200, timeout=0) - self.isInterfaceOk = True + self.isSerialInterfaceOk = True except: self.addToTrace("ERROR: Could not open serial port.") self.ser = None - self.isInterfaceOk = False + self.isSerialInterfaceOk = False def addToTrace(self, s): self.callbackAddToTrace("[HARDWAREINTERFACE] " + s) def setStateB(self): self.addToTrace("Setting CP line into state B.") + if (getConfigValue("digital_output_device")=="beaglebone"): + GPIO.output("P8_18", GPIO.LOW) self.outvalue &= ~1 def setStateC(self): self.addToTrace("Setting CP line into state C.") + if (getConfigValue("digital_output_device")=="beaglebone"): + GPIO.output("P8_18", GPIO.HIGH) self.outvalue |= 1 def setPowerRelayOn(self): self.addToTrace("Switching PowerRelay ON.") + if (getConfigValue("digital_output_device")=="beaglebone"): + GPIO.output("P8_16", GPIO.HIGH) self.outvalue |= 2 def setPowerRelayOff(self): self.addToTrace("Switching PowerRelay OFF.") + if (getConfigValue("digital_output_device")=="beaglebone"): + GPIO.output("P8_16", GPIO.LOW) self.outvalue &= ~2 def setRelay2On(self): @@ -90,7 +118,12 @@ class hardwareInterface(): #todo: get SOC from the BMS self.callbackShowStatus(format(self.simulatedSoc,".1f"), "soc") return self.simulatedSoc - + + def initPorts(self): + if (getConfigValue("digital_output_device") == "beaglebone"): + # Port configuration according to https://github.com/jsphuebner/pyPLC/commit/475f7fe9f3a67da3d4bd9e6e16dfb668d0ddb1d6 + GPIO.setup("P8_16", GPIO.OUT) #output for port relays + GPIO.setup("P8_18", GPIO.OUT) #output for CP def __init__(self, callbackAddToTrace=None, callbackShowStatus=None): self.callbackAddToTrace = callbackAddToTrace @@ -101,6 +134,8 @@ class hardwareInterface(): self.inletVoltage = 0.0 # volts self.rxbuffer = "" self.findSerialPort() + self.initPorts() + def resetSimulation(self): self.simulatedInletVoltage = 0.0 # volts @@ -111,7 +146,7 @@ class hardwareInterface(): self.simulatedInletVoltage = self.simulatedInletVoltage + 1.0 # simulate increasing voltage during PreCharge def close(self): - if (self.isInterfaceOk): + if (self.isSerialInterfaceOk): self.ser.close() def evaluateReceivedData(self, s): @@ -122,8 +157,8 @@ class hardwareInterface(): if (len(s)==4): try: self.inletVoltage = int(s) / 1024.0 * 1.08 * (6250) / (4.7+4.7) - - self.callbackShowStatus(format(self.inletVoltage,".1f"), "uInlet") + if (getConfigValue("analog_input_device")=="dieter"): + self.callbackShowStatus(format(self.inletVoltage,".1f"), "uInlet") except: # keep last known value, if nothing new valid was received. pass @@ -132,18 +167,20 @@ class hardwareInterface(): def showOnDisplay(self, s1, s2, s3): # show the given string s on the display which is connected to the serial port - s = "lc" + s1 + "\n" + "lc" + s2 + "\n" + "lc" + s3 + "\n" - self.ser.write(bytes(s, "utf-8")) + if (getConfigValueBool("display_via_serial") and self.isSerialInterfaceOk): + s = "lc" + s1 + "\n" + "lc" + s2 + "\n" + "lc" + s3 + "\n" + self.ser.write(bytes(s, "utf-8")) def mainfunction(self): - if (self.simulatedSoc<100): - if ((self.outvalue & 2)!=0): - # while the relay is closed, simulate increasing SOC - self.simulatedSoc = self.simulatedSoc + 0.01 + if (getConfigValueBool("soc_simulation")): + if (self.simulatedSoc<100): + if ((self.outvalue & 2)!=0): + # while the relay is closed, simulate increasing SOC + self.simulatedSoc = self.simulatedSoc + 0.01 self.loopcounter+=1 - if (self.isInterfaceOk): + if (self.isSerialInterfaceOk): if (self.loopcounter>15): self.loopcounter=0 # self.ser.write(b'hello world\n') diff --git a/pyPlc.ini b/pyPlc.ini new file mode 100644 index 0000000..e996739 --- /dev/null +++ b/pyPlc.ini @@ -0,0 +1,55 @@ + +[general] +# mode can be either PevMode to use as car, or EvseMode to use as charger +mode = PevMode + +# Simulation without modem +# For development purposes, make it possible to run two instances (pev and evse) on one machine, without +# a modem connected. This feature is not tested since a long time, most likely does not work as intended. +# todo: replace isSimulationMode by this setting +is_simulation_without_modems = false + +# The Ethernet interface. Usually eth0 on Raspberry. +# Todo: This setting is considered only on linux. Find a clean solution for windows is to be done. +eth_interface = eth0 + +# If a display is connected via serial line, e.g. an WIFI-KIT-32 running the software from https://github.com/uhi22/SerialToOLED +display_via_serial = yes + +# LightBulbDemo turns on the relay with a short delay in the charging loop, to stabilize the voltage +# before the resistive load is connected. +light_bulb_demo = yes + +# SOC simulation. +# In PevMode, simulate a rising SOC while charging. +soc_simulation = 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 +# beaglebone: GPIO pins of the beagleBone, as used in https://github.com/jsphuebner/pyPLC/tree/beaglebone +digital_output_device = dieter +#digital_output_device = beaglebone + +# Device to read the physically measured inlet voltage in PevMode +# Either the high-voltage dieter from https://github.com/uhi22/dieter, which is connected on serial port. +# Or "none", if no measurement is intended. +#analog_input_device = dieter +analog_input_device = none + +# Criteria for ending the PreCharge phase in PevMode +# Possible options: +# yes: use the EVSEPresentVoltage which is reported by the charger, to decide the end of the precharge +# no: use the physically measured inlet voltage to decide the end of the precharge +use_evsepresentvoltage_for_precharge_end = yes + +# Use the physically measured inlet voltage to show on display during the charge loop. +# If false, we are showing the EVSEPresentVoltage which is reported by the charger. +use_physical_inlet_voltage_during_chargeloop = no + +# Voltage threshold for the end-of-precharge decision +# This is the maximum difference voltage between the charger voltage and the accu voltage. If the actual voltage +# difference is lower than this threshold, we will close the power relay, to connect the accu to the charger. +# Unit: volt +u_delta_max_for_end_of_precharge = 10 + diff --git a/pyPlc.py b/pyPlc.py index 266efbb..1a7f117 100644 --- a/pyPlc.py +++ b/pyPlc.py @@ -11,6 +11,7 @@ import time import pyPlcWorker from pyPlcModes import * import sys # for argv +from configmodule import getConfigValue, getConfigValueBool startTime_ms = round(time.time()*1000) @@ -60,6 +61,10 @@ def cbShowStatus(s, selection=""): root.update() myMode = C_LISTEN_MODE +if (getConfigValue("mode")=="PevMode"): + myMode = C_PEV_MODE +if (getConfigValue("mode")=="EvseMode"): + myMode = C_EVSE_MODE if (len(sys.argv) > 1): if (sys.argv[1] == "P"): myMode = C_PEV_MODE