224 lines
6.4 KiB
Python
224 lines
6.4 KiB
Python
|
|
import logging
|
|
import victronclient
|
|
import mqttpublisher
|
|
import tibberclient
|
|
from enum import IntEnum
|
|
|
|
logger = logging.getLogger('main')
|
|
|
|
charge_max_price_level = 0.024
|
|
|
|
MAX_POWER=4000
|
|
MAX_CHARGE_POWER=4000
|
|
|
|
|
|
class Chargemode(IntEnum):
|
|
FLOATING=0
|
|
CHARGING_MANUAL=1
|
|
CHARGING_MAX=2
|
|
DISCHARGING_MANUAL=3
|
|
DISCHARGING_MAX=4
|
|
CHARGING_CHEAP=5
|
|
BALANCE_DYN=6
|
|
CHARGE_DYN=7
|
|
DISCHARGE_DYN=8
|
|
|
|
class chargeman_controller:
|
|
|
|
def __init__(self, victronconn: victronclient.client, tibber: tibberclient.client) -> None:
|
|
self.chargemode=Chargemode.FLOATING
|
|
self.chargevalue=0
|
|
self.consump_updated=False
|
|
self.return_updated=False
|
|
self.consumption=0
|
|
self.return_delivery=0
|
|
self.netp1usage =0
|
|
self.valuekwh = 0.0
|
|
self.battery_total_net_usage = 0.0
|
|
self.battery_total_net_delivery = 0.0
|
|
self.setpoint = 0
|
|
self.netp1usageupdated = False
|
|
self.victronconn = victronconn
|
|
self.tibber = tibber
|
|
self.mqtt = mqttpublisher.publisher()
|
|
|
|
|
|
def set_battery_total_net_delivery(self, value):
|
|
self.battery_total_net_delivery = value
|
|
|
|
def get_battery_total_net_delivery(self):
|
|
return self.battery_total_net_delivery
|
|
|
|
def set_battery_total_net_usage(self, value):
|
|
self.battery_total_net_usage = value
|
|
|
|
def get_battery_total_net_usage(self):
|
|
return self.battery_total_net_usage
|
|
|
|
|
|
def set_net_consumption(self, value):
|
|
self.consumption = value
|
|
self.consump_updated = True
|
|
self.check_net_usage()
|
|
|
|
|
|
def set_net_delivery(self, value):
|
|
self.return_delivery = value
|
|
self.return_updated = True
|
|
self.check_net_usage()
|
|
|
|
def check_net_usage(self):
|
|
|
|
|
|
if self.return_updated and self.consump_updated:
|
|
self.consump_updated=False
|
|
self.return_updated=False
|
|
|
|
if self.netp1usageupdated: # skip till next p1 cycle
|
|
self.netp1usageupdated=False
|
|
return
|
|
|
|
self.netp1usage = self.consumption - self.return_delivery
|
|
self.netp1usageupdated = True
|
|
|
|
|
|
#logger.info("Update P1 usage: {}".format(netp1usage))
|
|
if self.chargemode in [Chargemode.BALANCE_DYN,Chargemode.CHARGE_DYN,Chargemode.DISCHARGE_DYN]:
|
|
|
|
setpoint = -1 * self.victronconn.get_power_usage()
|
|
|
|
setpoint = self.netp1usage+setpoint
|
|
|
|
if self.chargemode==Chargemode.CHARGE_DYN and setpoint > 0:
|
|
setpoint =0
|
|
elif self.chargemode==Chargemode.DISCHARGE_DYN and setpoint < 0:
|
|
setpoint =0
|
|
|
|
|
|
self.setpoint = -1 * setpoint
|
|
|
|
if self.setpoint > MAX_CHARGE_POWER:
|
|
self.setpoint = MAX_CHARGE_POWER
|
|
elif self.setpoint < -MAX_POWER:
|
|
self.setpoint = MAX_POWER
|
|
|
|
#logger.info ("Dynamic setpoint: {}".format(setpoint))
|
|
self.apply_charge_mode()
|
|
|
|
|
|
def set_chargemode(self, mode: Chargemode):
|
|
self.chargemode = mode
|
|
|
|
self.apply_charge_mode()
|
|
|
|
|
|
def set_chargemode_int(self, mode: int):
|
|
self.chargemode = Chargemode(mode)
|
|
self.apply_charge_mode()
|
|
|
|
def set_chargevalue(self,value):
|
|
self.chargevalue = value
|
|
|
|
|
|
|
|
def get_chargevalue(self):
|
|
return self.chargevalue
|
|
|
|
def get_chargemode(self) -> Chargemode:
|
|
return self.chargemode
|
|
|
|
def get_chargemode_int(self) -> int:
|
|
return int(self.chargemode)
|
|
|
|
|
|
|
|
|
|
def get_chargevaluekwh(self) -> float:
|
|
return self.valuekwh
|
|
|
|
|
|
def set_chargevaluekwh(self,value: float):
|
|
self.valuekwh = value
|
|
|
|
|
|
def update(self):
|
|
|
|
logger.debug("Run controller update")
|
|
|
|
self.mqtt.loop()
|
|
|
|
logger.debug("Controller update mqtt loop done")
|
|
|
|
if self.victronconn.get_power_usage() > 0:
|
|
self.battery_total_net_usage += self.victronconn.get_power_usage() /60/60*10
|
|
elif self.victronconn.get_power_usage() < 0:
|
|
self.battery_total_net_delivery += (self.victronconn.get_power_usage() * -1 ) /60/60*10
|
|
|
|
self.apply_charge_mode()
|
|
|
|
|
|
logger.debug("Controller update mqtt update batteries")
|
|
|
|
|
|
for battery in self.victronconn.batteries:
|
|
self.mqtt.publish_battery(battery)
|
|
|
|
|
|
logger.debug("Controller update mqtt update done")
|
|
|
|
|
|
logger.info('Mode={} CV={}W L1: SP={}W Net usage={}W Power={}W Battery: SoC={}% Current={:.2f}A TUsage={:.1f}KwH TDelivery{:.1f}KwH Tibber=€{}'.format(
|
|
str(self.chargemode),
|
|
int(self.chargevalue),
|
|
self.victronconn.get_setpoint(),
|
|
self.netp1usage,
|
|
self.victronconn.get_power_usage(),
|
|
self.victronconn.get_soc() ,
|
|
float(self.victronconn.get_battery_current() ),
|
|
self.battery_total_net_usage,
|
|
self.battery_total_net_delivery,
|
|
self.tibber.getPrice() ))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_charge_mode(self):
|
|
|
|
|
|
if self.chargemode in [Chargemode.CHARGING_MANUAL, Chargemode.DISCHARGING_MANUAL]:
|
|
self.setpoint = self.chargevalue
|
|
elif self.chargemode in [Chargemode.CHARGING_MAX]:
|
|
self.setpoint = MAX_CHARGE_POWER
|
|
elif self.chargemode in [Chargemode.DISCHARGING_MAX]:
|
|
self.setpoint = -MAX_POWER
|
|
|
|
elif self.chargemode == Chargemode.CHARGING_CHEAP:
|
|
if self.tibber.getPrice() == None:
|
|
logger.info("Tibber price unknown, doing nothing")
|
|
self.setpoint = 0
|
|
elif round(self.tibber.getPrice(),2) > charge_max_price_level:
|
|
logger.info("Energy prive {} too expensive, no charging".format(round(self.tibber.getPrice(),2)))
|
|
self.setpoint = 0
|
|
else:
|
|
logger.info("Energy prive {} ok, charging".format(round(self.tibber.getPrice(),2)))
|
|
self.setpoint = MAX_CHARGE_POWER
|
|
elif self.chargemode == Chargemode.FLOATING:
|
|
self.setpoint = 0
|
|
if self.setpoint > 0:
|
|
self.victronconn.set_charging()
|
|
elif self.setpoint == 0:
|
|
self.victronconn.set_idle()
|
|
else:
|
|
if self.setpoint < -MAX_POWER:
|
|
self.setpoint = -MAX_POWER
|
|
self.victronconn.set_discharging()
|
|
|
|
self.victronconn.set_setpoint(self.setpoint)
|
|
|
|
self.mqtt.publish_controller(self)
|