Merge branch 'inverter-settings' into development
This commit is contained in:
commit
43dc10b868
263
docs/PowerLimiterInverterStates.drawio
Normal file
263
docs/PowerLimiterInverterStates.drawio
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram name="Page-1" id="b5b7bab2-c9e2-2cf4-8b2a-24fd1a2a6d21">
|
||||||
|
<mxGraphModel dx="1539" dy="843" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="none" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="6e0c8c40b5770093-72" value="" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=194;tabHeight=22;tabPosition=left;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=10;align=center;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="150" y="114.5" width="1090" height="1065.5" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6e0c8c40b5770093-73" value="<div style="color: rgb(212, 212, 212); background-color: rgb(30, 30, 30); font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; font-size: 12px; line-height: 18px;"><span style="color: #4ec9b0;">PowerLimiterClass</span>::<span style="color: #dcdcaa;">loop</span>()</div>" style="text;html=1;align=left;verticalAlign=top;spacingTop=-4;fontSize=10;fontFamily=Verdana" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="150" y="114.5" width="130" height="20" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="sqCMRMHiXPc9LqBIY9SA-1" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="475" y="50" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="sqCMRMHiXPc9LqBIY9SA-2" value="discover state on initial cal or after enabling powerLimiterl" style="html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-1" target="2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="205" y="370" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="sqCMRMHiXPc9LqBIY9SA-6" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_OFF</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry / stop inverter, limit lower limit<br>do / nothing<br>exit / start inverter</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="170" y="585" width="200" height="100" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="sqCMRMHiXPc9LqBIY9SA-7" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_CONSUME_SOLAR_POWER_ONLY</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry /<br>do / setNewLimit<br>exit /</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="525" y="590" width="270" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="sqCMRMHiXPc9LqBIY9SA-8" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_NORMAL_OPERATION</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry /<br>do / setNewLimit<br>exit /</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="970" y="585" width="200" height="100" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="2" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_DISOVER</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry /&nbsp;<br>do / nothing<br>exit /&nbsp;</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="390" y="180" width="200" height="100" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="414" y="780" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="<p><span style="font-size: 11px; background-color: rgb(255, 255, 255);">!Inverter-&gt;isProducing || isStopThresholdReached</span></p>" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="425" y="300" width="130" height="130" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="<span style="font-size: 11px; background-color: rgb(255, 255, 255);">canUseDirectSolarPower</span>" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="595" y="390" width="130" height="120" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="10" target="sqCMRMHiXPc9LqBIY9SA-7" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="620" y="540" as="sourcePoint"/>
|
||||||
|
<mxPoint x="670" y="490" as="targetPoint"/>
|
||||||
|
<Array as="points"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="11" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.075" y="1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="2" target="7" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="484" y="280" as="sourcePoint"/>
|
||||||
|
<mxPoint x="420" y="220" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryPerimeter=0;" parent="1" source="7" target="sqCMRMHiXPc9LqBIY9SA-6" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="570" y="290" as="sourcePoint"/>
|
||||||
|
<mxPoint x="400" y="330" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="270" y="365"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="13" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.3049" relative="1" as="geometry">
|
||||||
|
<mxPoint x="80" y="-105" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="7" target="10" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="360" y="370" as="sourcePoint"/>
|
||||||
|
<mxPoint x="280" y="595" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="660" y="365"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="15" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.3049" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-45" y="-15" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="18" value="" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;edgeStyle=orthogonalEdgeStyle;" parent="1" source="10" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="760" y="340" as="sourcePoint"/>
|
||||||
|
<mxPoint x="810" y="290" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="19" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="18" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.142" y="2" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-239" y="-8" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="isStopThresholdReached" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="730" width="130" height="130" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="22" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="21" target="24" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="620" y="540" as="sourcePoint"/>
|
||||||
|
<mxPoint x="670" y="490" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="285" y="995"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="22" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.702" y="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="39" y="91" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.581;exitY=0.998;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-6" target="21" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="480" y="810" as="sourcePoint"/>
|
||||||
|
<mxPoint x="530" y="760" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="24" value="isStartThresholdReached &amp;&amp; newPowerLimit &gt; lowerPowerLimit" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="595" y="930" width="130" height="130" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="25" value="cnUseDirectSolarPower &amp;&amp; newPowerLimit &gt; lowerPowerLimit" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="595" y="745" width="130" height="130" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="26" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="24" target="25" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="860" y="950" as="sourcePoint"/>
|
||||||
|
<mxPoint x="750" y="840" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="32" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="26" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.0933" y="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28" value="yes" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.113;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="24" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||||
|
<mxGeometry x="-0.2308" y="10" width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="620" y="510" as="sourcePoint"/>
|
||||||
|
<mxPoint x="670" y="460" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="993" y="1000"/>
|
||||||
|
</Array>
|
||||||
|
<mxPoint x="1" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="29" value="" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="21" target="5" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="350" y="830" as="sourcePoint"/>
|
||||||
|
<mxPoint x="400" y="780" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="29" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="3" y="-15" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="33" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="25" target="sqCMRMHiXPc9LqBIY9SA-7" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="620" y="480" as="sourcePoint"/>
|
||||||
|
<mxPoint x="670" y="430" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="34" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="33" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.424" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="25" target="38" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="390" y="910" as="sourcePoint"/>
|
||||||
|
<mxPoint x="440" y="860" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="36" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="35" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.4667" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-19" y="-5" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="38" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="795" y="795" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-38" value="" style="endArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-7" target="sqCMRMHiXPc9LqBIY9SA-6" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="570" y="820" as="sourcePoint"/>
|
||||||
|
<mxPoint x="620" y="770" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-39" value="!Inverter-&gt;isProducing ||<br>isStopThresholdReached ||<br>newLimit &lt; lowerLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-38" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.2129" y="-2" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-14" y="-33" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-40" value="!canUseDirectSolarPower ||<br>isStartThresholdReached" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-7" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||||
|
<mxGeometry x="-0.0286" y="25" width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="570" y="560" as="sourcePoint"/>
|
||||||
|
<mxPoint x="620" y="510" as="targetPoint"/>
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-41" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.22;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.354;exitY=1.025;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-8" target="sqCMRMHiXPc9LqBIY9SA-6" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="1040" y="680" as="sourcePoint"/>
|
||||||
|
<mxPoint x="890" y="750" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="1040" y="1070"/>
|
||||||
|
<mxPoint x="214" y="1070"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-42" value="!Inverter-&gt;isProducing ||<br>isStopThresholdReached ||<br>newLimit &lt; lowerLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-41" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.2129" y="-2" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-151" y="32" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-43" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1055" y="799.7" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-44" value="" style="endArrow=classic;html=1;exitX=0.82;exitY=1.003;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitPerimeter=0;" parent="1" target="JKiNQljIdbqBsyxyxwz1-43" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="1069.0000000000002" y="685" as="sourcePoint"/>
|
||||||
|
<mxPoint x="1119" y="749.7" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-45" value="inTargetRange do nothing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-44" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-46" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="530" y="795" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-47" value="" style="endArrow=classic;html=1;exitX=0.82;exitY=1.003;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitPerimeter=0;" parent="1" target="JKiNQljIdbqBsyxyxwz1-46" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="544" y="680" as="sourcePoint"/>
|
||||||
|
<mxPoint x="594" y="745" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-48" value="after setNewLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-47" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-49" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1110" y="800" width="30" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-50" value="" style="endArrow=classic;html=1;exitX=0.82;exitY=1.003;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitPerimeter=0;" parent="1" target="JKiNQljIdbqBsyxyxwz1-49" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="1124.0000000000002" y="685.3" as="sourcePoint"/>
|
||||||
|
<mxPoint x="1174" y="750" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JKiNQljIdbqBsyxyxwz1-51" value="after setNewLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-50" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="-0.1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="16" y="33" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
BIN
docs/PowerLimiterInverterStates.png
Normal file
BIN
docs/PowerLimiterInverterStates.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@ -18,7 +18,7 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
void loop();
|
void loop();
|
||||||
private:
|
private:
|
||||||
veStruct _kvFrame;
|
veStruct _kvFrame{};
|
||||||
uint32_t _lastPublish;
|
uint32_t _lastPublish;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,14 @@
|
|||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
enum PowerLimiterStates {
|
||||||
|
STATE_DISCOVER = 0,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_CONSUME_SOLAR_POWER_ONLY,
|
||||||
|
STATE_NORMAL_OPERATION
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class PowerLimiterClass {
|
class PowerLimiterClass {
|
||||||
public:
|
public:
|
||||||
void init();
|
void init();
|
||||||
@ -18,13 +26,15 @@ private:
|
|||||||
uint32_t _lastLoop;
|
uint32_t _lastLoop;
|
||||||
uint32_t _lastPowerMeterUpdate;
|
uint32_t _lastPowerMeterUpdate;
|
||||||
uint16_t _lastRequestedPowerLimit;
|
uint16_t _lastRequestedPowerLimit;
|
||||||
bool _consumeSolarPowerOnly;
|
u_int8_t _plState = STATE_DISCOVER;
|
||||||
|
|
||||||
float _powerMeter1Power;
|
float _powerMeter1Power;
|
||||||
float _powerMeter2Power;
|
float _powerMeter2Power;
|
||||||
float _powerMeter3Power;
|
float _powerMeter3Power;
|
||||||
|
|
||||||
bool canUseDirectSolarPower();
|
bool canUseDirectSolarPower();
|
||||||
|
int32_t calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly);
|
||||||
|
void setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, uint32_t newPowerLimit);
|
||||||
uint16_t getDirectSolarPower();
|
uint16_t getDirectSolarPower();
|
||||||
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
||||||
bool isStartThresholdReached(std::shared_ptr<InverterAbstract> inverter);
|
bool isStartThresholdReached(std::shared_ptr<InverterAbstract> inverter);
|
||||||
|
|||||||
@ -37,7 +37,7 @@ typedef struct {
|
|||||||
double V; // battery voltage in V
|
double V; // battery voltage in V
|
||||||
double I; // battery current in A
|
double I; // battery current in A
|
||||||
double VPV; // panel voltage in V
|
double VPV; // panel voltage in V
|
||||||
double PPV; // panel power in W
|
uint16_t PPV; // panel power in W
|
||||||
double H19; // yield total kWh
|
double H19; // yield total kWh
|
||||||
double H20; // yield today kWh
|
double H20; // yield today kWh
|
||||||
uint16_t H21; // maximum power today W
|
uint16_t H21; // maximum power today W
|
||||||
@ -61,13 +61,13 @@ public:
|
|||||||
String getOrAsString(uint32_t offReason); // off reason as string
|
String getOrAsString(uint32_t offReason); // off reason as string
|
||||||
String getMpptAsString(uint8_t mppt); // state of mppt as string
|
String getMpptAsString(uint8_t mppt); // state of mppt as string
|
||||||
|
|
||||||
veStruct veFrame; // public map for received name and value pairs
|
veStruct veFrame{}; // public struct for received name and value pairs
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setLastUpdate(); // set timestampt after successful frame read
|
void setLastUpdate(); // set timestampt after successful frame read
|
||||||
void rxData(uint8_t inbyte); // byte of serial data
|
void rxData(uint8_t inbyte); // byte of serial data
|
||||||
void textRxEvent(char *, char *);
|
void textRxEvent(char *, char *);
|
||||||
void frameEndEvent(bool); // copy temp map to public map
|
void frameEndEvent(bool); // copy temp struct to public struct
|
||||||
void logE(const char *, const char *);
|
void logE(const char *, const char *);
|
||||||
bool hexRxEvent(uint8_t);
|
bool hexRxEvent(uint8_t);
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ private:
|
|||||||
char * _textPointer; // pointer to the private buffer we're writing to, name or value
|
char * _textPointer; // pointer to the private buffer we're writing to, name or value
|
||||||
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
|
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
|
||||||
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
|
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
|
||||||
veStruct _tmpFrame; // private struct for received name and value pairs
|
veStruct _tmpFrame{}; // private struct for received name and value pairs
|
||||||
unsigned long _pollInterval;
|
unsigned long _pollInterval;
|
||||||
unsigned long _lastPoll;
|
unsigned long _lastPoll;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -39,7 +39,6 @@ void PowerLimiterClass::init()
|
|||||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||||
}
|
}
|
||||||
|
|
||||||
_consumeSolarPowerOnly = true;
|
|
||||||
_lastCommandSent = 0;
|
_lastCommandSent = 0;
|
||||||
_lastLoop = 0;
|
_lastLoop = 0;
|
||||||
_lastPowerMeterUpdate = 0;
|
_lastPowerMeterUpdate = 0;
|
||||||
@ -74,50 +73,48 @@ void PowerLimiterClass::loop()
|
|||||||
|| !Hoymiles.getRadio()->isIdle()
|
|| !Hoymiles.getRadio()->isIdle()
|
||||||
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|
||||||
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
|
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
|
||||||
|
if (!config.PowerLimiter_Enabled)
|
||||||
|
_plState = STATE_DISCOVER; // ensure STATE_DISCOVER is set, if PowerLimiter will be enabled.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastLoop = millis();
|
_lastLoop = millis();
|
||||||
|
|
||||||
std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
|
std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
|
||||||
|
|
||||||
if (inverter == nullptr || !inverter->isReachable()) {
|
if (inverter == nullptr || !inverter->isReachable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
|
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
|
||||||
|
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||||
|
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
||||||
|
|
||||||
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
|
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
|
||||||
uint32_t victronChargePower = this->getDirectSolarPower();
|
|
||||||
|
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s \r\n", victronChargePower, efficency, _consumeSolarPowerOnly ? "true" : "false");
|
|
||||||
|
|
||||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n",
|
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n",
|
||||||
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
|
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t powerMeter = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
|
||||||
|
|
||||||
if (inverter->isProducing()) {
|
while(true) {
|
||||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
switch(_plState) {
|
||||||
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
case STATE_DISCOVER:
|
||||||
|
if (!inverter->isProducing() || isStopThresholdReached(inverter)) {
|
||||||
if ((_consumeSolarPowerOnly && isStartThresholdReached(inverter))) {
|
_plState = STATE_OFF;
|
||||||
// The battery is full enough again, use the full battery power from now on.
|
|
||||||
_consumeSolarPowerOnly = false;
|
|
||||||
} else if (!_consumeSolarPowerOnly && isStopThresholdReached(inverter) && canUseDirectSolarPower()) {
|
|
||||||
// The battery voltage dropped too low
|
|
||||||
_consumeSolarPowerOnly = true;
|
|
||||||
}
|
}
|
||||||
|
else if (canUseDirectSolarPower()) {
|
||||||
if (isStopThresholdReached(inverter)
|
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||||
|| (_consumeSolarPowerOnly && !canUseDirectSolarPower())) {
|
}
|
||||||
// DC voltage too low, stop the inverter
|
else {
|
||||||
|
_plState = STATE_NORMAL_OPERATION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_OFF:
|
||||||
|
// if on turn off
|
||||||
|
if (inverter->isProducing()) {
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
|
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
|
||||||
dcVoltage, correctedDcVoltage);
|
dcVoltage, correctedDcVoltage);
|
||||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||||
@ -126,80 +123,67 @@ void PowerLimiterClass::loop()
|
|||||||
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||||
_lastRequestedPowerLimit = newPowerLimit;
|
_lastRequestedPowerLimit = newPowerLimit;
|
||||||
|
|
||||||
_lastCommandSent = millis();
|
_lastCommandSent = millis();
|
||||||
_consumeSolarPowerOnly = false;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if ((isStartThresholdReached(inverter) || (canUseDirectSolarPower() && (!isStopThresholdReached(inverter))))
|
// do nothing if battery is empty
|
||||||
&& powerMeter >= config.PowerLimiter_LowerPowerLimit) {
|
if (isStopThresholdReached(inverter))
|
||||||
|
return;
|
||||||
|
// check for possible state changes
|
||||||
|
if (isStartThresholdReached(inverter) && calcPowerLimit(inverter, false) >= config.PowerLimiter_LowerPowerLimit) {
|
||||||
|
_plState = STATE_NORMAL_OPERATION;
|
||||||
|
}
|
||||||
|
else if (canUseDirectSolarPower() && calcPowerLimit(inverter, true) >= config.PowerLimiter_LowerPowerLimit) {
|
||||||
|
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// inverter on on state change
|
||||||
|
if (_plState != STATE_OFF) {
|
||||||
// DC voltage high enough, start the inverter
|
// DC voltage high enough, start the inverter
|
||||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||||
_lastCommandSent = millis();
|
|
||||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
|
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
|
||||||
|
|
||||||
// In this mode, the inverter should consume the current solar power only
|
|
||||||
// and not drain additional power from the battery
|
|
||||||
if (!isStartThresholdReached(inverter)) {
|
|
||||||
_consumeSolarPowerOnly = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t newPowerLimit = 0;
|
|
||||||
|
|
||||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
|
||||||
newPowerLimit = powerMeter;
|
|
||||||
// check if grid power consumption is within the upper an lower threshold of the target consumption
|
|
||||||
if (!_consumeSolarPowerOnly &&
|
|
||||||
newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
|
||||||
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis))
|
|
||||||
return;
|
|
||||||
else {
|
|
||||||
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
|
|
||||||
// If the inverter the behind the power meter (part of measurement),
|
|
||||||
// the produced power of this inverter has also to be taken into account.
|
|
||||||
// We don't use FLD_PAC from the statistics, because that
|
|
||||||
// data might be too old and unrelieable.
|
|
||||||
newPowerLimit += _lastRequestedPowerLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
|
||||||
|
|
||||||
uint16_t upperPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
|
||||||
if (_consumeSolarPowerOnly && (upperPowerLimit > victronChargePower)) {
|
|
||||||
// Battery voltage too low, use Victron solar power (corrected by efficency factor) only
|
|
||||||
upperPowerLimit = victronChargePower * (efficency / 100.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newPowerLimit > upperPowerLimit)
|
|
||||||
newPowerLimit = upperPowerLimit;
|
|
||||||
else if (newPowerLimit < (uint16_t)config.PowerLimiter_LowerPowerLimit) {
|
|
||||||
newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
|
||||||
// stop the inverter
|
|
||||||
MessageOutput.println("[PowerLimiterClass::loop] Power limit below lower power limit. Stopping inverter...");
|
|
||||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] powerMeter: %d W lastRequestedPowerLimit: %d\r\n",
|
|
||||||
powerMeter, _lastRequestedPowerLimit);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the power meter values are older than 30 seconds,
|
|
||||||
// set the limit to config.PowerLimiter_LowerPowerLimit for safety reasons.
|
|
||||||
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
|
||||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
|
||||||
_lastRequestedPowerLimit = newPowerLimit;
|
|
||||||
|
|
||||||
_lastCommandSent = millis();
|
_lastCommandSent = millis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case STATE_CONSUME_SOLAR_POWER_ONLY: {
|
||||||
|
int32_t newPowerLimit = calcPowerLimit(inverter, true);
|
||||||
|
if (!inverter->isProducing()
|
||||||
|
|| isStopThresholdReached(inverter)
|
||||||
|
|| newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||||
|
_plState = STATE_OFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!canUseDirectSolarPower() || isStartThresholdReached(inverter)) {
|
||||||
|
_plState = STATE_NORMAL_OPERATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setNewPowerLimit(inverter, newPowerLimit);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATE_NORMAL_OPERATION: {
|
||||||
|
int32_t newPowerLimit = calcPowerLimit(inverter, false);
|
||||||
|
if (!inverter->isProducing()
|
||||||
|
|| isStopThresholdReached(inverter)
|
||||||
|
|| newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||||
|
_plState = STATE_OFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// check if grid power consumption is within the upper an lower threshold of the target consumption
|
||||||
|
else if (newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||||
|
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNewPowerLimit(inverter, newPowerLimit);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerLimiterClass::canUseDirectSolarPower()
|
bool PowerLimiterClass::canUseDirectSolarPower()
|
||||||
@ -219,13 +203,60 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly)
|
||||||
|
{
|
||||||
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
|
int32_t newPowerLimit = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||||
|
|
||||||
|
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
||||||
|
uint32_t victronChargePower = this->getDirectSolarPower();
|
||||||
|
uint32_t adjustedVictronChargePower = victronChargePower * (efficency > 0.0 ? (efficency / 100.0) : 1.0); // if inverter is off, use 1.0
|
||||||
|
|
||||||
|
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s \r\n", victronChargePower, efficency, consumeSolarPowerOnly ? "true" : "false");
|
||||||
|
|
||||||
|
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||||
|
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
|
||||||
|
// If the inverter the behind the power meter (part of measurement),
|
||||||
|
// the produced power of this inverter has also to be taken into account.
|
||||||
|
// We don't use FLD_PAC from the statistics, because that
|
||||||
|
// data might be too old and unrelieable.
|
||||||
|
newPowerLimit += _lastRequestedPowerLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
||||||
|
|
||||||
|
uint16_t upperPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
||||||
|
if (consumeSolarPowerOnly && (upperPowerLimit > adjustedVictronChargePower)) {
|
||||||
|
// Battery voltage too low, use Victron solar power (corrected by efficency factor) only
|
||||||
|
upperPowerLimit = adjustedVictronChargePower;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPowerLimit > upperPowerLimit)
|
||||||
|
newPowerLimit = upperPowerLimit;
|
||||||
|
} else {
|
||||||
|
// If the power meter values are older than 30 seconds,
|
||||||
|
// set the limit to config.PowerLimiter_LowerPowerLimit for safety reasons.
|
||||||
|
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||||
|
}
|
||||||
|
return newPowerLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, uint32_t newPowerLimit)
|
||||||
|
{
|
||||||
|
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
||||||
|
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||||
|
_lastRequestedPowerLimit = newPowerLimit;
|
||||||
|
_lastCommandSent = millis();
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t PowerLimiterClass::getDirectSolarPower()
|
uint16_t PowerLimiterClass::getDirectSolarPower()
|
||||||
{
|
{
|
||||||
if (!canUseDirectSolarPower()) {
|
if (!canUseDirectSolarPower()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return round(VeDirect.veFrame.PPV);
|
return VeDirect.veFrame.PPV;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)
|
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user