Skip to content

Commit

Permalink
Dev (#228)
Browse files Browse the repository at this point in the history
* bypass control
* when in bypass do not limit down to demand only
  • Loading branch information
reinhard-brandstaedter authored Apr 29, 2024
1 parent 68e731b commit 29f4d0c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 26 deletions.
12 changes: 9 additions & 3 deletions src/solarflow/dtus.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __str__(self):
return ' '.join(f'{yellow}INV: \
AC:{self.getCurrentACPower():>3.1f}W, AC_Prediction: {self.getPredictedACPower():>3.1f}W, \
DC:{self.getCurrentDCPower():>3.1f}W, DC_prediction: {self.getPredictedDCPower():>3.1f}W ({chPower}), \
L:{self.limitAbsolute:>3}W [{self.maxPower:>3}W]{reset}'.split())
L:{self.limitAbsolute:>3.0f}W ({self.getChannelLimit():.1f}W/channel) [{self.maxPower:>3.0f}W]{reset}'.split())

def subscribe(self, topics):
topics.append(f'solarflow-hub/+/control/dryRun')
Expand Down Expand Up @@ -191,6 +191,9 @@ def setDryRun(self,value):
def isWithin(self,a,b,range:int):
return b-range < a < b+range

def getChannelLimit(self) ->int:
return self.getLimit()/(len(self.channelsDCPower)-1)

def setLimit(self, limit:int):
# failsafe, never set the inverter limit to 0, keep a minimum
# see: https://github.com/lumapu/ahoy/issues/1079
Expand All @@ -210,22 +213,25 @@ def setLimit(self, limit:int):
# it could be that maxPower has not yet been detected resulting in a zero limit
inv_limit = 10 if inv_limit < 10 else int(inv_limit)

withinRange = 6
# failsafe: ensure that the inverter's AC output doesn't exceed acceptable legal limits
# note this could mean that the inverter limit is still higher but it ensures that not too much power is generated

if self.getCurrentACPower() > self.acLimit and inv_limit > self.acLimit:
# decrease inverter limit slowly
inv_limit = self.limitAbsolute - 4
inv_limit = self.limitAbsolute - 8
withinRange = 0
log.info(f'Current inverter AC output ({self.getCurrentACPower()}) is higher than configured output limit ({self.acLimit}), reducing limit to {inv_limit}')

# failsafe: if the current AC output is close to the AC limit do not increase the invert limit too much
if self.getCurrentACPower() < self.acLimit and self.isWithin(self.getCurrentACPower(), self.acLimit, 6):
# only increase inverter limit a little bit
inv_limit = self.limitAbsolute + 2
withinRange = 0
log.info(f'Current inverter AC output ({self.getCurrentACPower()}) is close to the configured AC output limit ({self.acLimit}), slow limit increase to {inv_limit}')

#if self.limitAbsolute != inv_limit and self.reachable:
if not self.isWithin(inv_limit,self.limitAbsolute,6) and self.reachable:
if not self.isWithin(inv_limit,self.limitAbsolute,withinRange) and self.reachable:
(not self.dryrun) and self.client.publish(self.limit_nonpersistent_absolute,f'{inv_limit}{self.limit_unit}')
#log.info(f'Setting inverter output limit to {inv_limit} W ({limit} x 1 / ({len(self.sf_inverter_channels)}/{len(self.channelsDCPower)-1})')
log.info(f'{"[DRYRUN] " if self.dryrun else ""}Setting inverter output limit to {inv_limit}W (1 min moving average of {limit}W x {len(self.channelsDCPower)-1})')
Expand Down
50 changes: 33 additions & 17 deletions src/solarflow/solarflow-control.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def on_connect(client, userdata, flags, rc):
hub = client._userdata['hub']
hub.subscribe()
hub.setBuzzer(False)
if hub.control_bypass:
hub.setBypass(False)
inv = client._userdata['dtu']
inv.subscribe()
smt = client._userdata['smartmeter']
Expand Down Expand Up @@ -187,7 +189,7 @@ def getDirectPanelLimit(inv, hub, smt) -> int:
direct_panel_power = inv.getDirectACPower() + inv.getHubACPower() if hub.getBypass() else 0
if direct_panel_power < MAX_INVERTER_LIMIT:
dc_values = inv.getDirectDCPowerValues() + inv.getHubDCPowerValues() if hub.getBypass() else inv.getDirectDCPowerValues()
return math.ceil(max(dc_values) * (inv.getEfficiency()/100)) if smt.getPower()- smt.zero_offset < 0 else limitedRise(max(dc_values) * (inv.getEfficiency()/100))
return math.ceil(max(dc_values) * (inv.getEfficiency()/100)) if smt.getPower() - smt.zero_offset < 0 else limitedRise(max(dc_values) * (inv.getEfficiency()/100))
else:
return int(MAX_INVERTER_LIMIT*(inv.getNrHubChannels()/inv.getNrTotalChannels()))

Expand All @@ -207,12 +209,12 @@ def getSFPowerLimit(hub, demand) -> int:
if hub.bypass:
path += "0."
# leave bypass after sunset/offset
if (now < (sunrise + sunrise_off) or now > sunset - sunset_off):
if (now < (sunrise + sunrise_off) or now > sunset - sunset_off) and hub.control_bypass:
hub.setBypass(False)
path += "1."
else:
path += "2."
limit = demand
limit = hub.getInverseMaxPower()

if not hub.bypass:
if hub_solarpower - demand > MIN_CHARGE_POWER:
Expand Down Expand Up @@ -241,6 +243,9 @@ def getSFPowerLimit(hub, demand) -> int:
if now > sunrise and now < sunrise + td:
hub.setSunriseSoC(hub_electricLevel)
log.info(f'Good morning! We have consumed {hub.getNightConsumption()}% of the battery tonight!')
# sometimes bypass resets to default (auto)
if hub.control_bypass:
hub.setBypass(False)

log.info(f'Based on time, solarpower ({hub_solarpower:4.1f}W) minimum charge power ({MIN_CHARGE_POWER}W) and bypass state ({hub.bypass}), hub could contribute {limit:4.1f}W - Decision path: {path}')
return int(limit)
Expand Down Expand Up @@ -287,26 +292,37 @@ def limitHomeInput(client: mqtt_client):
if demand < direct_panel_power:
# we can conver demand with direct panel power, just use all of it
log.info(f'Direct connected panels ({direct_panel_power:.1f}W) can cover demand ({demand:.1f}W)')
#direct_limit = getDirectPanelLimit(inv,hub,smt)
# keep inverter limit where it is, no need to change
direct_limit = getDirectPanelLimit(inv,hub,smt)
hub_limit = hub.setOutputLimit(0)
else:
# we need contribution from hub, if possible
# we need contribution from hub, if possible and/or try to get more from direct panels
log.info(f'Direct connected panels ({direct_panel_power:.1f}W) can\'t cover demand ({demand:.1f}W), trying to get {hub_contribution_ask:.1f}W from hub.')
if hub_contribution_ask > 5:
# check what hub is currently willing to contribute
sf_contribution = getSFPowerLimit(hub,hub_contribution_ask)

# if the hub's contribution (per channel) is larger than what the direct panels max is delivering (night, low light)
# then we can open the hub to max limit and use the inverter to limit it's output (more precise)
if sf_contribution/inv.getNrHubChannels() >= max(inv.getDirectDCPowerValues()) * (inv.getEfficiency()/100):
log.info(f'Hub should contribute more ({sf_contribution:.1f}W) than what we currently get from panels ({direct_panel_power:.1f}W), we will use the inverter for fast/precise limiting!')
hub_limit = hub.setOutputLimit(hub.getInverseMaxPower())
direct_limit = sf_contribution/inv.getNrHubChannels()
else:
hub_limit = hub.setOutputLimit(sf_contribution)
log.info(f'Solarflow is willing to contribute {min(hub_limit,hub_contribution_ask):.1f}W of the requested {hub_contribution_ask:.1f}!')
# is there potentially more to get from direct panels?
# if the direct channel power is below what is theoretically possible, it is worth trying to increase the limit

# if the max of direct channel power is close to the channel limit we should increase the limit first to get pot more from direct panels
if inv.isWithin(max(inv.getDirectDCPowerValues()),inv.getChannelLimit(),10):
log.info(f'The current max direct channel power {max(inv.getDirectDCPowerValues()):.1f}W is close to the current channel limit {inv.getChannelLimit():.1f}, trying to get more from direct panels.')
hub_limit = hub.getLimit()
direct_limit = getDirectPanelLimit(inv,hub,smt)
log.info(f'Direct connected panel limit is {direct_limit}W.')
else:
# check what hub is currently willing to contribute
sf_contribution = getSFPowerLimit(hub,hub_contribution_ask)

# if the hub's contribution (per channel) is larger than what the direct panels max is delivering (night, low light)
# then we can open the hub to max limit and use the inverter to limit it's output (more precise)
if sf_contribution/inv.getNrHubChannels() >= max(inv.getDirectDCPowerValues()) * (inv.getEfficiency()/100):
log.info(f'Hub should contribute more ({sf_contribution:.1f}W) than what we currently get from panels ({direct_panel_power:.1f}W), we will use the inverter for fast/precise limiting!')
hub_limit = hub.setOutputLimit(hub.getInverseMaxPower())
direct_limit = sf_contribution/inv.getNrHubChannels()
else:
hub_limit = hub.setOutputLimit(sf_contribution)
log.info(f'Solarflow is willing to contribute {min(hub_limit,hub_contribution_ask):.1f}W of the requested {hub_contribution_ask:.1f}!')
direct_limit = getDirectPanelLimit(inv,hub,smt)
log.info(f'Direct connected panel limit is {direct_limit}W.')

# likely no sun, not producing, eveything comes from hub
else:
Expand Down
9 changes: 3 additions & 6 deletions src/solarflow/solarflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,12 @@ def updElectricLevel(self, value:int):
self.setBypass(True)

self.lastFullTS = datetime.now()
if self.control_bypass:
log.info(f'Bypass control, turning on bypass!')
self.setBypass(True)
self.client.publish(f'solarflow-hub/{self.deviceId}/control/lastFullTimestamp',int(datetime.timestamp(self.lastFullTS)),retain=True)
self.client.publish(f'solarflow-hub/{self.deviceId}/control/batteryTarget',"discharging",retain=True)
if value == 0:
if self.batteryTarget == "discharging":
log.info(f'Battery is empty: {self.electricLevel}')

self.lastEmptyTS = datetime.now()
self.client.publish(f'solarflow-hub/{self.deviceId}/control/lastEmptyTimestamp',int(datetime.timestamp(self.lastEmptyTS)),retain=True)
self.client.publish(f'solarflow-hub/{self.deviceId}/control/batteryTarget',"charging",retain=True)
Expand Down Expand Up @@ -360,8 +357,8 @@ def setBuzzer(self, state: bool):
self.client.publish(self.property_topic,json.dumps(buzzer))

def setBypass(self, state: bool):
buzzer = {"properties": { "passMode": 2 if state else 1 }}
self.client.publish(self.property_topic,json.dumps(buzzer))
passmode = {"properties": { "passMode": 2 if state else 1 }}
self.client.publish(self.property_topic,json.dumps(passmode))
if not state:
self.bypass = state # required for cases where we can't wait on confirmation on turning bypass off

Expand Down

0 comments on commit 29f4d0c

Please sign in to comment.