# V1.0 Maintain Ah on restart # @reboot sleep 20 && sudo python /var/www/html/BatteryMon_3.py & # 24.6.23 Voltage measurement for Controller 2 # IMPORT DEFINITIONS #************************************************************************************** import sys import os # for CSV file existence check import time import serial import datetime # for time management import numpy as np # !!!not sure if needed import matplotlib # Styles matplotlib.use('Agg') #matplotlib.use('TkAgg') # !!! backend should speed up import pylab import matplotlib.pyplot as plt #graphs library #import random as rnd plt.style.use('ggplot') #plt.style.use(['dark_background', 'presentation']) import matplotlib.dates as mdates #formatting the graph #HOX 10.6.2024 removed temporarily from drawnow import * import atexit from csv import writer # for CSV file control import csv # HW RELATED DEFINITIONS #************************************************************************************** global VoltageFactor #VoltageFactor = 0.0178 #when 12V zener is bypassed VoltageFactor = 0.0189 #when 12V zener not bypassed #VoltageFactor = 0.0195 global IdcFactor # DC virran mittaus IdcFactor = 0.715/20 # = 0,03575 SetFastSampling = 1 # GRAPHS DEFINITIONS #************************************************************************************** def OwnGraph1(): plt.figure(1) fig = plt.figure(1) fig.set_figwidth(5) #Picture sizing fig.set_figheight(8) #SoC plt.subplot(311) plt.ylim(0, AhValue) plt.title("Varaustila %1.1f Ah" %Energy2, fontweight='bold', fontsize=12) plt.plot(x, values3, 'g') plt.grid(linewidth=1) plt.axhspan(60, 300, facecolor="green", alpha=0.1) plt.axhspan(30, 60, facecolor="yellow", alpha=0.1) plt.axhspan(0, 30, facecolor="red", alpha=0.1) #plt.suptitle("xxxxx", fontsize=12) #Current plt.subplot(312) plt.title("Virta %1.1f A, (+lataus, -purku)" %Current, fontweight='bold', fontsize=12) if ((max(values2) < 10) & (min(values2) > -10)): plt.ylim(-10, 10) plt.plot(x, values2, 'r') #Voltage plt.subplot(313) plt.title("Jannite %1.1f V" %Voltage, fontweight='bold', fontsize=12) plt.ylim(10, 16) plt.plot(x, values, 'b') plt.grid(linewidth=1) plt.axhspan(14.8, 16, facecolor="red", alpha=0.1) plt.axhspan(11.5, 14.8, facecolor="green", alpha=0.1) plt.axhspan(10, 11.5, facecolor="red", alpha=0.1) fig.tight_layout() plt.gcf().autofmt_xdate() #plt.show() # FUNCTIONS #************************************************************************************** def readArduino (Energy): global EnergyIn global EnergyOut global Energy2 # This is new energy value, Energy is the old value given via function call global Time0 # global Voltage global VoltageFactor global Current data = serialArduino.readline()[:-2] #the last bit gets rid of the new-line chars b = data koe = b.split(b" ")[0] if koe == b"Graph1": c = b.split(b" ")[1] e = b.split(b" ")[2] Voltage1 = int(c) #Proto1 Voltage = round(Voltage1*0.0189,2) #12V corresponds 2,99V in ADC and as 635 binary with Vref 4.5V #Voltage = round(Voltage1*0.0198,2) Voltage = round(Voltage1*VoltageFactor,2) Current1 = int(e) Current = round(IOffset+ICal*Current1*IdcFactor,3) #50 (proto1) or 20 (proto2) is the multiplier in Arduino # remove 1 bit noise if (Current1*Current1) == 1: Current = 0 Time = (datetime.datetime.now()-Time0).total_seconds() #print (Time, Current) Energy2 = round(Energy+Current*Time/3600,2) #Ensure that energy is not negative or above battery capacity if Energy2 < 0.05: Energy2 = 0.1 if Energy2 > (AhValue - 2): Energy2 = (AhValue - 2) Time0 = datetime.datetime.now() EnergyChange = round(Current*Time/3600,3) if EnergyChange > 0: EnergyIn = round(EnergyIn+EnergyChange,3) else: EnergyOut = round(EnergyOut-EnergyChange,3) # HOX low voltage power cut off removed 15.11.2022 #if Voltage <= 11.5: #sendToArduino("Load", "1") #State = "LowVolt" # print ("Idendified Arduino value:",koe,Voltage,Current,Energy2) timestamp = datetime.datetime.now().strftime('%Y:%d:%m-%H:%M:%S') updateStatusfile(Voltage,Current,Energy2,EnergyIn,EnergyOut, timestamp) updateGraph2(Voltage,Current,Energy2) updateLogfile(Voltage,Current,Energy2) return Energy2 def updateGraph2 (Voltage,Current,Energy): global values, values2, values3, x2 #startTime = time.time() try: if Voltage <= 10240000: if Voltage >= 0: # Step values values.append(Voltage) # Add new value in list, uses values.append method values.pop(0) # Remove first value from the list values2.append(Current) # Add new value in list, uses values.append method values2.pop(0) # Remove first value from the list values3.append(Energy) values3.pop(0) # Step time x2 = datetime.datetime.now() x.append(x2) x.pop(0) drawnow(OwnGraph1) pylab.savefig("/var/www/html/Graafi6.png", bbox_inches="tight") return Voltage, Current, Energy; else: print("Invalid! negative number") else: print("Invalid! too large") except ValueError: print (Voltage, Current, Energy, x2) print("Invalid! cannot cast") def sendToArduino( Label, Data ): #Add message header/terminator as defined in Arduino SW Task = "<" + Label + " " + Data + ">" print ("Sent command:",Task) serialArduino.write(Task.encode()) return; def initControlData (): global ControlData0 with open(Controlfile, 'r') as f: reader = csv.reader(f) ControlData0 = list(reader) lines = len(ControlData0) print ("Control data lines from file ",lines) for ind in range(0,lines): #print (ind,1) ControlData0[ind][1] = "xx" #Read data from CSV file cell, compare to previous and send delta to Arduino def readControlfile (): global ControlData0 global Energy2 global allowedMin global SetFastSampling # This reads the cells in csv file into an array with open(Controlfile, 'r') as f: reader = csv.reader(f) ControlData = list(reader) #compare firs full arrays, if delta compare line by line if ControlData0 != ControlData: C = len(ControlData0) #print ("Conrol data length",C) for ind in range(0, C): if ControlData0[ind] != ControlData[ind]: Label = ControlData[ind][0] Data = ControlData[ind][1] if Label == "Energy": Eluku = float(Data) if Eluku >= 2: Energy2 = Eluku #print ("komento", Label, Data) if Label == "AhValue": global AhValue AhValue = float(Data) # CHANGES THAT TRIGGER FAST SAMPLING if Label == "Load": SetFastSampling = 1 if Label == "Charge": SetFastSampling = 1 if Label == "HiPower": SetFastSampling = 1 # CURRENT MEASUREMENT CALIBRATION if Label == "IOffset": global IOffset IOffset = float(Data) if Label == "ICal": global ICal ICal = float(Data) # CHARGER AUTOSTART PARAMETERS if Label == "VStart": global VStart VStart = float(Data) if Label == "AhStart": global AhStart AhStart = float(Data) if Label == "VStop": global VStop VStop = float(Data) if Label == "AhStop": global AhStop AhStop = float(Data) if Label == "InvTrigger": global InvTrigger InvTrigger = float(Data) elif Label == "Run": if Data == "Stop": updateGraphFile() print("Stopped by Run stop command") sys.exit() elif Label == "VMin": t = float(Data) if t != allowedMin: #print('new t: {}'.format(t)) allowedMin = t #print("Print to Arduino due Control data change") sendToArduino(Label, Data) ControlData0 = ControlData return "NA" return "NA" def createLogfile (): fname = LogFile if os.path.isfile(fname): print("file does exist at this time") else: print("no such file exists at this time") #with open("Tiedosto.csv", "w", newline="") as f: with open(LogFile, "w") as f: writer = csv.writer(f) writer.writerow(['Volt','Curr','Ah']) writer.writerow([0,0,0,0]) writer.writerow([0,0,0,0]) def updateStatusfile (v,c,e,eIn,eOut, timestamp): # File opened with w for adding data with open(StatusFile, "w") as f: writer = csv.writer(f) writer.writerow(["Voltage/V",v]) writer.writerow(["Current/A",c]) writer.writerow(["Energy/Ah",e]) writer.writerow(["EnergyIn",eIn]) writer.writerow(["EnergyOut",eOut]) writer.writerow(["Timestamp",timestamp]) #This reads the last line -1 and picks 3rd column as oled energy value def readStatusfile (): global EnergyIn global EnergyOut global Energy2 EnergyOld = 0 with open(StatusFile, 'r') as f: reader = csv.reader(f) ControlData = list(reader) C = len(ControlData) for ind in range(0, C): if ControlData[ind][0] == "Energy/Ah": ener = ControlData[ind][1] EnergyOld = float(ener) Energy2 = float(ener) elif ControlData[ind][0] == "EnergyIn": ener = ControlData[ind][1] EnergyIn = float(ener) elif ControlData[ind][0] == "EnergyOut": ener = ControlData[ind][1] EnergyOut = float(ener) return EnergyOld def updateLogfile (v,c,ah): with open(LogFile, "a") as f: writer = csv.writer(f) x2 = datetime.datetime.now().strftime('%d:%m-%H:%M:%S') writer.writerow([v,c,ah,x2]) def AutoStart (Energy): global Energy2 global Voltage global Current global State if State == "Idle": if Energy2 < AhStart or Voltage <= VStart : print("Start condition ",Energy2," ", AhStart," ",Voltage," ",VStart) startCharge() if State == "Charge": if Energy2 > AhStop or Voltage > VStop: print("Stop condition",Energy2," ", AhStart," ",Voltage," ",VStart) stopCharge() return (1) def startCharge (): global State global shortTimerOn State = "Charge" readControlfile() updateControlData0("Charge",1) updateControlData0("GraphT",5) updateControlfile() return def stopCharge (): global State State = "Idle" readControlfile() updateControlData0("Charge",0) updateControlData0("GraphT",5) updateControlfile() return def updateControlData0 (Labelx,Datax): global ControlData0 C = len(ControlData0) for ind in range(0, C): Label = ControlData0[ind][0] Data = ControlData0[ind][1] if Label == Labelx: ControlData0[ind][1] = Datax print ("New control data:", Labelx, Datax) #Hox 12.11.2022 #This function writes ControlData0 back into file.This can be used to change controls from Python! def updateControlfile (): global ControlData0 # File opened with w for adding data with open(Controlfile, "w") as f: writer = csv.writer(f) lines = len(ControlData0) for ind in range(0,lines): writer.writerow([ControlData0[ind][0],ControlData0[ind][1]]) print ("Controlfile updated") def updateGraphFile (): global values, values2, values3, x with open(GraphFile, "w") as f: writer = csv.writer(f) for ind in range(0,XLength): writer.writerow([x[ind],values[ind],values2[ind],values3[ind]]) def readGraphFile (): # File opened with w for adding data global values, values2, values3, x with open(GraphFile, "r") as f: reader = csv.reader(f) GraphData = list(reader) # Check that oldest time stamp is less than 12 hours and data is then relevant Time1 = datetime.datetime.strptime(GraphData[1][0], '%Y-%m-%d %H:%M:%S.%f') Timeperiod = ((datetime.datetime.now() - Time1)/3600).seconds if Timeperiod > 24: return for ind in range(0,XLength): time = GraphData[ind][0] x[ind] = datetime.datetime.strptime(time, '%Y-%m-%d %H:%M:%S.%f') values[ind] = float(GraphData[ind][1]) values2[ind] = float(GraphData[ind][2]) values3[ind] = float(GraphData[ind][3]) print ("Max value", min(values3)) print ("GraphFile read from file") print ("Time from file",x[1]) print ("Values from file",values[XLength-1]) # MAIN #************************************************************************************** if __name__ == '__main__': # INTERFACES #************************************************************************************** # Define and open serial port, baud must be same as in Arduino #port = "/dev/ttyUSB0" #vanhalla Arduino Unolla port = "/dev/ttyACM0" # Arduino Every baud = 9600 serialArduino = serial.Serial(port, baud, timeout=1) time.sleep(1) Controlfile = '/var/www/html/Control.csv' Controlfile2 = '/var/www/html/Control2.csv' StatusFile = '/var/www/html/Status.csv' LogFile = '/var/www/html/Tiedosto.csv' GraphFile = '/var/www/html/GraphFile.csv' # TABLE AND ARRAYS DEFINITIONS #************************************************************************************** # Define table to be used for graph values = [] values2 = [] values3 = [] x = [] plt.ion() XLength=600 #pre-load dummy data into table for i in range(0,XLength): # original value was 26 values.append(0) # Add new value in list, uses values.append method values2.append(0) values3.append(0) x = [datetime.datetime.now() + datetime.timedelta(seconds=i-XLength) for i in range(0,XLength)] #updateGraphFile() readGraphFile() OldCommand="" createLogfile() #clear serial buffer to remove junk and noise serialArduino.flushInput() serialArduino.flushOutput() initControlData() #initialise array containing Command data from PHP # VARIABLE DEFINITIONS #************************************************************************************** Current = 0.0 Voltage = 12 Energy2 = 0.0 Energy = 0.0 EnergyIn = 0.0 EnergyOut = 0.0 Time0 = datetime.datetime.now() # Time when new set of samples from Arduino start AhValue = 100 allowedMin = 10 IOffset = 0 ICal = 0 State = "Idle" VStart = 11.0 # voltage to start charger AhStart = 30 # Ah to start charger VStop = 15.0 # voltage to stop charger AhStop = 280 # Ah to start charger shortTimerOn = 0 sec5Timer = 20 graphTimer = 600 readStatusfile() readControlfile() startTime = time.time() graphTime = time.time() # MAIN LOOP while True: if SetFastSampling == 1: updateControlData0("GraphT",5) updateControlfile() SetFastSampling = 0 readControlfile() #startTime = time.time() readArduino(Energy2) #reads Arduino and updates Graphs #print("Loop took {0}".format(time.clock()-startTime)) if float(ControlData0[17][1]) == 1: # Autostart activated AutoStart(200) # Counter to remove 5 sec interval setting after sec5Timer if shortTimerOn == 0: if float(ControlData0[0][1]) == 5: shortTimerOn = 1 startTime = time.time() if shortTimerOn == 1: timeSec = (time.time() - startTime) if (timeSec > sec5Timer): shortTimerOn = 0 updateControlData0("GraphT",120) updateControlfile() sendToArduino("GraphT","120") timeSec = (time.time() - graphTime) if (timeSec > graphTimer): updateGraphFile() print ("Graph data stored") graphTime = time.time() endTime = time.time() print("Loop took {0}".format(endTime-startTime))