From a83f5a4ce0f07fac6478f4808bfc14a7f33641fe Mon Sep 17 00:00:00 2001 From: britkat1980 <69121158+britkat1980@users.noreply.github.com> Date: Fri, 3 Nov 2023 21:42:34 +0000 Subject: [PATCH] prep for 3.0.0 --- .dayRate | 0 .gitignore | 2 +- Dockerfile | 25 +- GivEnergy-Smart-Home-Display-givtcp/js/app.js | 6 +- GivTCP/GivLUT.py | 2 +- GivTCP/HA_Discovery.py | 18 +- GivTCP/REST.py | 44 ++- GivTCP/evc.py | 49 ++- GivTCP/palm_settings.py | 2 +- GivTCP/palm_utils.py | 32 +- GivTCP/read.py | 4 +- GivTCP/write.py | 1 + README.md | 10 +- battery control dashboard raw config.txt | 312 ------------------ buildx.bat | 2 +- givenergy_modbus/client.py | 23 +- givenergy_modbus/model/battery.py | 91 +++++ givtcp-vuejs/package.json | 2 +- givtcp-vuejs/src/components/FormCard.vue | 17 + givtcp-vuejs/src/components/StepButton.vue | 34 +- givtcp-vuejs/src/stores/counter.js | 34 ++ index.html | 1 + ingress.conf | 28 ++ startup.py | 2 + startup_3.py | 144 +++++--- updatelib.bat | 1 - 26 files changed, 438 insertions(+), 448 deletions(-) delete mode 100644 .dayRate delete mode 100644 battery control dashboard raw config.txt create mode 100644 index.html create mode 100644 ingress.conf delete mode 100644 updatelib.bat diff --git a/.dayRate b/.dayRate deleted file mode 100644 index e69de29b..00000000 diff --git a/.gitignore b/.gitignore index 062fe681..dc315b39 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,8 @@ GivTCP/testdata.json .gitattributes GivTCP/settings.py MQTT_TEST.py -REST_TEST.py test.py +*.pkl settings.py __pycache__ givenergy_modbus/model/__pycache__/inverter.cpython-310.pyc diff --git a/Dockerfile b/Dockerfile index 9cd499b2..27ebc66a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # set base image (host OS) FROM python:rc-alpine -RUN apk --no-cache add mosquitto +RUN apk add mosquitto RUN apk add curl RUN apk add --update npm RUN npm install -g serve @@ -11,31 +11,34 @@ RUN apk add musl-utils RUN apk add xsel RUN apk add redis RUN apk add npm - +RUN apk add nginx && mkdir -p /run/nginx # set the working directory in the container WORKDIR /app # copy the dependencies file to the working directory COPY requirements.txt . - -# install dependencies RUN pip install -r requirements.txt +COPY givtcp-vuejs/package.json ./config_frontend/package.json + +RUN cd /app/config_frontend && npm install +COPY givtcp-vuejs ./config_frontend +RUN cd /app/config_frontend && npm run build + +COPY ingress.conf /etc/nginx/http.d/ +RUN rm /etc/nginx/http.d/default.conf + # copy the content of the local src directory to the working directory COPY GivTCP/ ./GivTCP COPY GivEnergy-Smart-Home-Display-givtcp/ ./GivEnergy-Smart-Home-Display-givtcp COPY givenergy_modbus/ /usr/local/lib/python3.10/site-packages/givenergy_modbus COPY startup.py startup.py -#COPY startup_3.py startup_3.py +COPY startup_3.py startup_3.py COPY redis.conf redis.conf COPY settings.json /app/settings.json - -#COPY givtcp-vuejs/package.json ./config_frontend/package.json -#RUN cd /app/config_frontend && npm install -#COPY givtcp-vuejs ./config_frontend -#RUN cd /app/config_frontend && npm run build +COPY index.html /app/index.html ENV NUMINVERTORS=1 ENV INVERTOR_IP_1="" @@ -106,6 +109,6 @@ ENV EVC_IP_ADDRESS="" ENV EVC_SELF_RUN_TIMER=5 -EXPOSE 6345 1883 3000 5173 6379 9181 +EXPOSE 1883 6379 8099 CMD ["python3", "/app/startup.py"] diff --git a/GivEnergy-Smart-Home-Display-givtcp/js/app.js b/GivEnergy-Smart-Home-Display-givtcp/js/app.js index fd5e3b68..14ab5c2a 100644 --- a/GivEnergy-Smart-Home-Display-givtcp/js/app.js +++ b/GivEnergy-Smart-Home-Display-givtcp/js/app.js @@ -11,14 +11,14 @@ class App { me.givTcpHostname = null; me.solarRate = null; me.exportRate = null; - + var host = window.location.protocol + "//" + window.location.hostname+":6345" // Fetch the settings from `app.json` fetch("./app.json") .then(response => { return response.json(); }) .then(data => { - me.givTcpHostname = data.givTcpHostname; + me.givTcpHostname = host; me.solarRate = data.solarRate; me.exportRate = data.exportRate; @@ -47,7 +47,7 @@ class App { fetchData() { const me = this; - fetch(`http://${me.givTcpHostname}/readData`, { + fetch(`${me.givTcpHostname}/readData`, { mode: 'cors', headers: { 'Access-Control-Allow-Origin': 'localhost:63342' diff --git a/GivTCP/GivLUT.py b/GivTCP/GivLUT.py index 7d8fc7bf..5cf2c920 100644 --- a/GivTCP/GivLUT.py +++ b/GivTCP/GivLUT.py @@ -326,7 +326,7 @@ class GivLUT: "Charge_End_Time":GEType("sensor","timestamp","","","",False,False,False), "System_Time":GEType("sensor","timestamp","","","",False,False,False), "Charging_Mode":GEType("select","","setChargingMode","","",False,False,False), - "Import_Cap":GEType("switch","","setImportCap","","",False,False,False), + "Import_Cap":GEType("number","","setImportCap","","",False,False,False), "Max_Session_Energy":GEType("number","","setMaxSessionEnergy","","",False,False,False), } time_slots=[ diff --git a/GivTCP/HA_Discovery.py b/GivTCP/HA_Discovery.py index cb5a3256..6444f8c8 100644 --- a/GivTCP/HA_Discovery.py +++ b/GivTCP/HA_Discovery.py @@ -209,26 +209,32 @@ def create_device_payload(topic,SN): tempObj['options']=options elif GivLUT.entity_type[str(topic).split("/")[-1]].devType=="number": # If its a rate then change to Watts - if "SOC" in str(topic).lower(): + item=str(topic).split("/")[-1] + if "soc" in str(item).lower(): tempObj['unit_of_meas']="%" tempObj['min']=4 tempObj['max']=100 tempObj['mode']="slider" - elif "limit" in str(topic).lower(): #if EVC current + elif "limit" in str(item).lower(): #if EVC current tempObj['unit_of_meas']="A" tempObj['min']=6 tempObj['max']=32 tempObj['mode']="slider" - elif "charge" in str(topic).lower(): - tempObj['unit_of_meas']="W" + elif "cap" in str(item).lower(): #if EVC current + tempObj['unit_of_meas']="A" tempObj['min']=0 - tempObj['max']=HAMQTT.getinvbatmax() + tempObj['max']=100 tempObj['mode']="slider" - elif "max_session_energy" in str(topic).lower(): + elif "energy" in str(item).lower(): tempObj['unit_of_meas']="kWh" tempObj['min']=0 tempObj['max']=100 tempObj['mode']="slider" + elif "charge" in str(item).lower(): + tempObj['unit_of_meas']="W" + tempObj['min']=0 + tempObj['max']=HAMQTT.getinvbatmax() + tempObj['mode']="slider" else: tempObj['unit_of_meas']="%" elif GivLUT.entity_type[str(topic).split("/")[-1]].devType=="button": diff --git a/GivTCP/REST.py b/GivTCP/REST.py index 7a84813c..055f1346 100644 --- a/GivTCP/REST.py +++ b/GivTCP/REST.py @@ -5,6 +5,7 @@ from flask_cors import CORS import read as rd #grab passthrough functions from main read file import write as wr #grab passthrough functions from main write file +import evc as evc import config_dash as cfdash from GivLUT import GivQueue, GivLUT import os @@ -21,11 +22,11 @@ @giv_api.route('/', methods=['GET', 'POST']) def root(): - return send_from_directory('/app/config_frontend/dist/', 'index.html') + return send_from_directory('/app/config_frontend/dist', 'index.html') @giv_api.route('/config') def get_config_page(): - return send_from_directory('/app/config_frontend/dist/', 'index.html') + return send_from_directory('/app/config_frontend/dist', 'index.html') # if request.method=="GET": # return cfdash.get_config() # if request.method=="POST": @@ -42,7 +43,7 @@ def reboot(): @giv_api.route('/restart', methods=['GET']) def restart(): - return wr.rebootAddon + return wr.rebootAddon() #Publish last cached Invertor Data @giv_api.route('/readData', methods=['GET']) @@ -209,5 +210,42 @@ def editFileData(): file.close() return data +@giv_api.route('/setImportCap', methods=['POST']) +def impCap(): + payload = request.get_json(silent=True, force=True) + return evc.setImportCap(payload) + +@giv_api.route('/setCurrentLimit', methods=['POST']) +def currLimit(): + payload = request.get_json(silent=True, force=True) + return evc.setCurrentLimit(payload) + +@giv_api.route('/setChargeControl', methods=['POST']) +def chrgeControl(): + payload = request.get_json(silent=True, force=True) + return evc.setChargeControl(payload) + +@giv_api.route('/setChargeMode', methods=['POST']) +def chrgMode(): + payload = request.get_json(silent=True, force=True) + return evc.setChargeMode(payload) + +@giv_api.route('/setChargingMode', methods=['POST']) +def chrgingMode(): + payload = request.get_json(silent=True, force=True) + return evc.setChargingMode(payload) + +@giv_api.route('/setMaxSessionEnergy', methods=['POST']) +def maxSession(): + payload = request.get_json(silent=True, force=True) + return evc.setMaxSessionEnergy(payload) + +@giv_api.route('/getEVCCache', methods=['GET']) +def gtEVCChce(): + payload = request.get_json(silent=True, force=True) + return evc.getEVCCache() + + + if __name__ == "__main__": giv_api.run() diff --git a/GivTCP/evc.py b/GivTCP/evc.py index 1f5d2bb1..9366b98e 100644 --- a/GivTCP/evc.py +++ b/GivTCP/evc.py @@ -118,8 +118,9 @@ def getEVC(): with open(EVCLut.regcache, 'rb') as inp: evcRegCache= pickle.load(inp) - if output['Charge_Session_Energy']==0: + if output['Charge_Session_Energy']==0 and not output['Charging_State']=='Charging': #If charging has finished, then hold the previous charge session energy output['Charge_Session_Energy']=evcRegCache['Charger']['Charge_Session_Energy'] + startTime=datetime.datetime.now().replace(hour=regs[74],minute=regs[75],second=regs[76],microsecond=0,tzinfo=datetime.timezone.utc).isoformat() endtime=datetime.datetime.now().replace(hour=regs[82],minute=regs[83],second=regs[84],microsecond=0,tzinfo=datetime.timezone.utc).isoformat() @@ -134,7 +135,7 @@ def getEVC(): output['Charge_End_Time']=datetime.datetime.now().replace(hour=regs[82],minute=regs[83],second=regs[84],microsecond=0,tzinfo=datetime.timezone.utc).isoformat() if not "Import_Cap" in evcRegCache['Charger']: - output['Import_Cap']="disable" + output['Import_Cap']=0 else: output['Import_Cap']=evcRegCache['Charger']['Import_Cap'] @@ -142,6 +143,7 @@ def getEVC(): output['Charging_Mode']="Grid" else: output['Charging_Mode']=evcRegCache['Charger']['Charging_Mode'] + if not 'Max_Session_Energy' in evcRegCache['Charger']: output['Max_Session_Energy']=0 else: @@ -153,7 +155,7 @@ def getEVC(): output['Charge_End_Time']=ts.replace(tzinfo=datetime.timezone.utc).isoformat() ts=datetime.datetime.now().replace(hour=regs[74],minute=regs[75],second=regs[76],microsecond=0) output['Charge_Start_Time']=ts.replace(tzinfo=datetime.timezone.utc).isoformat() - output['Import_Cap']="disable" + output['Import_Cap']=0 output['Charging_Mode']="Grid" output['Max_Session_Energy']=0 if regs[0]==4: @@ -171,6 +173,8 @@ def getEVC(): output['Charge_Session_Duration']=str(td) multi_output['Charger']=output # Save new data to Pickle + + with cacheLock: with open(EVCLut.regcache, 'wb') as outp: pickle.dump(multi_output, outp, pickle.HIGHEST_PROTOCOL) @@ -248,7 +252,7 @@ def updateFirstRun(SN): logger.error("Could not access settings file to update EVC Serial Number") break else: - logger.debug("Settings availble evc") + logger.debug("Settings available evc") #Create setting lockfile open(".settings_lockfile",'a').close() @@ -302,6 +306,14 @@ def iterate_dict(array): # Create a publish safe version of the output (c safeoutput[p_load] = output return(safeoutput) +def getEVCCache(): + if exists(EVCLut.regcache): + with open(EVCLut.regcache, 'rb') as inp: + evcRegCache= pickle.load(inp) + return json.dumps(evcRegCache) + else: + return json.dumps("No EVC data found") + def setChargeMode(mode): if mode=="enable": val=0 @@ -311,7 +323,7 @@ def setChargeMode(mode): logger.error("Invalid control mode called: "+str(mode)) return logger.info("Setting Charge mode to: "+ mode) - logger.info("numeric value "+str(val)+ " sent to EVC") + logger.debug("numeric value "+str(val)+ " sent to EVC") try: client=ModbusTcpClient(GiV_Settings.evc_ip_address) client.write_registers(93,val) @@ -323,7 +335,7 @@ def setChargeControl(mode): if mode in GivLUT.charge_control: logger.info("Setting Charge control to: "+ mode) val=GivLUT.charge_control.index(mode) - logger.info("numeric value "+str(val)+ " sent to EVC") + logger.debug("numeric value "+str(val)+ " sent to EVC") try: client=ModbusTcpClient(GiV_Settings.evc_ip_address) client.write_registers(95,val) @@ -357,19 +369,36 @@ def test(): #getEVC() print (result) -def chargeMode(): +def chargeMode(once=False): while True: #Run a regular check and manage load based on current mode and session energy if exists(EVCLut.regcache): with open(EVCLut.regcache, 'rb') as inp: - evcRegCache= pickle.load(inp) + evcRegCache= pickle.load(inp) if evcRegCache['Charger']['Charging_State']=="Charging" or evcRegCache['Charger']['Charging_State']=="Connected": if evcRegCache['Charger']['Charging_Mode']=="Hybrid": hybridmode() elif evcRegCache['Charger']['Charging_Mode']=="Solar": solarmode() - if not evcRegCache['Charger']['Max_Session_Energy']==0 and evcRegCache['Charger']['Charge_Session_Energy']>evcRegCache['Charger']['Max_Session_Energy'] and evcRegCache['Charger']['Charge_Control']=="Start": + if not evcRegCache['Charger']['Max_Session_Energy']==0 and evcRegCache['Charger']['Charge_Session_Energy']>=evcRegCache['Charger']['Max_Session_Energy'] and evcRegCache['Charger']['Charge_Control']=="Start": + logger.info("Session energy limit reached: "+str(evcRegCache['Charger']['Charge_Session_Energy'])+"kWh stopping charge") setChargeControl("Stop") + if not int(evcRegCache['Charger']['Import_Cap'])==0: + if exists(GivLUT.regcache): + with open(GivLUT.regcache, 'rb') as inp: + invRegCache= pickle.load(inp) + if float(invRegCache[4]['Power']['Power']['Grid_Current'])>(float(evcRegCache['Charger']['Import_Cap'])*0.95): + target=float(evcRegCache['Charger']['Import_Cap'])*0.9 + reduction=(float(invRegCache[4]['Power']['Power']['Grid_Current']))-target + newlimit=int(float(evcRegCache['Charger']['Charge_Limit'])-reduction) + if not int(evcRegCache['Charger']['Charge_Limit'])==4: + logger.info("Grid import threshold within 5%, reducing EVC charge current to: "+str(newlimit)) + setCurrentLimit(newlimit) + else: + logger.info("Grid import threshold within 5%, cannot reduce Charge limit below 6A. Stopping Charge") + setChargeControl("Stop") + if once: + break time.sleep(60) @@ -456,7 +485,7 @@ def setChargingMode(mode): with cacheLock: with open(EVCLut.regcache, 'wb') as outp: pickle.dump(evcRegCache, outp, pickle.HIGHEST_PROTOCOL) - chargeMode() # Run an initial call when changing modes + chargeMode(True) # Run an initial call when changing modes else: logger.error("Invalid selection for Charge Mode ("+str(mode)+")") diff --git a/GivTCP/palm_settings.py b/GivTCP/palm_settings.py index 96bdbe7b..f32c1bb0 100644 --- a/GivTCP/palm_settings.py +++ b/GivTCP/palm_settings.py @@ -28,7 +28,7 @@ class GE: # Modify url with system name in place of CExxxxxx and paste API key generated on GivEnergy web portal in place of xxxx url = "https://api.givenergy.cloud/v1/inverter/"+GiV_Settings.serial_number+"/" # key = str(os.getenv('GEAPI')) - key = str(GiV_Settings.GEAPI) + key = str(GiV_Settings.GE_API) # Most users will not need to touch that many of the pre-configured settings below diff --git a/GivTCP/palm_utils.py b/GivTCP/palm_utils.py index 108de077..a587e2eb 100644 --- a/GivTCP/palm_utils.py +++ b/GivTCP/palm_utils.py @@ -227,7 +227,7 @@ def get_load_hist_day(offset: int): i: int = 0 while i < len(stgs.GE.load_hist_weight): if stgs.GE.load_hist_weight[i] > 0: - logger.info("Processing load history for day -"+ str(i + 1)) + logger.debug("Processing load history for day -"+ str(i + 1)) load_hist_array = get_load_hist_day(i) j = 0 while j < 48: @@ -237,7 +237,7 @@ def get_load_hist_day(offset: int): total_weight += stgs.GE.load_hist_weight[i] logger.debug(str(acc_load)+ " total weight: "+ str(total_weight)) else: - logger.info("Skipping load history for day -"+ str(i + 1)+ " (weight <= 0)") + logger.debug("Skipping load history for day -"+ str(i + 1)+ " (weight <= 0)") i += 1 # Avoid DIV/0 if config file contains incorrect weightings @@ -250,7 +250,7 @@ def get_load_hist_day(offset: int): while i < 48: self.base_load[i] = round(acc_load[i]/total_weight, 1) i += 1 - logger.info("Load Calc Summary: "+ str(self.base_load)) + logger.debug("Load Calc Summary: "+ str(self.base_load)) def set_mode(self, cmd: str): """Configures inverter operating mode""" @@ -289,10 +289,10 @@ def set_inverter_register(register: str, value: str): logger.error(error) return if resp.status_code != 201: - logger.info("Invalid response: "+ str(resp.status_code)) + logger.debug("Invalid response: "+ str(resp.status_code)) return - logger.info("Setting Register "+ str(register)+ " ("+ str(cmd_name) + ") to "+ + logger.debug("Setting Register "+ str(register)+ " ("+ str(cmd_name) + ") to "+ str(value)+ " Response: "+ str(resp)) time.sleep(3) # Allow data on GE server to settle @@ -318,7 +318,7 @@ def set_inverter_register(register: str, value: str): returned_cmd = json.loads(resp.content.decode('utf-8'))['data']['value'] if str(returned_cmd) == str(value): - logger.info("Successful register read: "+ str(register)+ " = "+ str(returned_cmd)) + logger.debug("Successful register read: "+ str(register)+ " = "+ str(returned_cmd)) else: logger.error("Readback failed on GivEnergy API... Expected " + str(value) + ", Read: "+ str(returned_cmd)) @@ -396,8 +396,8 @@ def compute_tgt_soc(self, gen_fcast, weight: int, commit: bool) -> str: wgt_50 = weight - 10 wgt_90 = max(0, weight - 50) - logger.info("") - logger.info("{:<20} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}".format("SoC Calc;", + logger.debug("") + logger.debug("{:<20} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}".format("SoC Calc;", "Day", "Hour", "Charge", "Cons", "Gen", "SoC")) # Definitions for export of SoC forecast in chart form @@ -450,7 +450,7 @@ def compute_tgt_soc(self, gen_fcast, weight: int, commit: bool) -> str: elif i > end_charge_period: # Charging after overnight boost max_charge = max(max_charge, batt_charge[i]) - logger.info("{:<20} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}".format("SoC Calc;", + logger.debug("{:<20} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}".format("SoC Calc;", day, t_to_hrs(i * 30), round(batt_charge[i], 2), round(total_load, 2), round(est_gen, 2), int(100 * batt_charge[i] / batt_max_charge))) @@ -580,15 +580,15 @@ def get_solcast(url) -> Tuple[bool, str]: return if stgs.Solcast.url_sw != "": # Two arrays are specified - logger.info("url_sw = '"+str(stgs.Solcast.url_sw)+"'") + logger.debug("url_sw = '"+str(stgs.Solcast.url_sw)+"'") result, solcast_data_2 = get_solcast(stgs.Solcast.url_sw) if not result: logger.warning("Error; Problem with Solcast data, using previous values (if any)") return else: - logger.info("No second array") + logger.debug("No second array") - logger.info("Successful Solcast download.") + logger.debug("Successful Solcast download.") # Combine forecast for PV arrays & align data with day boundaries pv_est10 = [0] * 10080 @@ -604,7 +604,7 @@ def get_solcast(url) -> Tuple[bool, str]: # Check for BST and convert to local time to align with GivEnergy data if time.strftime("%z", time.localtime()) == "+0100": - logger.info("Applying BST offset to Solcast data") + logger.debug("Applying BST offset to Solcast data") solcast_offset += 60 i = solcast_offset @@ -664,11 +664,11 @@ def get_solcast(url) -> Tuple[bool, str]: i += 1 timestamp = time.strftime("%d-%m-%Y %H:%M:%S", time.localtime()) - logger.info("PV Estimate 10% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ + logger.debug("PV Estimate 10% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ str(self.pv_est10_30[0:47])+ str(self.pv_est10_day[0:6])) - logger.info("PV Estimate 50% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ + logger.debug("PV Estimate 50% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ str(self.pv_est50_30[0:47])+ str(self.pv_est50_day[0:6])) - logger.info("PV Estimate 90% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ + logger.debug("PV Estimate 90% (hrly, 7 days) / kWh; "+ timestamp+ "; "+ str(self.pv_est90_30[0:47])+ str(self.pv_est90_day[0:6])) # End of SolcastObj() class definition diff --git a/GivTCP/read.py b/GivTCP/read.py index 3ac2033b..5276aa09 100644 --- a/GivTCP/read.py +++ b/GivTCP/read.py @@ -174,7 +174,7 @@ def getData(fullrefresh): # Read from Inverter put in cache power_output['PV_Current_String_1'] = GEInv.i_pv1*10 power_output['PV_Current_String_2'] = GEInv.i_pv2*10 power_output['Grid_Voltage'] = GEInv.v_ac1 - power_output['Grid_Current'] = GEInv.i_ac1*10 + power_output['Grid_Current'] = GEInv.i_grid_port # Grid Power logger.debug("Getting Grid Power") @@ -504,7 +504,7 @@ def getData(fullrefresh): # Read from Inverter put in cache timeslots['Charge_start_time_slot_1'] = GEInv.charge_slot_1[0].isoformat() timeslots['Charge_end_time_slot_1'] = GEInv.charge_slot_1[1].isoformat() try: - if inverterModel.generation == "Gen 2" or inverterModel.generation == "Gen 3": + if inverterModel.model == "AIO" or (inverterModel.generation == "Gen 3" and int(GEInv.arm_firmware_version)>302): #10 slots only apply to AIO and new fw on Gen 3 timeslots['Charge_start_time_slot_2'] = GEInv.charge_slot_2[0].isoformat() timeslots['Charge_end_time_slot_2'] = GEInv.charge_slot_2[1].isoformat() timeslots['Charge_start_time_slot_3'] = GEInv.charge_slot_3[0].isoformat() diff --git a/GivTCP/write.py b/GivTCP/write.py index a983a651..9870ecf3 100644 --- a/GivTCP/write.py +++ b/GivTCP/write.py @@ -1167,6 +1167,7 @@ def rebootAddon(): result = requests.post(url, headers={'Content-Type':'application/json', 'Authorization': 'Bearer {}'.format(access_token)}) + return json.dumps(result) def getSavedBatteryReservePercentage(): saved_battery_reserve=4 diff --git a/README.md b/README.md index d1a70e50..40ab4dea 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,11 @@ GivTCP collects all inverter and battery data and creates a nested data structur ## GivEnergy Electric Vehicle Charger -From version 2.4 onwards GivTCP incorporates control and monitoring of the GE charger. Connecting via local modbus it can monitor real-time stats and provide simple control features. +From version 2.4 onwards GivTCP incorporates control and monitoring of the GE charger. Connecting via local modbus it can monitor real-time stats and provide simple control features. With the EVC cloud conrol does not use the modbus protocol, so there is minimal opportunity for changes to reflect in the official app/cloud portal. Particularly the Charging modes, which are merely mimiced in GivEVC ## Configuration -All that is required for config are the IP address and the self run timer. Setting EVC_EABLE to True will turn on the function. +All that is required for config are the IP address and the self run timer. Setting EVC_ENABLE to True will turn on the function. ## Control @@ -98,3 +98,9 @@ Modulates the Charge Limit based on the amount of "excess solar" available after #### Hybrid This will modulate Charge Limit to top up a base 6A grid charge with any excess solar energy. Similar to Solar but uses a constant 6A from Grid plus additional solar energy on top. + +### Max Session Energy +This will cap the maximum energy delivered to the vehicle in a single charge session. Setting this to 0kWh disables this setting. + +### Import Cap +This will monitor the Grid current from the first GivTCP inverter and if it is within 5% of the Import Cap setting it will reduce EVC Charge current to stay 10% under the import Cap. Setting this to 0A disables this setting. diff --git a/battery control dashboard raw config.txt b/battery control dashboard raw config.txt deleted file mode 100644 index 3d18b15e..00000000 --- a/battery control dashboard raw config.txt +++ /dev/null @@ -1,312 +0,0 @@ -title: Home -views: - - title: Energy Now - path: energy-now - badges: [] - cards: - - type: custom:apexcharts-card - header: - title: Load Usage - show_states: true - colorize_states: true - apex_config: - plotOptions: - pie: - donut: - labels: - total: - show: true - chart_type: donut - series: - - entity: sensor.other_power - - entity: sensor.tumble_dryer_electric_consumption_w - - entity: sensor.av_rack_electric_consumption_w - - entity: sensor.boiling_tap_electric_consumption_w - - entity: sensor.car_charge_power - - entity: sensor.server_rack_electric_consumption_w_2 - - entity: sensor.lights_power - - entity: sensor.eden_fan_energy_power_2 - - entity: sensor.hot_tub_power - - entity: sensor.tsmart_power_w - - type: custom:tesla-style-solar-power-card - name: My Flows - grid_consumption_entity: sensor.givtcp_XX1234X567_import_power - house_consumption_entity: sensor.givtcp_XX1234X567_load_power - generation_yield_entity: sensor.givtcp_XX1234X567_pv_power - battery_consumption_entity: sensor.givtcp_XX1234X567_discharge_power - grid_to_house_entity: sensor.givtcp_XX1234X567_grid_to_house - grid_to_battery_entity: sensor.givtcp_XX1234X567_grid_to_battery - generation_to_grid_entity: sensor.givtcp_XX1234X567_solar_to_grid - generation_to_battery_entity: sensor.givtcp_XX1234X567_solar_to_battery - generation_to_house_entity: sensor.givtcp_XX1234X567_solar_to_house - battery_to_house_entity: sensor.givtcp_XX1234X567_battery_to_house - battery_to_grid_entity: sensor.givtcp_XX1234X567_battery_to_grid - battery_extra_entity: sensor.givtcp_XX1234X567_soc - appliance1_consumption_entity: sensor.car_charge_power - appliance1_extra_entity: sensor.etron_state_of_charge - grid_entity: sensor.givtcp_XX1234X567_grid_power - house_entity: sensor.givtcp_XX1234X567_load_power - generation_entity: sensor.givtcp_XX1234X567_pv_power - battery_entity: sensor.givtcp_XX1234X567_battery_power - show_w_not_kw: 1 - hide_inactive_lines: 1 - change_house_bubble_color_with_flow: 1 - - type: gauge - entity: sensor.electric_cost_today - min: 0 - max: 20 - needle: false - - type: gauge - entity: sensor.gas_cost_today - min: 0 - max: 20 - - type: entities - entities: - - entity: sensor.givtcp_XX1234X567_night_cost - - entity: sensor.givtcp_XX1234X567_night_energy_kwh - - entity: sensor.givtcp_XX1234X567_day_cost - - entity: sensor.givtcp_XX1234X567_day_energy_kwh - - entity: sensor.givtcp_XX1234X567_import_ppkwh_today - - type: custom:apexcharts-card - header: - title: Load Usage - show_states: true - colorize_states: true - apex_config: - plotOptions: - pie: - donut: - labels: - total: - show: true - chart_type: donut - series: - - entity: sensor.givtcp_XX1234X567_day_energy_kwh - - entity: sensor.givtcp_XX1234X567_night_energy_kwh - - entity: sensor.givtcp_XX1234X567_pv_energy_today_kwh - - type: history-graph - entities: - - entity: sensor.givtcp_XX1234X567_battery_value - - entity: sensor.givtcp_XX1234X567_battery_ppkwh - hours_to_show: 24 - - panel: false - path: energy - title: Energy - badges: [] - cards: - - type: history-graph - entities: - - entity: sensor.givtcp_XX1234X567_battery_power - - entity: sensor.givtcp_XX1234X567_load_power - - entity: sensor.givtcp_XX1234X567_grid_power - - entity: sensor.givtcp_XX1234X567_pv_power - hours_to_show: 24 - refresh_interval: 0 - title: Energy Usage - - type: history-graph - entities: - - entity: sensor.givtcp_XX1234X567_soc - - entity: number.givtcp_XX1234X567_target_soc - hours_to_show: 24 - refresh_interval: 0 - title: Battery State - - type: custom:apexcharts-card - header: - show: true - title: Import/Export - show_states: true - colorize_states: true - graph_span: 24h - span: - end: hour - experimental: - color_threshold: true - series: - - entity: sensor.givtcp_XX1234X567_grid_power - type: column - group_by: - func: avg - duration: 30min - color_threshold: - - value: -2 - color: red - opacity: 1 - - value: 2 - color: green - - type: custom:apexcharts-card - header: - title: Load Usage - show_states: true - colorize_states: true - graph_span: 24h - span: - end: hour - apex_config: - chart: - stacked: 'true' - series: - - entity: sensor.givtcp_XX1234X567_grid_to_battery - type: column - color: blue - group_by: - func: avg - duration: 30min - - entity: sensor.lights_power - type: column - color: yellow - group_by: - func: avg - duration: 30min - - entity: sensor.server_rack_electric_consumption_w_2 - type: column - group_by: - func: avg - duration: 30min - - entity: sensor.tumble_dryer_electric_consumption_w - type: column - group_by: - func: avg - duration: 30min - - entity: sensor.av_rack_electric_consumption_w - type: column - group_by: - func: avg - duration: 30min - - entity: sensor.boiling_tap_power_stable - type: column - group_by: - func: avg - duration: 30min - - entity: sensor.car_charge_power - type: column - group_by: - func: avg - duration: 30min - - entity: sensor.hot_tub_power - type: column - color: brown - group_by: - func: avg - duration: 30min - - entity: sensor.other_power - type: column - color: grey - group_by: - func: avg - duration: 30min - - entity: sensor.tsmart_power_w - type: column - color: orange - group_by: - func: avg - duration: 30min - - type: history-graph - entities: - - entity: sensor.energy_production_tomorrow - - entity: sensor.givtcp_XX1234X567_pv_energy_today_kwh - refresh_interval: 0 - title: Forecast vs Actual PV - hours_to_show: 72 - - type: history-graph - entities: - - entity: sensor.givtcp_XX1234X567_pv_power - - entity: sensor.givtcp_XX1234X567_pv_power_string_1 - - entity: sensor.givtcp_XX1234X567_pv_power_string_2 - hours_to_show: 12 - - type: custom:apexcharts-card - header: - show: true - title: Daily cost - show_states: true - colorize_states: true - graph_span: 28d - span: - end: day - apex_config: - chart: - stacked: 'true' - series: - - entity: sensor.givtcp_XX1234X567_day_cost - type: column - color: red - group_by: - func: max - duration: 24h - - entity: sensor.givtcp_XX1234X567_night_cost - type: column - color: orange - group_by: - func: max - duration: 24h - - type: custom:apexcharts-card - header: - show: true - title: Daily Load - show_states: true - colorize_states: true - graph_span: 28d - span: - end: hour - apex_config: - chart: - stacked: 'true' - series: - - entity: sensor.givtcp_XX1234X567_day_energy_kwh - type: column - color: red - group_by: - func: last - duration: 24h - - entity: sensor.givtcp_XX1234X567_night_energy_kwh - type: column - color: orange - group_by: - func: last - duration: 24h - - entity: sensor.givtcp_XX1234X567_pv_energy_today_kwh - type: column - color: green - group_by: - func: last - duration: 24h - - type: history-graph - entities: - - entity: sensor.givtcp_XX1234X567_load_energy_today_kwh - - theme: Backend-selected - title: GivEnergy Control - path: givenergy-control - badges: [] - cards: - - type: entities - entities: - - entity: select.givtcp_XX1234X567_mode - - entity: sensor.givtcp_XX1234X567_soc - - entity: number.givtcp_XX1234X567_target_soc - - entity: number.givtcp_XX1234X567_battery_charge_rate - - entity: switch.givtcp_XX1234X567_enable_charge_schedule - - entity: select.givtcp_XX1234X567_charge_start_time_slot_1 - - entity: select.givtcp_XX1234X567_charge_end_time_slot_1 - - entity: switch.givtcp_XX1234X567_enable_discharge - - entity: number.givtcp_XX1234X567_battery_discharge_rate - - entity: number.givtcp_XX1234X567_battery_power_reserve - - entity: switch.givtcp_XX1234X567_enable_discharge_schedule - - entity: select.givtcp_XX1234X567_discharge_start_time_slot_1 - - entity: select.givtcp_XX1234X567_discharge_end_time_slot_1 - - entity: select.givtcp_XX1234X567_discharge_start_time_slot_2 - - entity: select.givtcp_XX1234X567_discharge_end_time_slot_2 - title: Battery Control - show_header_toggle: false - state_color: true - header: - type: graph - entity: sensor.givtcp_XX1234X567_soc - hours_to_show: 24 - - type: entities - entities: - - entity: select.givtcp_XX1234X567_temp_pause_charge - - entity: select.givtcp_XX1234X567_temp_pause_discharge - - entity: select.givtcp_XX1234X567_force_charge - - entity: select.givtcp_XX1234X567_force_export - title: Battery Boost Control - show_header_toggle: false - state_color: true \ No newline at end of file diff --git a/buildx.bat b/buildx.bat index 5312db20..86bbaeec 100644 --- a/buildx.bat +++ b/buildx.bat @@ -1,3 +1,3 @@ -docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:2.3.113 -t britkat/giv_tcp-dev:latest --push . +docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:2.3.151 -t britkat/giv_tcp-dev:latest --push . ::docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:latest --push . ::docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-ma:latest -t britkat/giv_tcp-ma:2.3.3 --push . diff --git a/givenergy_modbus/client.py b/givenergy_modbus/client.py index 35cdc4a4..db8becc3 100644 --- a/givenergy_modbus/client.py +++ b/givenergy_modbus/client.py @@ -72,13 +72,22 @@ def refresh_plant(self, plant: Plant, isAIO: bool, isAC: bool, full_refresh: boo inverter_registers, plant.inverter_rc, slave_address=0x31, sleep_between_queries=sleep_between_queries ) _logger.debug("Inverter is normal so using the 0x31 slave_address") - for i, battery_rc in enumerate(plant.batteries_rcs): - self.fetch_register_pages( - {InputRegister: [60]}, - battery_rc, - slave_address=0x32 + i, - sleep_between_queries=sleep_between_queries, - ) + if isAIO: + for i, battery_rc in enumerate(plant.batteries_rcs): + self.fetch_register_pages( + {InputRegister: [60]}, + battery_rc, + slave_address=0x32 + i, + sleep_between_queries=sleep_between_queries, + ) + else: + for i, battery_rc in enumerate(plant.batteries_rcs): + self.fetch_register_pages( + {InputRegister: [60]}, + battery_rc, + slave_address=0x32 + i, + sleep_between_queries=sleep_between_queries, + ) def get_inverter_stats(self): try: diff --git a/givenergy_modbus/model/battery.py b/givenergy_modbus/model/battery.py index 0a34abf3..5bbf7c2b 100644 --- a/givenergy_modbus/model/battery.py +++ b/givenergy_modbus/model/battery.py @@ -48,3 +48,94 @@ class Battery(GivEnergyBaseModel): usb_inserted: bool e_battery_charge_total_2: float e_battery_discharge_total_2: float + +class HVBMU(GivEnergyBaseModel): + """Structured format for all inverter attributes.""" + """BMU Register structure Cell Volt and Temp""" + v_battery_cell_01: float + v_battery_cell_02: float + v_battery_cell_03: float + v_battery_cell_04: float + v_battery_cell_05: float + v_battery_cell_06: float + v_battery_cell_07: float + v_battery_cell_08: float + v_battery_cell_09: float + v_battery_cell_10: float + v_battery_cell_11: float + v_battery_cell_12: float + v_battery_cell_13: float + v_battery_cell_14: float + v_battery_cell_15: float + v_battery_cell_16: float + v_battery_cell_17: float + v_battery_cell_18: float + v_battery_cell_19: float + v_battery_cell_20: float + v_battery_cell_21: float + v_battery_cell_22: float + v_battery_cell_23: float + v_battery_cell_24: float + temp_battery_cells_1: float + temp_battery_cells_2: float + temp_battery_cells_3: float + temp_battery_cells_4: float + temp_battery_cells_5: float + temp_battery_cells_6: float + temp_battery_cells_7: float + temp_battery_cells_8: float + temp_battery_cells_9: float + temp_battery_cells_10: float + temp_battery_cells_11: float + temp_battery_cells_12: float + temp_battery_cells_13: float + temp_battery_cells_14: float + temp_battery_cells_15: float + temp_battery_cells_16: float + temp_battery_cells_17: float + temp_battery_cells_18: float + temp_battery_cells_19: float + temp_battery_cells_20: float + temp_battery_cells_21: float + temp_battery_cells_22: float + temp_battery_cells_23: float + temp_battery_cells_24: float + +class HVBCU(GivEnergyBaseModel): + pack_software_version: str + number_of_module: float + cells_per_module: float + cluster_cell_voltage: float + cluster_cell_temperature: float + status: str + battery_voltage: float + load_voltage: float + battery_current: float + battery_power: float + battery_soh: float # should this be SOC? + charge_energy_total: float + discharge_energy_total: float + charge_energy_capacity_total: float + discharge_energy_capacity_total: float + charge_energy_today: float + discharge_energy_today: float + charge_energy_capacity_today: float + discharge_energy_capacity_today: float + battery_capacity: float + number_of_cycles: float + min_discharge_voltage: float + max_discharge_voltage: float + min_discharge_current: float + max_discharge_current: float + max_cell_voltage: float + max_voltage_module: float + max_voltage_cell: float + min_cell_voltage: float + min_voltage_module: float + min_voltage_cell: float + max_cell_temperature: float + max_temperature_module: float + max_temperature_cell: float + min_cell_temperature: float + min_temperature_module: float + min_temperature_cell: float \ No newline at end of file diff --git a/givtcp-vuejs/package.json b/givtcp-vuejs/package.json index f84a3b71..391052ec 100644 --- a/givtcp-vuejs/package.json +++ b/givtcp-vuejs/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "dev": "vite --host", + "dev": "vite --host -l silent", "build": "vite build", "preview": "vite preview", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", diff --git a/givtcp-vuejs/src/components/FormCard.vue b/givtcp-vuejs/src/components/FormCard.vue index 0ec82956..7301dd2d 100644 --- a/givtcp-vuejs/src/components/FormCard.vue +++ b/givtcp-vuejs/src/components/FormCard.vue @@ -22,6 +22,15 @@ :label="input?.options?.label" color='#4fbba9' /> +
+ {{input?.options?.label}} +

{{ storeTCP.restart.hasRestarted != null ? storeTCP.restart.hasRestarted ? "Container Restarted Successfully" : "Container Failed to Restart. Try Restarting Manually" : ''}}

+
@@ -45,6 +54,14 @@ export default { return { storeTCP: useTcpStore() } + }, + watch:{ + storeTCP:{ + async handler(){ + setTimeout(() => this.storeTCP.restart.hasRestarted = null,10000) + }, + deep:true + } } } diff --git a/givtcp-vuejs/src/components/StepButton.vue b/givtcp-vuejs/src/components/StepButton.vue index 3ba28cf5..01441b0f 100644 --- a/givtcp-vuejs/src/components/StepButton.vue +++ b/givtcp-vuejs/src/components/StepButton.vue @@ -3,7 +3,7 @@ Previous - Next + Next { if(key in this.storeTCP.web){ this.storeTCP.web[key] = getJSON[key] @@ -89,26 +100,13 @@ export default { return } }) - var host = window.location.protocol + "//" + window.location.hostname+":6345/settings" - console.log("JSON data being sent is: "+JSON.stringify(data)) - const setResponse = await fetch(host,{ - method:"POST", - headers:{ - "Content-Type":"application/json" - }, - body:JSON.stringify(data) - }) - if(setResponse.ok){ - this.snackbar = true - this.message = `Success` }else{ this.snackbar = true this.message = `Server Error: ${setResponse.statusText}` } }catch(e){ this.snackbar = true - this.message = `Error: ${e.name}` - console.log("Error was: "+e) + this.message = `Error: ${e}` } }, deep:true @@ -123,4 +121,4 @@ export default { flex-wrap: wrap; gap: 12px; } - + \ No newline at end of file diff --git a/givtcp-vuejs/src/stores/counter.js b/givtcp-vuejs/src/stores/counter.js index 9e0e8310..03758ef2 100644 --- a/givtcp-vuejs/src/stores/counter.js +++ b/givtcp-vuejs/src/stores/counter.js @@ -75,6 +75,10 @@ export const useTcpStore = defineStore('givtcp-form', { evc_ip_address: "", evc_self_run_timer: 10, evc_import_max_current: 60 + }), + restart: useStorage('restart',{ + restart:false, + hasRestarted:null }) }) }) @@ -575,6 +579,36 @@ export const useCard = defineStore('card', { } } ] + }, + restart:{ + title:"Finished Setup", + subtitle:"Restart Container to apply changes", + fields:[ + { + type: 'button', + options: { + label: 'Restart Container', + parent: 'restart', + key: 'restart', + message:useTcpStore().restart.hasRestarted != null ? useTcpStore().restart.hasRestarted ? "Container Restarted Successfully" : "Container Failed to Restart. Try Restarting Manually" : '', + onClick:async ()=>{ + const store = useTcpStore() + try{ + var host = window.location.protocol + "//" + window.location.hostname+":6345/restart" + //var host = "http://127.0.0.1:6345/restart" + const res = await fetch(host) + if(res.ok){ + store.restart.hasRestarted = true + }else{ + store.restart.hasRestarted = false + } + } catch(e){ + store.restart.hasRestarted = false + } + } + } + }, + ] } }) }) diff --git a/index.html b/index.html new file mode 100644 index 00000000..5e1c309d --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/ingress.conf b/ingress.conf new file mode 100644 index 00000000..8ed3e99d --- /dev/null +++ b/ingress.conf @@ -0,0 +1,28 @@ +server { + listen 8099; +# allow 172.30.32.2; +# deny all; + access_log off; + error_log off; + + location / { + root /app/config_frontend/dist; + index index.html; + try_files $uri $uri/ /index.html; + } + location /REST1/ { + proxy_pass http://127.0.0.1:6345/; + } + location /REST2/ { + proxy_pass http://127.0.0.1:6346/; + } + location /REST3/ { + proxy_pass http://127.0.0.1:6347/; + } + location /dashboard/ { + proxy_pass http://127.0.0.1:3000/; + } + location /rq/ { + proxy_pass http://127.0.0.1:9181/; + } +} \ No newline at end of file diff --git a/startup.py b/startup.py index 7be71e4e..805ca96c 100644 --- a/startup.py +++ b/startup.py @@ -214,6 +214,8 @@ def getInvDeets(HOST): #vueConfig=subprocess.Popen(["npm", "run", "dev","-- --host"],cwd="/app/config_frontend") #logger.critical("Running Config Frontend") +subprocess.Popen(["nginx","-g","daemon off;error_log /dev/stdout debug;"]) + ########################################################################################################## # # diff --git a/startup_3.py b/startup_3.py index fa4080d2..93acf9cc 100644 --- a/startup_3.py +++ b/startup_3.py @@ -33,7 +33,7 @@ def palm_job(): subprocess.Popen(["/usr/local/bin/python3","/app/GivTCP_1/palm_soc.py"]) def validateEVC(HOST): - logger.info("Validating "+str(HOST)) + logger.debug("Validating "+str(HOST)) try: client = ModbusTcpClient(HOST) regs = client.read_holding_registers(97,6).registers @@ -41,9 +41,28 @@ def validateEVC(HOST): return True except: e=sys.exc_info() - logger.info(e) + logger.error(e) return False +def isitoldfw(invstats): + '''Firmware Versions for each Model + AC coupled 5xx old, 2xx new. 28x, 29x beta + Gen1 4xx Old, 1xx New. 19x Beta + Gen 2 909+ New. 99x Beta Schedule Pause only for Gen2+ + Gen 3 303+ New 39x Beta New has 10 slots + AIO 6xx New 69x Beta ALL has 10 slots''' + if invstats['Model']=='AC' and int(invstats['Firmware'])>500: + return True + elif invstats['Model']=='AIO' and int(invstats['Firmware'])>600: + return True + elif invstats['Generation']=='Gen 1' and int(invstats['Firmware'])<400: + return True + elif invstats['Generation']=='Gen 2' and int(invstats['Firmware'])<908: + return True + elif invstats['Generation']=='Gen 3' and int(invstats['Firmware'])<302: + return True + return False + def getInvDeets(HOST): try: client=GivEnergyClient(host=HOST) @@ -64,7 +83,7 @@ def getInvDeets(HOST): isAddon=True access_token = os.getenv("SUPERVISOR_TOKEN") except: - logger.critical("SUPERVISOR TOKEN does not exist") + logger.debug("SUPERVISOR TOKEN does not exist") isAddon=False hasMQTT=False SuperTimezone=False @@ -85,7 +104,7 @@ def getInvDeets(HOST): hasMQTT=True else: hasMQTT=False - logger.critical("No HA MQTT service has been found") + logger.debug("No HA MQTT service has been found") #Get Timezone url="http://supervisor/info" @@ -94,7 +113,7 @@ def getInvDeets(HOST): 'Authorization': 'Bearer {}'.format(access_token)}) info=result.json() SuperTimezone=info['data']['timezone'] - logger.info("Supervisor Timezone: "+str(SuperTimezone)) + logger.debug("Supervisor Timezone: "+str(SuperTimezone)) #Get Host Details url="http://supervisor/network/info" @@ -131,7 +150,7 @@ def getInvDeets(HOST): invList={} list={} evclist={} - logger.critical("Scanning network for inverters...") + logger.debug("Scanning network for inverters...") try: for subnet in networks: if networks[subnet]: @@ -139,7 +158,7 @@ def getInvDeets(HOST): # Get EVC Details while len(evclist)<=0: if count<2: - logger.info("EVC- Scanning network ("+str(count+1)+"):"+str(networks[subnet])) + logger.debug("EVC- Scanning network ("+str(count+1)+"):"+str(networks[subnet])) evclist=findEVC(networks[subnet]) if len(evclist)>0: break count=count+1 @@ -149,16 +168,16 @@ def getInvDeets(HOST): poplist=[] for evc in evclist: if validateEVC(evclist[evc]): - logger.info("GivEVC found at: "+str(evclist[evc])) + logger.critical("GivEVC found at: "+str(evclist[evc])) else: - logger.info(evclist[evc]+" is not an EVC") + logger.debug(evclist[evc]+" is not an EVC") poplist.append(evc) for pop in poplist: evclist.pop(pop) #remove the unknown modbus device(s) # Get Inverter Details while len(list)<=0: if count<2: - logger.info("INV- Scanning network ("+str(count+1)+"):"+str(networks[subnet])) + logger.debug("INV- Scanning network ("+str(count+1)+"):"+str(networks[subnet])) list=findInvertor(networks[subnet]) if len(list)>0: break count=count+1 @@ -187,7 +206,7 @@ def getInvDeets(HOST): else: break if len(invList)==0: - logger.critical("No inverters found...") + logger.debug("No inverters found...") else: # write data to pickle with open('invippkl.pkl', 'wb') as outp: @@ -198,14 +217,18 @@ def getInvDeets(HOST): else: logger.error("Unable to get host details from Supervisor\Container") -logger.critical("GivTCP isAddon: "+str(isAddon)) +logger.debug("GivTCP isAddon: "+str(isAddon)) #rqdash=subprocess.Popen(["/usr/local/bin/rq-dashboard","-u redis://127.0.0.1:6379"]) #logger.critical("Running RQ Dashboard on port 9181") -vueConfig=subprocess.Popen(["npm", "run", "dev","-- --host"],cwd="/app/config_frontend") -logger.critical("Running Config Frontend") +#vueConfig=subprocess.Popen(["npm", "run", "dev","-- --host --silent"],cwd="/app/config_frontend") +#logger.debug("Running Config Frontend") + + +#shutil.copytree("/app/config_frontend/dist", '/usr/share/nginx/html') +subprocess.Popen(["nginx","-g","daemon off;error_log /dev/stdout debug;"]) ########################################################################################################## # @@ -222,41 +245,53 @@ def getInvDeets(HOST): ########################################################################################################## redis=subprocess.Popen(["/usr/bin/redis-server","/app/redis.conf"]) -logger.critical("Running Redis") +logger.debug("Running Redis") if not os.path.exists("/config/GivTCP"): os.makedirs("/config/GivTCP") - logger.critical("No config directory exists, so creating it...") + logger.debug("No config directory exists, so creating it...") else: - logger.critical("Config directory already exists") + logger.debug("Config directory already exists") for inv in range(1,int(os.getenv('NUMINVERTORS'))+1): - logger.critical ("Setting up invertor: "+str(inv)+" of "+str(os.getenv('NUMINVERTORS'))) + logger.debug ("Setting up invertor: "+str(inv)+" of "+str(os.getenv('NUMINVERTORS'))) PATH= "/app/GivTCP_"+str(inv) PATH2= "/app/GivEnergy-Smart-Home-Display-givtcp_"+str(inv) SFILE="/config/GivTCP/settings"+str(inv)+".json" # Create folder per instance if not exists(PATH): - logger.info("Local instance folder doesn't exist") + logger.debug("Local instance folder doesn't exist") shutil.copytree("/app/GivTCP", PATH) shutil.copytree("/app/GivEnergy-Smart-Home-Display-givtcp", PATH2) if not exists(SFILE): - logger.info("Copying in a template settings.json to: "+str(SFILE)) + logger.debug("Copying in a template settings.json to: "+str(SFILE)) shutil.copyfile("/app/settings.json",SFILE) - + else: + # If theres already a settings file, make sure its got any new elements + with open(SFILE, 'r') as f1: + setts=json.load(f1) + with open("/app/settings.json", 'r') as f2: + templatesetts=json.load(f2) + for setting in templatesetts: + if not setting in setts: + setts[setting]=templatesetts[setting] + with open(SFILE, 'w') as f: + f.write(json.dumps(setts,indent=4)) + + # Remove old settings file if exists(PATH+"/settings.py"): os.remove(PATH+"/settings.py") # Update json object with found data - logger.critical ("Recreating settings.py for invertor "+str(inv)) + logger.debug ("Recreating settings.py for invertor "+str(inv)) with open(SFILE, 'r') as f: setts=json.load(f) if SuperTimezone: setts["TZ"]=str(SuperTimezone) if hasMQTT: - logger.info("Using found MQTT data to autosetup settings.json") + logger.debug("Using found MQTT data to autosetup settings.json") setts["MQTT_Output"]=True # Only autosetup if there's not already a setting, to stop overriding manual setup if setts["MQTT_Address"]=="": setts["MQTT_Address"]=mqtt_host @@ -265,7 +300,7 @@ def getInvDeets(HOST): setts["MQTT_Port"]=mqtt_port if setts["MQTT_Address"]=="": setts['MQTT_Output']=False if len(invList)>0: - logger.info("Using found Inverter data to autosetup settings.json") + logger.debug("Using found Inverter data to autosetup settings.json") if setts["invertorIP"]=="": setts["invertorIP"]=invList[inv] if setts["invertorIP"]=="": setts["serial_number"]=inverterStats[inv]['Serial_Number'] if setts["invertorIP"]=="": setts['self_run']=False @@ -278,6 +313,15 @@ def getInvDeets(HOST): if len(evclist)>0: setts["evc_ip_address"]=evclist[1] setts["evc_enable"]=True + if len(inverterStats)>0: + if inverterStats[inv]['Model']=="All in One": + setts['isAIO']=True + else: + setts['isAIO']=False + if isitoldfw(inverterStats[inv]): + setts['isAC']=True + else: + setts['isAC']=False with open(SFILE, 'w') as f: f.write(json.dumps(setts,indent=4)) @@ -296,19 +340,19 @@ def getInvDeets(HOST): # Always delete lockfiles and FCRunning etc... but only delete pkl if too old? if exists(setts["cache_location"]+"/regCache_"+str(inv)+".pkl"): - logger.critical("Removing old invertor data cache") + logger.debug("Removing old invertor data cache") os.remove(str(setts["cache_location"])+"/regCache_"+str(inv)+".pkl") if exists(PATH+"/.lockfile"): - logger.critical("Removing old .lockfile") + logger.debug("Removing old .lockfile") os.remove(PATH+"/.lockfile") if exists(PATH+"/.FCRunning"): - logger.critical("Removing old .FCRunning") + logger.debug("Removing old .FCRunning") os.remove(PATH+"/.FCRunning") if exists(PATH+"/.FERunning"): - logger.critical("Removing old .FERunning") + logger.debug("Removing old .FERunning") os.remove(PATH+"/.FERunning") if exists(setts["cache_location"]+"/battery_"+str(inv)+".pkl"): - logger.critical("Removing old battery data cache") + logger.debug("Removing old battery data cache") os.remove(str(setts["cache_location"])+"/battery_"+str(inv)+".pkl") if exists(setts["cache_location"]+"/rateData_"+str(inv)+".pkl"): if "TZ" in setts: @@ -317,10 +361,10 @@ def getInvDeets(HOST): timezone=zoneinfo.ZoneInfo(key="Europe/London") modDay= datetime.fromtimestamp(os.path.getmtime(setts["cache_location"]+"/rateData_"+str(inv)+".pkl")).date() if modDay