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 loop();
|
||||
private:
|
||||
veStruct _kvFrame;
|
||||
veStruct _kvFrame{};
|
||||
uint32_t _lastPublish;
|
||||
};
|
||||
|
||||
|
||||
@ -7,6 +7,14 @@
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
|
||||
enum PowerLimiterStates {
|
||||
STATE_DISCOVER = 0,
|
||||
STATE_OFF,
|
||||
STATE_CONSUME_SOLAR_POWER_ONLY,
|
||||
STATE_NORMAL_OPERATION
|
||||
};
|
||||
|
||||
|
||||
class PowerLimiterClass {
|
||||
public:
|
||||
void init();
|
||||
@ -18,13 +26,15 @@ private:
|
||||
uint32_t _lastLoop;
|
||||
uint32_t _lastPowerMeterUpdate;
|
||||
uint16_t _lastRequestedPowerLimit;
|
||||
bool _consumeSolarPowerOnly;
|
||||
u_int8_t _plState = STATE_DISCOVER;
|
||||
|
||||
float _powerMeter1Power;
|
||||
float _powerMeter2Power;
|
||||
float _powerMeter3Power;
|
||||
|
||||
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();
|
||||
float getLoadCorrectedVoltage(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 I; // battery current in A
|
||||
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 H20; // yield today kWh
|
||||
uint16_t H21; // maximum power today W
|
||||
@ -61,13 +61,13 @@ public:
|
||||
String getOrAsString(uint32_t offReason); // off reason 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:
|
||||
void setLastUpdate(); // set timestampt after successful frame read
|
||||
void rxData(uint8_t inbyte); // byte of serial data
|
||||
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 *);
|
||||
bool hexRxEvent(uint8_t);
|
||||
|
||||
@ -77,7 +77,7 @@ private:
|
||||
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 _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 _lastPoll;
|
||||
};
|
||||
|
||||
@ -39,8 +39,7 @@ void PowerLimiterClass::init()
|
||||
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;
|
||||
_lastPowerMeterUpdate = 0;
|
||||
_lastRequestedPowerLimit = 0;
|
||||
@ -74,132 +73,117 @@ void PowerLimiterClass::loop()
|
||||
|| !Hoymiles.getRadio()->isIdle()
|
||||
|| (millis() - _lastCommandSent) < (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;
|
||||
}
|
||||
|
||||
_lastLoop = millis();
|
||||
|
||||
std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
|
||||
|
||||
if (inverter == nullptr || !inverter->isReachable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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)) {
|
||||
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());
|
||||
}
|
||||
|
||||
int32_t powerMeter = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
|
||||
if (inverter->isProducing()) {
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
||||
while(true) {
|
||||
switch(_plState) {
|
||||
case STATE_DISCOVER:
|
||||
if (!inverter->isProducing() || isStopThresholdReached(inverter)) {
|
||||
_plState = STATE_OFF;
|
||||
}
|
||||
else if (canUseDirectSolarPower()) {
|
||||
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||
}
|
||||
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",
|
||||
dcVoltage, correctedDcVoltage);
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
||||
|
||||
if ((_consumeSolarPowerOnly && isStartThresholdReached(inverter))) {
|
||||
// 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;
|
||||
}
|
||||
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
_lastCommandSent = millis();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStopThresholdReached(inverter)
|
||||
|| (_consumeSolarPowerOnly && !canUseDirectSolarPower())) {
|
||||
// DC voltage too low, stop the inverter
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
|
||||
dcVoltage, correctedDcVoltage);
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
||||
// do nothing if battery is empty
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
|
||||
_lastCommandSent = millis();
|
||||
_consumeSolarPowerOnly = false;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ((isStartThresholdReached(inverter) || (canUseDirectSolarPower() && (!isStopThresholdReached(inverter))))
|
||||
&& powerMeter >= config.PowerLimiter_LowerPowerLimit) {
|
||||
// DC voltage high enough, start the inverter
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||
_lastCommandSent = millis();
|
||||
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;
|
||||
// inverter on on state change
|
||||
if (_plState != STATE_OFF) {
|
||||
// DC voltage high enough, start the inverter
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool PowerLimiterClass::canUseDirectSolarPower()
|
||||
@ -219,13 +203,60 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
||||
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()
|
||||
{
|
||||
if (!canUseDirectSolarPower()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(VeDirect.veFrame.PPV);
|
||||
return VeDirect.veFrame.PPV;
|
||||
}
|
||||
|
||||
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user