Merge branch 'refactoring-async-stream' of https://github.com/s-allius/tsun-gen3-proxy into titan-scan

This commit is contained in:
Stefan Allius
2024-10-06 21:28:12 +02:00
31 changed files with 1763 additions and 1222 deletions

View File

@@ -4,526 +4,257 @@
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: G Pages: 1 -->
<svg width="751pt" height="1880pt"
viewBox="0.00 0.00 751.00 1880.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1876)">
<svg width="626pt" height="966pt"
viewBox="0.00 0.00 625.50 966.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 962)">
<title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1876 747,-1876 747,4 -4,4"/>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-962 621.5,-962 621.5,4 -4,4"/>
<!-- A0 -->
<g id="node1" class="node">
<title>A0</title>
<polygon fill="#fff8dc" stroke="#000000" points="295.6964,-1848 187.3036,-1848 187.3036,-1812 301.6964,-1812 301.6964,-1842 295.6964,-1848"/>
<polyline fill="none" stroke="#000000" points="295.6964,-1848 295.6964,-1842 "/>
<polyline fill="none" stroke="#000000" points="301.6964,-1842 295.6964,-1842 "/>
<text text-anchor="middle" x="244.5" y="-1833" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
<text text-anchor="middle" x="244.5" y="-1821" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
<polygon fill="#fff8dc" stroke="#000000" points="191.6964,-934 83.3036,-934 83.3036,-898 197.6964,-898 197.6964,-928 191.6964,-934"/>
<polyline fill="none" stroke="#000000" points="191.6964,-934 191.6964,-928 "/>
<polyline fill="none" stroke="#000000" points="197.6964,-928 191.6964,-928 "/>
<text text-anchor="middle" x="140.5" y="-919" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
<text text-anchor="middle" x="140.5" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
</g>
<!-- A1 -->
<g id="node2" class="node">
<title>A1</title>
<polygon fill="none" stroke="#000000" points="428.5,-904 428.5,-948 550.5,-948 550.5,-904 428.5,-904"/>
<text text-anchor="start" x="479.777" y="-929" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
<text text-anchor="start" x="456.9815" y="-917" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;Singleton&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="428.5,-848 428.5,-904 550.5,-904 550.5,-848 428.5,-848"/>
<text text-anchor="start" x="446.9875" y="-885" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;ha_restarts</text>
<text text-anchor="start" x="454.7665" y="-873" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__client</text>
<text text-anchor="start" x="438.3735" y="-861" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__cb_MqttIsUp</text>
<polygon fill="none" stroke="#000000" points="428.5,-804 428.5,-848 550.5,-848 550.5,-804 428.5,-804"/>
<text text-anchor="start" x="451.436" y="-829" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish()</text>
<text text-anchor="start" x="455.6045" y="-817" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;close()</text>
</g>
<!-- A2 -->
<g id="node3" class="node">
<title>A2</title>
<polygon fill="none" stroke="#000000" points="345.5,-620 345.5,-652 641.5,-652 641.5,-620 345.5,-620"/>
<text text-anchor="start" x="476.5535" y="-633" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Inverter</text>
<polygon fill="none" stroke="#000000" points="345.5,-504 345.5,-620 641.5,-620 641.5,-504 345.5,-504"/>
<text text-anchor="start" x="469.604" y="-601" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.db_stat</text>
<text text-anchor="start" x="462.9405" y="-589" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.entity_prfx</text>
<text text-anchor="start" x="453.7755" y="-577" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.discovery_prfx</text>
<text text-anchor="start" x="453.2115" y="-565" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_node_id</text>
<text text-anchor="start" x="449.3225" y="-553" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_unique_id</text>
<text text-anchor="start" x="465.1655" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.mqtt:Mqtt</text>
<text text-anchor="start" x="462.9355" y="-517" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<polygon fill="none" stroke="#000000" points="345.5,-472 345.5,-504 641.5,-504 641.5,-472 345.5,-472"/>
<text text-anchor="start" x="355.418" y="-485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote(inv_prot, conn_class)async_publ_mqtt()</text>
</g>
<!-- A1&#45;&gt;A2 -->
<g id="edge3" class="edge">
<title>A1&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M490.5482,-793.7124C491.0521,-754.1609 491.661,-706.3601 492.1946,-664.4723"/>
<polygon fill="#000000" stroke="#000000" points="490.4177,-803.9621 486.0455,-793.9055 490.4814,-798.9625 490.5452,-793.9629 490.5452,-793.9629 490.5452,-793.9629 490.4814,-798.9625 495.0448,-794.0202 490.4177,-803.9621 490.4177,-803.9621"/>
<polygon fill="#000000" stroke="#000000" points="492.1973,-664.2532 488.2741,-658.2027 492.3503,-652.2542 496.2735,-658.3047 492.1973,-664.2532"/>
</g>
<!-- A3 -->
<g id="node4" class="node">
<title>A3</title>
<polygon fill="none" stroke="#000000" points="432.5,-342 432.5,-374 554.5,-374 554.5,-342 432.5,-342"/>
<text text-anchor="start" x="469.8845" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
<polygon fill="none" stroke="#000000" points="432.5,-286 432.5,-342 554.5,-342 554.5,-286 432.5,-286"/>
<text text-anchor="start" x="483.497" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="454.053" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
<text text-anchor="start" x="459.332" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
<polygon fill="none" stroke="#000000" points="432.5,-230 432.5,-286 554.5,-286 554.5,-230 432.5,-230"/>
<text text-anchor="start" x="442.1035" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote()</text>
<text text-anchor="start" x="478.5025" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A2&#45;&gt;A3 -->
<g id="edge1" class="edge">
<title>A2&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M493.5,-461.5694C493.5,-432.385 493.5,-401.1011 493.5,-374.0334"/>
<polygon fill="none" stroke="#000000" points="490.0001,-461.7943 493.5,-471.7943 497.0001,-461.7943 490.0001,-461.7943"/>
<polygon fill="none" stroke="#000000" points="215.5,-926 215.5,-958 331.5,-958 331.5,-926 215.5,-926"/>
<text text-anchor="start" x="225.149" y="-939" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AbstractIterMeta&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="215.5,-906 215.5,-926 331.5,-926 331.5,-906 215.5,-906"/>
<polygon fill="none" stroke="#000000" points="215.5,-874 215.5,-906 331.5,-906 331.5,-874 215.5,-874"/>
<text text-anchor="start" x="255.439" y="-887" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__</text>
</g>
<!-- A4 -->
<g id="node5" class="node">
<title>A4</title>
<polygon fill="none" stroke="#000000" points="109.5,-342 109.5,-374 231.5,-374 231.5,-342 109.5,-342"/>
<text text-anchor="start" x="143.55" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
<polygon fill="none" stroke="#000000" points="109.5,-286 109.5,-342 231.5,-342 231.5,-286 109.5,-286"/>
<text text-anchor="start" x="160.497" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="131.053" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
<text text-anchor="start" x="136.332" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
<polygon fill="none" stroke="#000000" points="109.5,-230 109.5,-286 231.5,-286 231.5,-230 109.5,-230"/>
<text text-anchor="start" x="119.1035" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote()</text>
<text text-anchor="start" x="155.5025" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<polygon fill="none" stroke="#000000" points="178.5,-726 178.5,-758 369.5,-758 369.5,-726 178.5,-726"/>
<text text-anchor="start" x="240.0965" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;InverterIfc&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="178.5,-706 178.5,-726 369.5,-726 369.5,-706 178.5,-706"/>
<polygon fill="none" stroke="#000000" points="178.5,-650 178.5,-706 369.5,-706 369.5,-650 178.5,-650"/>
<text text-anchor="start" x="240.522" y="-687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()&#45;&gt;bool</text>
<text text-anchor="start" x="188.2835" y="-675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;disc(shutdown_started=False)</text>
<text text-anchor="start" x="219.544" y="-663" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;create_remote()</text>
</g>
<!-- A2&#45;&gt;A4 -->
<g id="edge2" class="edge">
<title>A2&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M336.3402,-472.102C291.9351,-449.5364 273.2132,-453.1826 240.5,-422 226.1844,-408.3541 213.8577,-391.1382 203.7145,-374.0392"/>
<polygon fill="none" stroke="#000000" points="334.7789,-475.2356 345.2564,-476.808 338.0464,-469.0449 334.7789,-475.2356"/>
<!-- A1&#45;&gt;A4 -->
<g id="edge1" class="edge">
<title>A1&#45;&gt;A4</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M273.5,-863.7744C273.5,-831.6663 273.5,-790.6041 273.5,-758.1476"/>
<polygon fill="none" stroke="#000000" points="270.0001,-863.8621 273.5,-873.8622 277.0001,-863.8622 270.0001,-863.8621"/>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="none" stroke="#000000" points="146.5,-100 146.5,-132 324.5,-132 324.5,-100 146.5,-100"/>
<text text-anchor="start" x="191.0515" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamServer</text>
<polygon fill="none" stroke="#000000" points="146.5,-68 146.5,-100 324.5,-100 324.5,-68 146.5,-68"/>
<text text-anchor="start" x="187.4325" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote</text>
<polygon fill="none" stroke="#000000" points="146.5,0 146.5,-68 324.5,-68 324.5,0 146.5,0"/>
<text text-anchor="start" x="187.1575" y="-49" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;server_loop()</text>
<text text-anchor="start" x="177.9885" y="-37" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_forward()</text>
<text text-anchor="start" x="156.309" y="-25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish_outstanding_mqtt()</text>
<text text-anchor="start" x="220.5025" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<!-- A2 -->
<g id="node3" class="node">
<title>A2</title>
<polygon fill="none" stroke="#000000" points="441.5,-454 441.5,-498 563.5,-498 563.5,-454 441.5,-454"/>
<text text-anchor="start" x="492.777" y="-479" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
<text text-anchor="start" x="469.9815" y="-467" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;Singleton&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="441.5,-398 441.5,-454 563.5,-454 563.5,-398 441.5,-398"/>
<text text-anchor="start" x="459.9875" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;ha_restarts</text>
<text text-anchor="start" x="467.7665" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__client</text>
<text text-anchor="start" x="451.3735" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__cb_MqttIsUp</text>
<polygon fill="none" stroke="#000000" points="441.5,-354 441.5,-398 563.5,-398 563.5,-354 441.5,-354"/>
<text text-anchor="start" x="464.436" y="-379" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish()</text>
<text text-anchor="start" x="468.6045" y="-367" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;close()</text>
</g>
<!-- A3&#45;&gt;A10 -->
<g id="edge12" class="edge">
<title>A3&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M423.406,-237.883C390.0272,-207.3505 349.9479,-170.6888 315.394,-139.0813"/>
<polygon fill="#000000" stroke="#000000" points="423.5032,-237.9719 430.6302,-239.0701 432.3576,-246.0713 425.2306,-244.9731 423.5032,-237.9719"/>
<polygon fill="#000000" stroke="#000000" points="307.6541,-132.0015 318.0701,-135.4306 311.3435,-135.3762 315.0328,-138.751 315.0328,-138.751 315.0328,-138.751 311.3435,-135.3762 311.9955,-142.0714 307.6541,-132.0015 307.6541,-132.0015"/>
<text text-anchor="middle" x="413.278" y="-237.0738" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
<!-- A3 -->
<g id="node4" class="node">
<title>A3</title>
<polygon fill="none" stroke="#000000" points="387.5,-792 387.5,-824 617.5,-824 617.5,-792 387.5,-792"/>
<text text-anchor="start" x="489.7215" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Proxy</text>
<polygon fill="none" stroke="#000000" points="387.5,-676 387.5,-792 617.5,-792 617.5,-676 387.5,-676"/>
<text text-anchor="start" x="474.1545" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;db_stat</text>
<text text-anchor="start" x="467.491" y="-761" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;entity_prfx</text>
<text text-anchor="start" x="458.326" y="-749" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;discovery_prfx</text>
<text text-anchor="start" x="457.762" y="-737" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;proxy_node_id</text>
<text text-anchor="start" x="453.873" y="-725" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;proxy_unique_id</text>
<text text-anchor="start" x="469.716" y="-713" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;cls&gt;mqtt:Mqtt</text>
<text text-anchor="start" x="471.9355" y="-689" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<polygon fill="none" stroke="#000000" points="387.5,-584 387.5,-676 617.5,-676 617.5,-584 387.5,-584"/>
<text text-anchor="start" x="478.6145" y="-657" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">class_init()</text>
<text text-anchor="start" x="473.334" y="-645" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">class_close()</text>
<text text-anchor="start" x="444.984" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_cb_mqtt_is_up()</text>
<text text-anchor="start" x="397.197" y="-609" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_register_proxy_stat_home_assistant()</text>
<text text-anchor="start" x="406.084" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_publ_mqtt_proxy_stat(key)</text>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="342.5,-82 342.5,-114 480.5,-114 480.5,-82 342.5,-82"/>
<text text-anchor="start" x="368.997" y="-95" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamClient</text>
<polygon fill="none" stroke="#000000" points="342.5,-62 342.5,-82 480.5,-82 480.5,-62 342.5,-62"/>
<polygon fill="none" stroke="#000000" points="342.5,-18 342.5,-62 480.5,-62 480.5,-18 342.5,-18"/>
<text text-anchor="start" x="365.378" y="-43" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;client_loop()</text>
<text text-anchor="start" x="352.324" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_forward())</text>
</g>
<!-- A3&#45;&gt;A11 -->
<g id="edge10" class="edge">
<title>A3&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M468.4517,-229.9099C456.6284,-195.8818 442.7083,-155.8191 431.5897,-123.8193"/>
<polygon fill="#000000" stroke="#000000" points="428.2345,-114.1628 435.7674,-122.1318 429.8756,-118.8858 431.5167,-123.6088 431.5167,-123.6088 431.5167,-123.6088 429.8756,-118.8858 427.266,-125.0858 428.2345,-114.1628 428.2345,-114.1628"/>
<text text-anchor="middle" x="442.1678" y="-125.5107" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A4&#45;&gt;A10 -->
<g id="edge16" class="edge">
<title>A4&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M193.635,-218.0021C200.4711,-193.1818 207.9116,-166.167 214.5982,-141.8895"/>
<polygon fill="#000000" stroke="#000000" points="193.5418,-218.3407 195.805,-225.1874 190.3553,-229.9099 188.0922,-223.0631 193.5418,-218.3407"/>
<polygon fill="#000000" stroke="#000000" points="217.3156,-132.0235 218.9986,-142.8595 215.9878,-136.844 214.6601,-141.6645 214.6601,-141.6645 214.6601,-141.6645 215.9878,-136.844 210.3217,-140.4696 217.3156,-132.0235 217.3156,-132.0235"/>
<text text-anchor="middle" x="187.0196" y="-207.19" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
</g>
<!-- A4&#45;&gt;A11 -->
<g id="edge14" class="edge">
<title>A4&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M204.0248,-229.7777C214.1298,-212.7789 226.361,-195.6557 240.5,-182 274.2553,-149.3986 294.7096,-158.4116 333.5,-132 338.7924,-128.3965 344.1429,-124.4972 349.43,-120.4571"/>
<polygon fill="#000000" stroke="#000000" points="357.6686,-114.0147 352.5631,-123.7196 353.7298,-117.0947 349.7911,-120.1747 349.7911,-120.1747 349.7911,-120.1747 353.7298,-117.0947 347.0191,-116.6298 357.6686,-114.0147 357.6686,-114.0147"/>
<text text-anchor="middle" x="348.5964" y="-128.8387" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
<!-- A3&#45;&gt;A2 -->
<g id="edge9" class="edge">
<title>A3&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M502.5,-571.373C502.5,-549.9571 502.5,-528.339 502.5,-508.5579"/>
<polygon fill="#000000" stroke="#000000" points="502.5001,-571.682 506.5,-577.6821 502.5,-583.682 498.5,-577.682 502.5001,-571.682"/>
<polygon fill="#000000" stroke="#000000" points="502.5,-498.392 507.0001,-508.3919 502.5,-503.392 502.5001,-508.392 502.5001,-508.392 502.5001,-508.392 502.5,-503.392 498.0001,-508.392 502.5,-498.392 502.5,-498.392"/>
</g>
<!-- A5 -->
<g id="node6" class="node">
<title>A5</title>
<polygon fill="none" stroke="#000000" points="320.5,-1840 320.5,-1872 391.5,-1872 391.5,-1840 320.5,-1840"/>
<text text-anchor="start" x="330.445" y="-1853" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">IterRegistry</text>
<polygon fill="none" stroke="#000000" points="320.5,-1820 320.5,-1840 391.5,-1840 391.5,-1820 320.5,-1820"/>
<polygon fill="none" stroke="#000000" points="320.5,-1788 320.5,-1820 391.5,-1820 391.5,-1788 320.5,-1788"/>
<text text-anchor="start" x="337.939" y="-1801" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__</text>
<polygon fill="none" stroke="#000000" points="205.5,-502 205.5,-534 396.5,-534 396.5,-502 205.5,-502"/>
<text text-anchor="start" x="272.66" y="-515" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterBase</text>
<polygon fill="none" stroke="#000000" points="205.5,-386 205.5,-502 396.5,-502 396.5,-386 205.5,-386"/>
<text text-anchor="start" x="281.8335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
<text text-anchor="start" x="270.4355" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<text text-anchor="start" x="290.997" y="-447" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="274.0505" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">config_id:str</text>
<text text-anchor="start" x="247.3785" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:MessageProt</text>
<text text-anchor="start" x="261.553" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
<text text-anchor="start" x="266.832" y="-399" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
<polygon fill="none" stroke="#000000" points="205.5,-318 205.5,-386 396.5,-386 396.5,-318 205.5,-318"/>
<text text-anchor="start" x="267.522" y="-367" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()&#45;&gt;bool</text>
<text text-anchor="start" x="215.2835" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;disc(shutdown_started=False)</text>
<text text-anchor="start" x="246.544" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;create_remote()</text>
<text text-anchor="start" x="240.984" y="-331" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;async_publ_mqtt()</text>
</g>
<!-- A3&#45;&gt;A5 -->
<g id="edge7" class="edge">
<title>A3&#45;&gt;A5</title>
<path fill="none" stroke="#000000" d="M409.1791,-575.5683C399.1409,-561.7533 389.0008,-547.7982 379.1588,-534.2532"/>
<polygon fill="none" stroke="#000000" points="406.3649,-577.6495 415.0747,-583.682 412.0279,-573.5347 406.3649,-577.6495"/>
</g>
<!-- A4&#45;&gt;A5 -->
<g id="edge2" class="edge">
<title>A4&#45;&gt;A5</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M279.7719,-639.4228C282.8086,-608.1559 286.5373,-569.7639 289.991,-534.2034"/>
<polygon fill="none" stroke="#000000" points="276.2531,-639.4473 278.77,-649.7389 283.2203,-640.1241 276.2531,-639.4473"/>
</g>
<!-- A6 -->
<g id="node7" class="node">
<title>A6</title>
<polygon fill="none" stroke="#000000" points="288.5,-1706 288.5,-1738 422.5,-1738 422.5,-1706 288.5,-1706"/>
<text text-anchor="start" x="335.2175" y="-1719" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
<polygon fill="none" stroke="#000000" points="288.5,-1530 288.5,-1706 422.5,-1706 422.5,-1530 288.5,-1530"/>
<text text-anchor="start" x="318.8265" y="-1687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
<text text-anchor="start" x="316.043" y="-1675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
<text text-anchor="start" x="308.814" y="-1663" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len:unsigned</text>
<text text-anchor="start" x="314.648" y="-1651" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len:unsigned</text>
<text text-anchor="start" x="333.8245" y="-1639" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
<text text-anchor="start" x="337.7135" y="-1627" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="334.6585" y="-1615" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area</text>
<text text-anchor="start" x="305.489" y="-1603" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_recv_buffer:bytearray</text>
<text text-anchor="start" x="304.0945" y="-1591" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_buffer:bytearray</text>
<text text-anchor="start" x="298.2665" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_forward_buffer:bytearray</text>
<text text-anchor="start" x="337.7135" y="-1567" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:Infos</text>
<text text-anchor="start" x="326.326" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:list</text>
<text text-anchor="start" x="344.662" y="-1543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state</text>
<polygon fill="none" stroke="#000000" points="288.5,-1462 288.5,-1530 422.5,-1530 422.5,-1462 288.5,-1462"/>
<text text-anchor="start" x="305.2095" y="-1511" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_read():void&lt;abstract&gt;</text>
<text text-anchor="start" x="329.9445" y="-1499" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close():void</text>
<text text-anchor="start" x="315.7725" y="-1487" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter():void</text>
<text text-anchor="start" x="314.1025" y="-1475" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter():void</text>
<polygon fill="none" stroke="#000000" points="356.5,-236 356.5,-268 456.5,-268 456.5,-236 356.5,-236"/>
<text text-anchor="start" x="383.9995" y="-249" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">StreamPtr</text>
<polygon fill="none" stroke="#000000" points="356.5,-216 356.5,-236 456.5,-236 456.5,-216 356.5,-216"/>
<polygon fill="none" stroke="#000000" points="356.5,-172 356.5,-216 456.5,-216 456.5,-172 356.5,-172"/>
<text text-anchor="start" x="366.2175" y="-197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:ProtocolIfc</text>
<text text-anchor="start" x="381.2185" y="-185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
</g>
<!-- A5&#45;&gt;A6 -->
<g id="edge4" class="edge">
<g id="edge8" class="edge">
<title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" d="M355.5,-1777.5582C355.5,-1765.4749 355.5,-1752.067 355.5,-1738.189"/>
<polygon fill="none" stroke="#000000" points="352.0001,-1777.8144 355.5,-1787.8145 359.0001,-1777.8145 352.0001,-1777.8144"/>
</g>
<!-- A17 -->
<g id="node18" class="node">
<title>A17</title>
<polygon fill="none" stroke="#000000" points="481.5,-1380 481.5,-1412 595.5,-1412 595.5,-1380 481.5,-1380"/>
<text text-anchor="start" x="524.608" y="-1393" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text>
<polygon fill="none" stroke="#000000" points="481.5,-1228 481.5,-1380 595.5,-1380 595.5,-1228 481.5,-1228"/>
<text text-anchor="start" x="513.2185" y="-1361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
<text text-anchor="start" x="519.323" y="-1349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
<text text-anchor="start" x="528.497" y="-1337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="491.263" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text>
<text text-anchor="start" x="526.2775" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text>
<text text-anchor="start" x="507.1" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text>
<text text-anchor="start" x="510.44" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text>
<text text-anchor="start" x="514.0445" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text>
<text text-anchor="start" x="512.384" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="524.612" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="481.5,-1100 481.5,-1228 595.5,-1228 595.5,-1100 481.5,-1100"/>
<text text-anchor="start" x="495.9925" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text>
<text text-anchor="start" x="497.9325" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text>
<text text-anchor="start" x="503.7765" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text>
<text text-anchor="start" x="491.8285" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text>
<text text-anchor="start" x="493.7735" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
<text text-anchor="start" x="502.9405" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="519.054" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="523.5025" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A6&#45;&gt;A17 -->
<g id="edge25" class="edge">
<title>A6&#45;&gt;A17</title>
<path fill="none" stroke="#000000" d="M427.9888,-1489.9254C443.4425,-1464.7479 459.121,-1437.7954 472.5,-1412 475.4787,-1406.2569 478.4329,-1400.3487 481.3448,-1394.3464"/>
<polygon fill="none" stroke="#000000" points="424.814,-1488.4051 422.5361,-1498.7522 430.7693,-1492.084 424.814,-1488.4051"/>
</g>
<!-- A18 -->
<g id="node19" class="node">
<title>A18</title>
<polygon fill="none" stroke="#000000" points="251.5,-1344 251.5,-1376 342.5,-1376 342.5,-1344 251.5,-1344"/>
<text text-anchor="start" x="269.495" y="-1357" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text>
<polygon fill="none" stroke="#000000" points="251.5,-1204 251.5,-1344 342.5,-1344 342.5,-1204 251.5,-1204"/>
<text text-anchor="start" x="271.7185" y="-1325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
<text text-anchor="start" x="277.823" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
<text text-anchor="start" x="286.997" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="281.998" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
<text text-anchor="start" x="285.0575" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text>
<text text-anchor="start" x="290.056" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text>
<text text-anchor="start" x="269.21" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text>
<text text-anchor="start" x="270.884" y="-1229" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="283.112" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="251.5,-1136 251.5,-1204 342.5,-1204 342.5,-1136 251.5,-1136"/>
<text text-anchor="start" x="261.4405" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="277.554" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="282.0025" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A6&#45;&gt;A18 -->
<g id="edge26" class="edge">
<title>A6&#45;&gt;A18</title>
<path fill="none" stroke="#000000" d="M330.044,-1451.5785C325.7233,-1426.3869 321.2847,-1400.5076 317.1084,-1376.1574"/>
<polygon fill="none" stroke="#000000" points="326.6173,-1452.3046 331.7575,-1461.569 333.5166,-1451.1212 326.6173,-1452.3046"/>
<path fill="none" stroke="#000000" d="M356.1387,-317.872C363.3786,-303.802 370.5526,-289.86 377.1187,-277.0995"/>
<polygon fill="#000000" stroke="#000000" points="381.7846,-268.0318 381.2105,-278.9826 379.4969,-272.4777 377.2091,-276.9237 377.2091,-276.9237 377.2091,-276.9237 379.4969,-272.4777 373.2078,-274.8647 381.7846,-268.0318 381.7846,-268.0318"/>
<text text-anchor="middle" x="381.0069" y="-285.0166" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">2</text>
</g>
<!-- A7 -->
<g id="node8" class="node">
<title>A7</title>
<polygon fill="none" stroke="#000000" points="293.5,-1018 293.5,-1050 410.5,-1050 410.5,-1018 293.5,-1018"/>
<text text-anchor="start" x="321.429" y="-1031" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AsyncIfc&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="293.5,-998 293.5,-1018 410.5,-1018 410.5,-998 293.5,-998"/>
<polygon fill="none" stroke="#000000" points="293.5,-702 293.5,-998 410.5,-998 410.5,-702 293.5,-702"/>
<text text-anchor="start" x="321.436" y="-979" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_node_id()</text>
<text text-anchor="start" x="319.766" y="-967" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_conn_no()</text>
<text text-anchor="start" x="333.6635" y="-943" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_add()</text>
<text text-anchor="start" x="331.444" y="-931" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_flush()</text>
<text text-anchor="start" x="335.0535" y="-919" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_get()</text>
<text text-anchor="start" x="331.1635" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_peek()</text>
<text text-anchor="start" x="335.3335" y="-895" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_log()</text>
<text text-anchor="start" x="331.169" y="-883" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_clear()</text>
<text text-anchor="start" x="335.3335" y="-871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_len()</text>
<text text-anchor="start" x="329.7745" y="-847" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_add()</text>
<text text-anchor="start" x="327.555" y="-835" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_flush()</text>
<text text-anchor="start" x="331.4445" y="-823" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_log()</text>
<text text-anchor="start" x="327.28" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_clear()</text>
<text text-anchor="start" x="334.7785" y="-799" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_get()</text>
<text text-anchor="start" x="330.8885" y="-787" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_peek()</text>
<text text-anchor="start" x="335.0585" y="-775" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_log()</text>
<text text-anchor="start" x="330.894" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_clear()</text>
<text text-anchor="start" x="335.0585" y="-751" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_len()</text>
<text text-anchor="start" x="326.999" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_set_cb()</text>
<text text-anchor="start" x="303.3795" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_set_timeout_cb()</text>
<polygon fill="none" stroke="#000000" points="338.2314,-238 262.7686,-238 262.7686,-202 338.2314,-202 338.2314,-238"/>
<text text-anchor="middle" x="300.5" y="-217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
</g>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="234.5,-592 234.5,-624 327.5,-624 327.5,-592 234.5,-592"/>
<text text-anchor="start" x="252.664" y="-605" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncIfcImpl</text>
<polygon fill="none" stroke="#000000" points="234.5,-500 234.5,-592 327.5,-592 327.5,-500 234.5,-500"/>
<text text-anchor="start" x="244.048" y="-573" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_fifo:ByteFifo</text>
<text text-anchor="start" x="247.937" y="-561" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_fifo:ByteFifo</text>
<text text-anchor="start" x="247.662" y="-549" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_fifo:ByteFifo</text>
<text text-anchor="start" x="247.096" y="-537" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no:Count</text>
<text text-anchor="start" x="263.2135" y="-525" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="256.5495" y="-513" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout_cb</text>
</g>
<!-- A7&#45;&gt;A8 -->
<!-- A5&#45;&gt;A7 -->
<g id="edge5" class="edge">
<title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M309.7911,-691.541C304.3381,-667.4249 299.0868,-644.201 294.5737,-624.2415"/>
<polygon fill="none" stroke="#000000" points="306.4357,-692.5718 312.0551,-701.5536 313.2634,-691.0279 306.4357,-692.5718"/>
<title>A5&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M300.5,-307.7729C300.5,-280.5002 300.5,-254.684 300.5,-238.2013"/>
<polygon fill="none" stroke="#000000" points="297.0001,-307.872 300.5,-317.872 304.0001,-307.872 297.0001,-307.872"/>
</g>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="249.5,-390 249.5,-422 351.5,-422 351.5,-390 249.5,-390"/>
<text text-anchor="start" x="270.774" y="-403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
<polygon fill="none" stroke="#000000" points="249.5,-310 249.5,-390 351.5,-390 351.5,-310 249.5,-310"/>
<text text-anchor="start" x="286.053" y="-371" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">reader</text>
<text text-anchor="start" x="288.283" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text>
<text text-anchor="start" x="290.497" y="-347" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="286.053" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text>
<text text-anchor="start" x="286.608" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</text>
<polygon fill="none" stroke="#000000" points="249.5,-182 249.5,-310 351.5,-310 351.5,-182 249.5,-182"/>
<text text-anchor="start" x="272.154" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;loop</text>
<text text-anchor="start" x="288.282" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">disc()</text>
<text text-anchor="start" x="285.5025" y="-255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="281.054" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="265.7705" y="-219" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_read()</text>
<text text-anchor="start" x="265.221" y="-207" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_write()</text>
<text text-anchor="start" x="259.107" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_forward()</text>
<polygon fill="none" stroke="#000000" points="94.4001,-238 12.5999,-238 12.5999,-202 94.4001,-202 94.4001,-238"/>
<text text-anchor="middle" x="53.5" y="-217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
</g>
<!-- A8&#45;&gt;A9 -->
<!-- A5&#45;&gt;A9 -->
<g id="edge6" class="edge">
<title>A8&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M286.0661,-489.6408C287.6796,-468.6648 289.4863,-445.1785 291.2608,-422.1099"/>
<polygon fill="#000000" stroke="#000000" points="285.2905,-499.7235 281.5708,-489.4078 285.674,-494.7382 286.0576,-489.753 286.0576,-489.753 286.0576,-489.753 285.674,-494.7382 290.5443,-490.0982 285.2905,-499.7235 285.2905,-499.7235"/>
<title>A5&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M196.7667,-346.4637C165.8973,-321.9347 132.3582,-294.4156 102.5,-268 91.7971,-258.5312 80.3616,-247.3925 71.232,-238.23"/>
<polygon fill="none" stroke="#000000" points="194.962,-349.4991 204.9739,-352.965 199.3086,-344.0121 194.962,-349.4991"/>
</g>
<!-- A9&#45;&gt;A10 -->
<g id="edge7" class="edge">
<title>A9&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M264.6771,-171.9352C260.9087,-158.2531 257.2005,-144.7897 253.7581,-132.2911"/>
<polygon fill="#000000" stroke="#000000" points="267.4287,-181.9259 260.4348,-173.4798 266.101,-177.1054 264.7733,-172.2849 264.7733,-172.2849 264.7733,-172.2849 266.101,-177.1054 269.1118,-171.0899 267.4287,-181.9259 267.4287,-181.9259"/>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="450.1421,-36 360.8579,-36 360.8579,0 450.1421,0 450.1421,-36"/>
<text text-anchor="middle" x="405.5" y="-15" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AsyncIfc&gt;&gt;</text>
</g>
<!-- A9&#45;&gt;A11 -->
<g id="edge8" class="edge">
<title>A9&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M355.9159,-184.1788C367.6037,-159.329 379.2966,-134.4686 388.921,-114.0059"/>
<polygon fill="#000000" stroke="#000000" points="351.6139,-193.3253 351.798,-182.361 353.742,-188.8008 355.8701,-184.2763 355.8701,-184.2763 355.8701,-184.2763 353.742,-188.8008 359.9422,-186.1915 351.6139,-193.3253 351.6139,-193.3253"/>
<!-- A6&#45;&gt;A11 -->
<g id="edge11" class="edge">
<title>A6&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M392.6633,-171.974C386.9982,-146.4565 382.5868,-114.547 386.5,-86 388.3468,-72.5276 392.161,-57.9618 395.8907,-45.7804"/>
<polygon fill="#000000" stroke="#000000" points="398.9587,-36.1851 400.1994,-47.0805 397.4359,-40.9476 395.9131,-45.71 395.9131,-45.71 395.9131,-45.71 397.4359,-40.9476 391.6269,-44.3395 398.9587,-36.1851 398.9587,-36.1851"/>
<text text-anchor="middle" x="401.4892" y="-53.0243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A12 -->
<g id="node13" class="node">
<title>A12</title>
<polygon fill="none" stroke="#000000" points="660.5,-566 660.5,-598 743.5,-598 743.5,-566 660.5,-566"/>
<text text-anchor="start" x="670.0455" y="-579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3</text>
<polygon fill="none" stroke="#000000" points="660.5,-546 660.5,-566 743.5,-566 743.5,-546 660.5,-546"/>
<polygon fill="none" stroke="#000000" points="660.5,-526 660.5,-546 743.5,-546 743.5,-526 660.5,-526"/>
<polygon fill="none" stroke="#000000" points="493.5879,-122 395.4121,-122 395.4121,-86 493.5879,-86 493.5879,-122"/>
<text text-anchor="middle" x="444.5" y="-101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;ProtocolIfc&gt;&gt;</text>
</g>
<!-- A12&#45;&gt;A3 -->
<g id="edge9" class="edge">
<title>A12&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M670.0413,-516.8237C661.3918,-502.0835 651.6725,-485.9341 641.5,-472 615.4427,-436.3073 582.7913,-399.9006 554.7376,-369.9836"/>
<polygon fill="#000000" stroke="#000000" points="675.2831,-525.787 666.3503,-519.4264 672.759,-521.4709 670.2349,-517.1547 670.2349,-517.1547 670.2349,-517.1547 672.759,-521.4709 674.1194,-514.883 675.2831,-525.787 675.2831,-525.787"/>
<text text-anchor="middle" x="658.8364" y="-511.4069" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
<!-- A6&#45;&gt;A12 -->
<g id="edge10" class="edge">
<title>A6&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M422.2853,-171.8133C426.7329,-158.2365 431.4225,-143.9208 435.3408,-131.9595"/>
<polygon fill="#000000" stroke="#000000" points="438.5602,-122.132 439.7235,-133.036 437.0036,-126.8835 435.4471,-131.6351 435.4471,-131.6351 435.4471,-131.6351 437.0036,-126.8835 431.1707,-130.2341 438.5602,-122.132 438.5602,-122.132"/>
<text text-anchor="middle" x="440.9498" y="-138.9887" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A12&#45;&gt;A3 -->
<g id="edge11" class="edge">
<title>A12&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M687.0902,-516.8237C679.3918,-502.0835 669.6725,-485.9341 659.5,-472 631.135,-433.1461 594.956,-393.4463 563.5578,-362.1925"/>
<polygon fill="#000000" stroke="#000000" points="691.6084,-525.787 683.0888,-518.8829 689.3578,-521.3222 687.1072,-516.8573 687.1072,-516.8573 687.1072,-516.8573 689.3578,-521.3222 691.1255,-514.8318 691.6084,-525.787 691.6084,-525.787"/>
<polygon fill="#000000" stroke="#000000" points="563.3904,-362.0278 556.3085,-360.6689 554.8391,-353.609 561.921,-354.968 563.3904,-362.0278"/>
<text text-anchor="middle" x="573.6858" y="-357.3024" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="#fff8dc" stroke="#000000" points="574.906,-248 474.094,-248 474.094,-192 580.906,-192 580.906,-242 574.906,-248"/>
<polyline fill="none" stroke="#000000" points="574.906,-248 574.906,-242 "/>
<polyline fill="none" stroke="#000000" points="580.906,-242 574.906,-242 "/>
<text text-anchor="middle" x="527.5" y="-235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Creates an GEN3</text>
<text text-anchor="middle" x="527.5" y="-223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inverter instance</text>
<text text-anchor="middle" x="527.5" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">with</text>
<text text-anchor="middle" x="527.5" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:Talent</text>
</g>
<!-- A7&#45;&gt;A8 -->
<g id="edge3" class="edge">
<title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M308.5491,-238.3283C317.4345,-256.0056 333.5793,-281.6949 356.5,-293 396.3598,-312.6598 415.5578,-310.2929 456.5,-293 478.1607,-283.8511 496.4784,-264.5049 509.0802,-248.0264"/>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="#fff8dc" stroke="#000000" points="239.022,-248 111.978,-248 111.978,-192 245.022,-192 245.022,-242 239.022,-248"/>
<polyline fill="none" stroke="#000000" points="239.022,-248 239.022,-242 "/>
<polyline fill="none" stroke="#000000" points="245.022,-242 239.022,-242 "/>
<text text-anchor="middle" x="178.5" y="-235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Creates an GEN3PLUS</text>
<text text-anchor="middle" x="178.5" y="-223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inverter instance</text>
<text text-anchor="middle" x="178.5" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">with</text>
<text text-anchor="middle" x="178.5" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:SolarmanV5</text>
</g>
<!-- A9&#45;&gt;A10 -->
<g id="edge4" class="edge">
<title>A9&#45;&gt;A10</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M94.5156,-220C100.3114,-220 106.1072,-220 111.903,-220"/>
</g>
<!-- A12&#45;&gt;A11 -->
<g id="edge12" class="edge">
<title>A12&#45;&gt;A11</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M436.2291,-85.7616C430.9033,-74.0176 423.8824,-58.5355 417.896,-45.3349"/>
<polygon fill="#000000" stroke="#000000" points="413.759,-36.2121 421.9874,-43.4608 415.824,-40.7657 417.8891,-45.3194 417.8891,-45.3194 417.8891,-45.3194 415.824,-40.7657 413.7908,-47.1779 413.759,-36.2121 413.759,-36.2121"/>
<text text-anchor="middle" x="421.0451" y="-69.7445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A13 -->
<g id="node14" class="node">
<title>A13</title>
<polygon fill="none" stroke="#000000" points="125.5,-566 125.5,-598 215.5,-598 215.5,-566 125.5,-566"/>
<text text-anchor="start" x="135.211" y="-579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3P</text>
<polygon fill="none" stroke="#000000" points="125.5,-546 125.5,-566 215.5,-566 215.5,-546 125.5,-546"/>
<polygon fill="none" stroke="#000000" points="125.5,-526 125.5,-546 215.5,-546 215.5,-526 125.5,-526"/>
<polygon fill="none" stroke="#000000" points=".5,-454 .5,-486 107.5,-486 107.5,-454 .5,-454"/>
<text text-anchor="start" x="24.2695" y="-467" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
<polygon fill="none" stroke="#000000" points=".5,-386 .5,-454 107.5,-454 107.5,-386 .5,-386"/>
<text text-anchor="start" x="44.5515" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text>
<text text-anchor="start" x="45.387" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text>
<text text-anchor="start" x="43.997" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="10.383" y="-399" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
<polygon fill="none" stroke="#000000" points=".5,-366 .5,-386 107.5,-386 107.5,-366 .5,-366"/>
</g>
<!-- A13&#45;&gt;A4 -->
<!-- A13&#45;&gt;A9 -->
<g id="edge13" class="edge">
<title>A13&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M165.1942,-515.7657C163.5941,-476.4902 163.3236,-419.4483 164.3827,-374.1655"/>
<polygon fill="#000000" stroke="#000000" points="165.655,-525.8481 160.7031,-516.064 165.4267,-520.8533 165.1984,-515.8585 165.1984,-515.8585 165.1984,-515.8585 165.4267,-520.8533 169.6937,-515.653 165.655,-525.8481 165.655,-525.8481"/>
<text text-anchor="middle" x="156.3839" y="-505.1268" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A13&#45;&gt;A4 -->
<g id="edge15" class="edge">
<title>A13&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M175.8058,-515.7657C177.2652,-479.9422 177.6185,-429.338 176.8658,-386.3578"/>
<polygon fill="#000000" stroke="#000000" points="175.345,-525.8481 171.3063,-515.653 175.5733,-520.8533 175.8016,-515.8585 175.8016,-515.8585 175.8016,-515.8585 175.5733,-520.8533 180.2969,-516.064 175.345,-525.8481 175.345,-525.8481"/>
<polygon fill="#000000" stroke="#000000" points="176.8619,-386.163 172.7404,-380.2458 176.6173,-374.1655 180.7388,-380.0827 176.8619,-386.163"/>
<text text-anchor="middle" x="185.4372" y="-389.1157" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
<title>A13&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M53.5,-365.8625C53.5,-327.1513 53.5,-278.6088 53.5,-248.4442"/>
<polygon fill="#000000" stroke="#000000" points="53.5,-238.2147 58.0001,-248.2147 53.5,-243.2147 53.5001,-248.2147 53.5001,-248.2147 53.5001,-248.2147 53.5,-243.2147 49.0001,-248.2148 53.5,-238.2147 53.5,-238.2147"/>
<text text-anchor="middle" x="61.9524" y="-253.3409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="45.0476" y="-344.7363" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g>
<!-- A14 -->
<g id="node15" class="node">
<title>A14</title>
<polygon fill="none" stroke="#000000" points="360.5,-1344 360.5,-1376 463.5,-1376 463.5,-1344 360.5,-1344"/>
<text text-anchor="start" x="401.162" y="-1357" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
<polygon fill="none" stroke="#000000" points="360.5,-1288 360.5,-1344 463.5,-1344 463.5,-1288 360.5,-1288"/>
<text text-anchor="start" x="403.9415" y="-1325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text>
<text text-anchor="start" x="379.486" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
<text text-anchor="start" x="393.1035" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
<polygon fill="none" stroke="#000000" points="360.5,-1136 360.5,-1288 463.5,-1288 463.5,-1136 360.5,-1136"/>
<text text-anchor="start" x="387.8355" y="-1269" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
<text text-anchor="start" x="385.8845" y="-1257" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
<text text-anchor="start" x="382.8305" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="381.1605" y="-1233" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
<text text-anchor="start" x="379.21" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
<text text-anchor="start" x="394.213" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
<text text-anchor="start" x="386.994" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
<text text-anchor="start" x="388.3745" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
<text text-anchor="start" x="372.537" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text>
<text text-anchor="start" x="381.9855" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
<text text-anchor="start" x="370.3225" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text>
<polygon fill="none" stroke="#000000" points="93.7333,-722 13.2667,-722 13.2667,-686 93.7333,-686 93.7333,-722"/>
<text text-anchor="middle" x="53.5" y="-701" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusTcp</text>
</g>
<!-- A15 -->
<g id="node16" class="node">
<title>A15</title>
<polygon fill="none" stroke="#000000" points="569.5,-892 569.5,-924 636.5,-924 636.5,-892 569.5,-892"/>
<text text-anchor="start" x="585.493" y="-905" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text>
<polygon fill="none" stroke="#000000" points="569.5,-872 569.5,-892 636.5,-892 636.5,-872 569.5,-872"/>
<polygon fill="none" stroke="#000000" points="569.5,-828 569.5,-872 636.5,-872 636.5,-828 569.5,-828"/>
<text text-anchor="start" x="579.384" y="-853" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="587.168" y="-841" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A14&#45;&gt;A15 -->
<g id="edge17" class="edge">
<title>A14&#45;&gt;A15</title>
<path fill="none" stroke="#000000" d="M453.5702,-1126.8365C459.1676,-1117.273 465.4534,-1108.1823 472.5,-1100 501.6027,-1066.2069 532.6622,-1085.6184 559.5,-1050 586.933,-1013.5917 597.05,-961.8709 600.6858,-924.2866"/>
<polygon fill="none" stroke="#000000" points="450.4206,-1125.2989 448.6212,-1135.7398 456.5389,-1128.6999 450.4206,-1125.2989"/>
</g>
<!-- A16 -->
<g id="node17" class="node">
<title>A16</title>
<polygon fill="none" stroke="#000000" points="208.5,-892 208.5,-924 275.5,-924 275.5,-892 208.5,-892"/>
<text text-anchor="start" x="221.1585" y="-905" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text>
<polygon fill="none" stroke="#000000" points="208.5,-872 208.5,-892 275.5,-892 275.5,-872 208.5,-872"/>
<polygon fill="none" stroke="#000000" points="208.5,-828 208.5,-872 275.5,-872 275.5,-828 208.5,-828"/>
<text text-anchor="start" x="218.384" y="-853" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="226.168" y="-841" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A14&#45;&gt;A16 -->
<g id="edge18" class="edge">
<title>A14&#45;&gt;A16</title>
<path fill="none" stroke="#000000" d="M368.702,-1126.8311C363.5287,-1117.3934 357.8082,-1108.3251 351.5,-1100 329.0603,-1070.3857 305.0477,-1080.957 284.5,-1050 259.462,-1012.2779 248.9948,-961.2276 244.6235,-924.1849"/>
<polygon fill="none" stroke="#000000" points="365.7545,-1128.7471 373.4957,-1135.9807 371.9551,-1125.4985 365.7545,-1128.7471"/>
</g>
<!-- A17&#45;&gt;A7 -->
<g id="edge20" class="edge">
<title>A17&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M481.2663,-1113.4759C478.4435,-1108.8532 475.521,-1104.3466 472.5,-1100 454.0182,-1073.4084 437.6119,-1076.8449 419.5,-1050 418.0715,-1047.8827 416.6665,-1045.731 415.285,-1043.5491"/>
<polygon fill="#000000" stroke="#000000" points="410.0723,-1034.9917 419.1178,-1041.1909 412.6735,-1039.2618 415.2746,-1043.532 415.2746,-1043.532 415.2746,-1043.532 412.6735,-1039.2618 411.4315,-1045.873 410.0723,-1034.9917 410.0723,-1034.9917"/>
<text text-anchor="middle" x="464.576" y="-1099.4562" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A17&#45;&gt;A12 -->
<g id="edge19" class="edge">
<title>A17&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M600.4516,-1154.6847C617.6767,-1122.289 634.6239,-1085.5805 645.5,-1050 695.4203,-886.6897 701.5078,-680.2741 701.7917,-598.1154"/>
<polygon fill="none" stroke="#000000" points="597.2545,-1153.2396 595.5908,-1163.703 603.4164,-1156.5609 597.2545,-1153.2396"/>
</g>
<!-- A17&#45;&gt;A15 -->
<g id="edge21" class="edge">
<title>A17&#45;&gt;A15</title>
<path fill="none" stroke="#000000" d="M564.8083,-1099.7944C574.6598,-1041.3014 585.2548,-978.3931 592.6952,-934.2163"/>
<polygon fill="#000000" stroke="#000000" points="594.3746,-924.2447 597.1512,-934.8533 593.5441,-929.1753 592.7137,-934.1059 592.7137,-934.1059 592.7137,-934.1059 593.5441,-929.1753 588.2762,-933.3585 594.3746,-924.2447 594.3746,-924.2447"/>
</g>
<!-- A18&#45;&gt;A7 -->
<g id="edge23" class="edge">
<title>A18&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M313.9023,-1135.7656C317.3752,-1111.771 321.0977,-1086.0526 324.8007,-1060.468"/>
<polygon fill="#000000" stroke="#000000" points="326.2737,-1050.2909 329.2948,-1060.8324 325.5574,-1055.2393 324.8412,-1060.1877 324.8412,-1060.1877 324.8412,-1060.1877 325.5574,-1055.2393 320.3876,-1059.5431 326.2737,-1050.2909 326.2737,-1050.2909"/>
<text text-anchor="middle" x="308.1336" y="-1113.6156" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A18&#45;&gt;A13 -->
<g id="edge22" class="edge">
<title>A18&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M246.2928,-1168.5418C228.2709,-1132.9118 209.8568,-1090.6085 199.5,-1050 157.5515,-885.5216 163.4473,-680.1892 168.0341,-598.2267"/>
<polygon fill="none" stroke="#000000" points="243.1839,-1170.1499 250.861,-1177.4515 249.4129,-1166.956 243.1839,-1170.1499"/>
</g>
<!-- A18&#45;&gt;A16 -->
<g id="edge24" class="edge">
<title>A18&#45;&gt;A16</title>
<path fill="none" stroke="#000000" d="M279.0977,-1135.7656C269.3705,-1068.56 257.686,-987.8309 249.9522,-934.3969"/>
<polygon fill="#000000" stroke="#000000" points="248.5077,-924.4166 254.3938,-933.6689 249.2239,-929.3651 249.9402,-934.3135 249.9402,-934.3135 249.9402,-934.3135 249.2239,-929.3651 245.4866,-934.9581 248.5077,-924.4166 248.5077,-924.4166"/>
</g>
<!-- A19 -->
<g id="node20" class="node">
<title>A19</title>
<polygon fill="none" stroke="#000000" points="471.5,-1694 471.5,-1726 546.5,-1726 546.5,-1694 471.5,-1694"/>
<text text-anchor="start" x="491.2175" y="-1707" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
<polygon fill="none" stroke="#000000" points="471.5,-1542 471.5,-1694 546.5,-1694 546.5,-1542 471.5,-1542"/>
<text text-anchor="start" x="500.6615" y="-1675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
<text text-anchor="start" x="481.49" y="-1651" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
<text text-anchor="start" x="482.605" y="-1639" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
<text text-anchor="start" x="492.6085" y="-1627" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
<text text-anchor="start" x="482.8895" y="-1615" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
<text text-anchor="start" x="490.942" y="-1603" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
<text text-anchor="start" x="502.8915" y="-1591" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
<text text-anchor="start" x="489.5535" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
<text text-anchor="start" x="487.879" y="-1567" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
<text text-anchor="start" x="502.3365" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
<polygon fill="none" stroke="#000000" points="471.5,-1474 471.5,-1542 546.5,-1542 546.5,-1474 471.5,-1474"/>
<text text-anchor="start" x="482.89" y="-1523" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
<text text-anchor="start" x="486.224" y="-1511" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
<text text-anchor="start" x="483.724" y="-1499" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
<text text-anchor="start" x="494.0025" y="-1487" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A19&#45;&gt;A17 -->
<g id="edge28" class="edge">
<title>A19&#45;&gt;A17</title>
<path fill="none" stroke="#000000" d="M520.408,-1463.4549C521.8748,-1446.6352 523.3829,-1429.3425 524.8728,-1412.2588"/>
<polygon fill="#000000" stroke="#000000" points="519.5005,-1473.8604 515.8864,-1463.5072 519.935,-1468.8793 520.3694,-1463.8982 520.3694,-1463.8982 520.3694,-1463.8982 519.935,-1468.8793 524.8524,-1464.2892 519.5005,-1473.8604 519.5005,-1473.8604"/>
<text text-anchor="middle" x="531.7184" y="-1428.0507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="512.6549" y="-1452.0685" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A19&#45;&gt;A18 -->
<g id="edge27" class="edge">
<title>A19&#45;&gt;A18</title>
<path fill="none" stroke="#000000" d="M465.5007,-1507.5446C455.6528,-1491.3512 444.2658,-1475.4159 431.5,-1462 402.597,-1431.625 377.7201,-1444.719 351.5,-1412 342.88,-1401.2435 335.5589,-1389.0363 329.3527,-1376.3006"/>
<polygon fill="#000000" stroke="#000000" points="470.8124,-1516.5531 461.8569,-1510.2246 468.2729,-1512.246 465.7333,-1507.939 465.7333,-1507.939 465.7333,-1507.939 468.2729,-1512.246 469.6096,-1505.6533 470.8124,-1516.5531 470.8124,-1516.5531"/>
<text text-anchor="middle" x="345.0021" y="-1385.7544" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="454.325" y="-1502.2321" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A20 -->
<g id="node21" class="node">
<title>A20</title>
<polygon fill="none" stroke="#000000" points=".5,-590 .5,-622 107.5,-622 107.5,-590 .5,-590"/>
<text text-anchor="start" x="24.2695" y="-603" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
<polygon fill="none" stroke="#000000" points=".5,-522 .5,-590 107.5,-590 107.5,-522 .5,-522"/>
<text text-anchor="start" x="44.5515" y="-571" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text>
<text text-anchor="start" x="45.387" y="-559" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text>
<text text-anchor="start" x="43.997" y="-547" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="10.383" y="-535" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
<polygon fill="none" stroke="#000000" points=".5,-502 .5,-522 107.5,-522 107.5,-502 .5,-502"/>
</g>
<!-- A20&#45;&gt;A4 -->
<g id="edge29" class="edge">
<title>A20&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M80.6362,-501.6973C96.3809,-466.7091 116.429,-422.1579 133.5925,-384.0168"/>
<polygon fill="#000000" stroke="#000000" points="137.8895,-374.4677 137.8895,-385.4336 135.8377,-379.0273 133.7858,-383.5869 133.7858,-383.5869 133.7858,-383.5869 135.8377,-379.0273 129.6822,-381.7402 137.8895,-374.4677 137.8895,-374.4677"/>
<text text-anchor="middle" x="138.1591" y="-391.4659" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="80.3667" y="-478.6991" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<!-- A14&#45;&gt;A13 -->
<g id="edge14" class="edge">
<title>A14&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M53.5,-685.7596C53.5,-647.9991 53.5,-559.5189 53.5,-496.3277"/>
<polygon fill="#000000" stroke="#000000" points="53.5,-486.0223 58.0001,-496.0223 53.5,-491.0223 53.5001,-496.0223 53.5001,-496.0223 53.5001,-496.0223 53.5,-491.0223 49.0001,-496.0224 53.5,-486.0223 53.5,-486.0223"/>
<text text-anchor="middle" x="61.9524" y="-501.1485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">*</text>
<text text-anchor="middle" x="45.0476" y="-664.6335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">creates</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -3,57 +3,34 @@
// {generate:true}
[note: You can stick notes on diagrams too!{bg:cornsilk}]
[<<AbstractIterMeta>>||__iter__]
[Mqtt;<<Singleton>>|<static>ha_restarts;<static>__client;<static>__cb_MqttIsUp|<async>publish();<async>close()]
[Inverter|cls.db_stat;cls.entity_prfx;cls.discovery_prfx;cls.proxy_node_id;cls.proxy_unique_id;cls.mqtt:Mqtt;;__ha_restarts|async_create_remote(inv_prot, conn_class)async_publ_mqtt()]
[Inverter]^[InverterG3|addr;remote:StreamPtr;local:StreamPtr|async_create_remote();;close()]
[Inverter]^[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|async_create_remote();;close()]
[Mqtt;<<Singleton>>]<-++[Inverter]
[Proxy|<cls>db_stat;<cls>entity_prfx;<cls>discovery_prfx;<cls>proxy_node_id;<cls>proxy_unique_id;<cls>mqtt:Mqtt;;__ha_restarts|class_init();class_close();;<async>_cb_mqtt_is_up();<async>_register_proxy_stat_home_assistant();<async>_async_publ_mqtt_proxy_stat(key)]
[IterRegistry||__iter__]^[Message|server_side:bool;header_valid:bool;header_len:unsigned;data_len:unsigned;unique_id;node_id;sug_area;_recv_buffer:bytearray;_send_buffer:bytearray;_forward_buffer:bytearray;db:Infos;new_data:list;state|_read():void<abstract>;close():void;inc_counter():void;dec_counter():void]
[<<InverterIfc>>||healthy()->bool;<async>disc(shutdown_started=False);<async>create_remote();]
[<<AbstractIterMeta>>]^-.-[<<InverterIfc>>]
[InverterBase|_registry;__ha_restarts;;addr;config_id:str;prot_class:MessageProt;remote:StreamPtr;local:StreamPtr;|healthy()->bool;<async>disc(shutdown_started=False);<async>create_remote();<async>async_publ_mqtt()]
[StreamPtr||stream:ProtocolIfc;ifc:AsyncIfc]
[<<InverterIfc>>]^-.-[InverterBase]
[InverterG3]-[note: Creates an GEN3 inverter instance with prot_class:Talent{bg:cornsilk}]
[InverterG3P]-[note: Creates an GEN3PLUS inverter instance with prot_class:SolarmanV5{bg:cornsilk}]
[InverterBase]^[InverterG3]
[InverterBase]^[InverterG3P]
[Proxy]^[InverterBase]
[InverterBase]-2>[StreamPtr]
[Proxy]++->[Mqtt;<<Singleton>>]
[<<AsyncIfc>>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_flush();fwd_log();fwd_clear();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()]
[AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb]
[AsyncStream|reader;writer;addr;r_addr;l_addr|;<async>loop;disc();close();healthy();;__async_read();__async_write();__async_forward()]
[AsyncStreamServer|async_create_remote|<async>server_loop();<async>_async_forward();<async>publish_outstanding_mqtt();close()]
[AsyncStreamClient||<async>client_loop();<async>_async_forward())]
[<<AsyncIfc>>]^-.-[AsyncIfcImpl]
[AsyncIfcImpl]<-[AsyncStream]
[AsyncStream]<-[AsyncStreamServer]
[AsyncStream]<-[AsyncStreamClient]
[<<AsyncIfc>>]
[ConnectionG3||]
[ConnectionG3]<remote-[InverterG3]
[InverterG3]-remote>[AsyncStreamClient]
[ConnectionG3]<-local++[InverterG3]
[InverterG3]++local->[AsyncStreamServer]
[StreamPtr]-1>[<<ProtocolIfc>>]
[StreamPtr]-1>[<<AsyncIfc>>]
[ConnectionG3P||]
[ConnectionG3P]<remote-[InverterG3P]
[InverterG3P]-remote>[AsyncStreamClient]
[ConnectionG3P]<-local++[InverterG3P]
[InverterG3P]++local->[AsyncStreamServer]
[Infos|stat;new_stat_data;info_dev|static_init();dev_value();inc_counter();dec_counter();ha_proxy_conf;ha_conf;ha_remove;update_db;set_db_def_value;get_db_value;ignore_this_device]
[Infos]^[InfosG3||ha_confs();parse()]
[Infos]^[InfosG3P||ha_confs();parse()]
[<<ProtocolIfc>>]use-.->[<<AsyncIfc>>]
[Talent|ifc:AsyncIfc;conn_no;addr;;await_conn_resp_cnt;id_str;contact_name;contact_mail;db:InfosG3;mb:Modbus;switch|msg_contact_info();msg_ota_update();msg_get_time();msg_collector_data();msg_inverter_data();msg_unknown();;healthy();close()]
[Talent]^[ConnectionG3]
[Talent]use->[<<AsyncIfc>>]
[Talent]->[InfosG3]
[SolarmanV5|ifc:AsyncIfc;conn_no;addr;;control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;healthy();close()]
[SolarmanV5]^[ConnectionG3P]
[SolarmanV5]use->[<<AsyncIfc>>]
[SolarmanV5]->[InfosG3P]
[Message]^[Talent]
[Message]^[SolarmanV5]
[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()]
[Modbus]<1-has[SolarmanV5]
[Modbus]<1-has[Talent]
[ModbusConn|host;port;addr;stream:InverterG3P;|]has-1>[InverterG3P]
[ModbusTcp]creates-*>[ModbusConn]

416
app/proxy_2.svg Normal file
View File

@@ -0,0 +1,416 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: G Pages: 1 -->
<svg width="446pt" height="1796pt"
viewBox="0.00 0.00 446.35 1796.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1792)">
<title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1792 442.348,-1792 442.348,4 -4,4"/>
<!-- A0 -->
<g id="node1" class="node">
<title>A0</title>
<polygon fill="#fff8dc" stroke="#000000" points="108.5444,-1764 .1516,-1764 .1516,-1728 114.5444,-1728 114.5444,-1758 108.5444,-1764"/>
<polyline fill="none" stroke="#000000" points="108.5444,-1764 108.5444,-1758 "/>
<polyline fill="none" stroke="#000000" points="114.5444,-1758 108.5444,-1758 "/>
<text text-anchor="middle" x="57.348" y="-1749" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
<text text-anchor="middle" x="57.348" y="-1737" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
</g>
<!-- A1 -->
<g id="node2" class="node">
<title>A1</title>
<polygon fill="none" stroke="#000000" points="133.348,-1756 133.348,-1788 204.348,-1788 204.348,-1756 133.348,-1756"/>
<text text-anchor="start" x="143.293" y="-1769" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">IterRegistry</text>
<polygon fill="none" stroke="#000000" points="133.348,-1736 133.348,-1756 204.348,-1756 204.348,-1736 133.348,-1736"/>
<polygon fill="none" stroke="#000000" points="133.348,-1704 133.348,-1736 204.348,-1736 204.348,-1704 133.348,-1704"/>
<text text-anchor="start" x="150.787" y="-1717" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__</text>
</g>
<!-- A14 -->
<g id="node15" class="node">
<title>A14</title>
<polygon fill="none" stroke="#000000" points="128.348,-1550 128.348,-1582 209.348,-1582 209.348,-1550 128.348,-1550"/>
<text text-anchor="start" x="148.5655" y="-1563" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
<polygon fill="none" stroke="#000000" points="128.348,-1518 128.348,-1550 209.348,-1550 209.348,-1518 128.348,-1518"/>
<text text-anchor="start" x="151.0615" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<polygon fill="none" stroke="#000000" points="128.348,-1474 128.348,-1518 209.348,-1518 209.348,-1474 128.348,-1474"/>
<text text-anchor="start" x="139.6785" y="-1499" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="138.0085" y="-1487" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
</g>
<!-- A1&#45;&gt;A14 -->
<g id="edge19" class="edge">
<title>A1&#45;&gt;A14</title>
<path fill="none" stroke="#000000" d="M168.348,-1693.8572C168.348,-1660.1684 168.348,-1616.4162 168.348,-1582.3079"/>
<polygon fill="none" stroke="#000000" points="164.8481,-1693.9674 168.348,-1703.9674 171.8481,-1693.9674 164.8481,-1693.9674"/>
</g>
<!-- A2 -->
<g id="node3" class="node">
<title>A2</title>
<polygon fill="none" stroke="#000000" points="340.348,-584 340.348,-616 438.348,-616 438.348,-584 340.348,-584"/>
<text text-anchor="start" x="365.7325" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
<polygon fill="none" stroke="#000000" points="340.348,-528 340.348,-584 438.348,-584 438.348,-528 340.348,-528"/>
<text text-anchor="start" x="379.345" y="-565" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="349.901" y="-553" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
<text text-anchor="start" x="355.18" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
<polygon fill="none" stroke="#000000" points="340.348,-472 340.348,-528 438.348,-528 438.348,-472 340.348,-472"/>
<text text-anchor="start" x="353.79" y="-509" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote()</text>
<text text-anchor="start" x="374.3505" y="-485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A7 -->
<g id="node8" class="node">
<title>A7</title>
<polygon fill="none" stroke="#000000" points="7.348,-100 7.348,-132 185.348,-132 185.348,-100 7.348,-100"/>
<text text-anchor="start" x="51.8995" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamServer</text>
<polygon fill="none" stroke="#000000" points="7.348,-68 7.348,-100 185.348,-100 185.348,-68 7.348,-68"/>
<text text-anchor="start" x="64.119" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote</text>
<polygon fill="none" stroke="#000000" points="7.348,0 7.348,-68 185.348,-68 185.348,0 7.348,0"/>
<text text-anchor="start" x="48.0055" y="-49" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;server_loop()</text>
<text text-anchor="start" x="38.8365" y="-37" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_forward()</text>
<text text-anchor="start" x="17.157" y="-25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish_outstanding_mqtt()</text>
<text text-anchor="start" x="81.3505" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A2&#45;&gt;A7 -->
<g id="edge8" class="edge">
<title>A2&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M355.1669,-460.7015C350.2935,-447.8271 345.5247,-434.6178 341.348,-422 306.6206,-317.0888 342.6855,-270.3837 276.348,-182 250.7246,-147.8611 230.2817,-155.039 194.348,-132 194.2563,-131.9412 194.1645,-131.8823 194.0727,-131.8234"/>
<polygon fill="#000000" stroke="#000000" points="355.1669,-460.7018 361.0503,-464.8714 359.4643,-471.9059 353.5809,-467.7363 355.1669,-460.7018"/>
<polygon fill="#000000" stroke="#000000" points="185.433,-126.2441 196.2748,-127.8888 189.6333,-128.9566 193.8336,-131.6691 193.8336,-131.6691 193.8336,-131.6691 189.6333,-128.9566 191.3923,-135.4493 185.433,-126.2441 185.433,-126.2441"/>
<text text-anchor="middle" x="345.0813" y="-455.0087" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
</g>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="203.348,-82 203.348,-114 341.348,-114 341.348,-82 203.348,-82"/>
<text text-anchor="start" x="229.845" y="-95" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamClient</text>
<polygon fill="none" stroke="#000000" points="203.348,-62 203.348,-82 341.348,-82 341.348,-62 203.348,-62"/>
<polygon fill="none" stroke="#000000" points="203.348,-18 203.348,-62 341.348,-62 341.348,-18 203.348,-18"/>
<text text-anchor="start" x="226.226" y="-43" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;client_loop()</text>
<text text-anchor="start" x="213.172" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_forward())</text>
</g>
<!-- A2&#45;&gt;A8 -->
<g id="edge6" class="edge">
<title>A2&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M388.2845,-471.9734C385.2471,-397.4776 374.9339,-278.7213 341.348,-182 334.28,-161.6455 323.3872,-140.9471 312.3293,-122.7781"/>
<polygon fill="#000000" stroke="#000000" points="306.9036,-114.0745 316.0125,-120.18 309.5487,-118.3175 312.1938,-122.5606 312.1938,-122.5606 312.1938,-122.5606 309.5487,-118.3175 308.375,-124.9412 306.9036,-114.0745 306.9036,-114.0745"/>
<text text-anchor="middle" x="323.6654" y="-121.9851" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A3 -->
<g id="node4" class="node">
<title>A3</title>
<polygon fill="none" stroke="#000000" points="18.348,-342 18.348,-374 116.348,-374 116.348,-342 18.348,-342"/>
<text text-anchor="start" x="40.398" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
<polygon fill="none" stroke="#000000" points="18.348,-286 18.348,-342 116.348,-342 116.348,-286 18.348,-286"/>
<text text-anchor="start" x="57.345" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="27.901" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
<text text-anchor="start" x="33.18" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
<polygon fill="none" stroke="#000000" points="18.348,-230 18.348,-286 116.348,-286 116.348,-230 18.348,-230"/>
<text text-anchor="start" x="31.79" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote()</text>
<text text-anchor="start" x="52.3505" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A3&#45;&gt;A7 -->
<g id="edge12" class="edge">
<title>A3&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M77.7163,-217.6237C80.7418,-193.0021 84.0292,-166.2494 86.9885,-142.1671"/>
<polygon fill="#000000" stroke="#000000" points="77.6702,-217.9995 80.9085,-224.4425 76.2065,-229.9099 72.9682,-223.4668 77.6702,-217.9995"/>
<polygon fill="#000000" stroke="#000000" points="88.2349,-132.0235 91.4816,-142.4978 87.6251,-136.9862 87.0152,-141.9489 87.0152,-141.9489 87.0152,-141.9489 87.6251,-136.9862 82.5488,-141.4 88.2349,-132.0235 88.2349,-132.0235"/>
<text text-anchor="middle" x="70.028" y="-207.8882" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
</g>
<!-- A3&#45;&gt;A8 -->
<g id="edge10" class="edge">
<title>A3&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M116.4873,-232.1115C129.0204,-215.3231 142.7944,-197.7122 156.348,-182 174.1632,-161.3474 194.8826,-139.9135 213.8403,-121.173"/>
<polygon fill="#000000" stroke="#000000" points="221.01,-114.1296 217.0299,-124.3477 217.4432,-117.6336 213.8763,-121.1375 213.8763,-121.1375 213.8763,-121.1375 217.4432,-117.6336 210.7227,-117.9274 221.01,-114.1296 221.01,-114.1296"/>
<text text-anchor="middle" x="214.0028" y="-129.8619" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A4 -->
<g id="node5" class="node">
<title>A4</title>
<polygon fill="none" stroke="#000000" points="158.348,-958 158.348,-990 275.348,-990 275.348,-958 158.348,-958"/>
<text text-anchor="start" x="186.277" y="-971" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AsyncIfc&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="158.348,-938 158.348,-958 275.348,-958 275.348,-938 158.348,-938"/>
<polygon fill="none" stroke="#000000" points="158.348,-666 158.348,-938 275.348,-938 275.348,-666 158.348,-666"/>
<text text-anchor="start" x="186.284" y="-919" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_node_id()</text>
<text text-anchor="start" x="184.614" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_conn_no()</text>
<text text-anchor="start" x="198.5115" y="-883" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_add()</text>
<text text-anchor="start" x="196.292" y="-871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_flush()</text>
<text text-anchor="start" x="199.9015" y="-859" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_get()</text>
<text text-anchor="start" x="196.0115" y="-847" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_peek()</text>
<text text-anchor="start" x="200.1815" y="-835" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_log()</text>
<text text-anchor="start" x="196.017" y="-823" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_clear()</text>
<text text-anchor="start" x="200.1815" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_len()</text>
<text text-anchor="start" x="194.6225" y="-787" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_add()</text>
<text text-anchor="start" x="196.2925" y="-775" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_log()</text>
<text text-anchor="start" x="199.6265" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_get()</text>
<text text-anchor="start" x="195.7365" y="-751" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_peek()</text>
<text text-anchor="start" x="199.9065" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_log()</text>
<text text-anchor="start" x="195.742" y="-727" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_clear()</text>
<text text-anchor="start" x="199.9065" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_len()</text>
<text text-anchor="start" x="191.847" y="-703" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_set_cb()</text>
<text text-anchor="start" x="168.2275" y="-679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_set_timeout_cb()</text>
</g>
<!-- A5 -->
<g id="node6" class="node">
<title>A5</title>
<polygon fill="none" stroke="#000000" points="170.348,-574 170.348,-606 263.348,-606 263.348,-574 170.348,-574"/>
<text text-anchor="start" x="188.512" y="-587" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncIfcImpl</text>
<polygon fill="none" stroke="#000000" points="170.348,-482 170.348,-574 263.348,-574 263.348,-482 170.348,-482"/>
<text text-anchor="start" x="179.896" y="-555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_fifo:ByteFifo</text>
<text text-anchor="start" x="183.785" y="-543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_fifo:ByteFifo</text>
<text text-anchor="start" x="183.51" y="-531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_fifo:ByteFifo</text>
<text text-anchor="start" x="182.944" y="-519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no:Count</text>
<text text-anchor="start" x="199.0615" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="192.3975" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout_cb</text>
</g>
<!-- A4&#45;&gt;A5 -->
<g id="edge1" class="edge">
<title>A4&#45;&gt;A5</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M216.348,-655.339C216.348,-637.8929 216.348,-621.0952 216.348,-606.0686"/>
<polygon fill="none" stroke="#000000" points="212.8481,-655.6774 216.348,-665.6775 219.8481,-655.6775 212.8481,-655.6774"/>
</g>
<!-- A6 -->
<g id="node7" class="node">
<title>A6</title>
<polygon fill="none" stroke="#000000" points="165.348,-390 165.348,-422 267.348,-422 267.348,-390 165.348,-390"/>
<text text-anchor="start" x="186.622" y="-403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
<polygon fill="none" stroke="#000000" points="165.348,-310 165.348,-390 267.348,-390 267.348,-310 165.348,-310"/>
<text text-anchor="start" x="201.901" y="-371" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">reader</text>
<text text-anchor="start" x="204.131" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text>
<text text-anchor="start" x="206.345" y="-347" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="201.901" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text>
<text text-anchor="start" x="202.456" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</text>
<polygon fill="none" stroke="#000000" points="165.348,-182 165.348,-310 267.348,-310 267.348,-182 165.348,-182"/>
<text text-anchor="start" x="188.002" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;loop</text>
<text text-anchor="start" x="204.13" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">disc()</text>
<text text-anchor="start" x="201.3505" y="-255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="196.902" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="181.6185" y="-219" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_read()</text>
<text text-anchor="start" x="181.069" y="-207" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_write()</text>
<text text-anchor="start" x="174.955" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_forward()</text>
</g>
<!-- A5&#45;&gt;A6 -->
<g id="edge2" class="edge">
<title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" d="M216.348,-471.886C216.348,-456.1951 216.348,-439.1858 216.348,-422.1976"/>
<polygon fill="none" stroke="#000000" points="212.8481,-471.9932 216.348,-481.9932 219.8481,-471.9933 212.8481,-471.9932"/>
</g>
<!-- A6&#45;&gt;A7 -->
<g id="edge3" class="edge">
<title>A6&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M160.6502,-192.461C150.1789,-171.8674 139.5319,-150.9284 129.9938,-132.1701"/>
<polygon fill="none" stroke="#000000" points="157.6308,-194.2451 165.2832,-201.5725 163.8705,-191.0723 157.6308,-194.2451"/>
</g>
<!-- A6&#45;&gt;A8 -->
<g id="edge4" class="edge">
<title>A6&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M247.1746,-172.088C252.0746,-151.438 256.8579,-131.2796 260.9161,-114.1772"/>
<polygon fill="none" stroke="#000000" points="243.7436,-171.3879 244.8402,-181.9259 250.5545,-173.0041 243.7436,-171.3879"/>
</g>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="280.348,-1320 280.348,-1352 394.348,-1352 394.348,-1320 280.348,-1320"/>
<text text-anchor="start" x="323.456" y="-1333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text>
<polygon fill="none" stroke="#000000" points="280.348,-1168 280.348,-1320 394.348,-1320 394.348,-1168 280.348,-1168"/>
<text text-anchor="start" x="312.0665" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
<text text-anchor="start" x="318.171" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
<text text-anchor="start" x="327.345" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="290.111" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text>
<text text-anchor="start" x="325.1255" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text>
<text text-anchor="start" x="305.948" y="-1229" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text>
<text text-anchor="start" x="309.288" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text>
<text text-anchor="start" x="312.8925" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text>
<text text-anchor="start" x="311.232" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="323.46" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="280.348,-1040 280.348,-1168 394.348,-1168 394.348,-1040 280.348,-1040"/>
<text text-anchor="start" x="294.8405" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text>
<text text-anchor="start" x="296.7805" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text>
<text text-anchor="start" x="302.6245" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text>
<text text-anchor="start" x="290.6765" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text>
<text text-anchor="start" x="292.6215" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
<text text-anchor="start" x="301.7885" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="317.902" y="-1065" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="322.3505" y="-1053" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A9&#45;&gt;A2 -->
<g id="edge5" class="edge">
<title>A9&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M355.8061,-1029.6429C357.5037,-1016.2527 359.0593,-1002.9144 360.348,-990 373.4702,-858.5013 377.9848,-704.3277 382.1955,-616.0127"/>
<polygon fill="#000000" stroke="#000000" points="354.4823,-1039.8864 351.3012,-1029.3921 355.1232,-1034.9276 355.764,-1029.9689 355.764,-1029.9689 355.764,-1029.9689 355.1232,-1034.9276 360.2269,-1030.5457 354.4823,-1039.8864 354.4823,-1039.8864"/>
<text text-anchor="middle" x="348.4228" y="-1017.8264" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A9&#45;&gt;A2 -->
<g id="edge7" class="edge">
<title>A9&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M373.7566,-1029.6429C375.5037,-1016.2527 377.0593,-1002.9144 378.348,-990 390.8936,-864.2801 395.5714,-717.8344 394.9909,-628.0298"/>
<polygon fill="#000000" stroke="#000000" points="372.3845,-1039.8864 369.2521,-1029.3774 373.0484,-1034.9307 373.7122,-1029.9749 373.7122,-1029.9749 373.7122,-1029.9749 373.0484,-1034.9307 378.1724,-1030.5724 372.3845,-1039.8864 372.3845,-1039.8864"/>
<polygon fill="#000000" stroke="#000000" points="394.9908,-628.0122 390.9345,-622.0501 394.8778,-616.0127 398.9341,-621.9747 394.9908,-628.0122"/>
<text text-anchor="middle" x="403.5004" y="-631.0585" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
</g>
<!-- A9&#45;&gt;A4 -->
<g id="edge15" class="edge">
<title>A9&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M286.0331,-1039.9349C281.6793,-1026.6936 277.2605,-1013.2547 272.8698,-999.9011"/>
<polygon fill="#000000" stroke="#000000" points="269.679,-990.1968 277.0774,-998.2908 271.2408,-994.9466 272.8026,-999.6964 272.8026,-999.6964 272.8026,-999.6964 271.2408,-994.9466 268.5277,-1001.102 269.679,-990.1968 269.679,-990.1968"/>
<text text-anchor="middle" x="272.3419" y="-1022.3558" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A12 -->
<g id="node13" class="node">
<title>A12</title>
<polygon fill="none" stroke="#000000" points="293.348,-844 293.348,-876 360.348,-876 360.348,-844 293.348,-844"/>
<text text-anchor="start" x="309.341" y="-857" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text>
<polygon fill="none" stroke="#000000" points="293.348,-824 293.348,-844 360.348,-844 360.348,-824 293.348,-824"/>
<polygon fill="none" stroke="#000000" points="293.348,-780 293.348,-824 360.348,-824 360.348,-780 293.348,-780"/>
<text text-anchor="start" x="303.232" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="311.016" y="-793" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A9&#45;&gt;A12 -->
<g id="edge16" class="edge">
<title>A9&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M332.683,-1039.9349C331.0582,-985.577 329.3338,-927.8888 328.095,-886.4463"/>
<polygon fill="#000000" stroke="#000000" points="327.7909,-876.272 332.5878,-886.1331 327.9404,-881.2698 328.0898,-886.2676 328.0898,-886.2676 328.0898,-886.2676 327.9404,-881.2698 323.5918,-886.4021 327.7909,-876.272 327.7909,-876.272"/>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="none" stroke="#000000" points="50.348,-1284 50.348,-1316 141.348,-1316 141.348,-1284 50.348,-1284"/>
<text text-anchor="start" x="68.343" y="-1297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text>
<polygon fill="none" stroke="#000000" points="50.348,-1144 50.348,-1284 141.348,-1284 141.348,-1144 50.348,-1144"/>
<text text-anchor="start" x="70.5665" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
<text text-anchor="start" x="76.671" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
<text text-anchor="start" x="85.845" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="80.846" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
<text text-anchor="start" x="83.9055" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text>
<text text-anchor="start" x="88.904" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text>
<text text-anchor="start" x="68.058" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text>
<text text-anchor="start" x="69.732" y="-1169" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="81.96" y="-1157" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="50.348,-1076 50.348,-1144 141.348,-1144 141.348,-1076 50.348,-1076"/>
<text text-anchor="start" x="60.2885" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="76.402" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="80.8505" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A10&#45;&gt;A3 -->
<g id="edge9" class="edge">
<title>A10&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M63.2254,-1066.0442C59.4271,-1040.8951 56.2542,-1014.6889 54.348,-990 37.0619,-766.107 47.322,-500.0139 57.5568,-374.449"/>
<polygon fill="#000000" stroke="#000000" points="64.7695,-1075.9973 58.7895,-1066.8055 64.0029,-1071.0565 63.2364,-1066.1156 63.2364,-1066.1156 63.2364,-1066.1156 64.0029,-1071.0565 67.6832,-1065.4256 64.7695,-1075.9973 64.7695,-1075.9973"/>
<text text-anchor="middle" x="53.6382" y="-1056.3813" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
</g>
<!-- A10&#45;&gt;A3 -->
<g id="edge11" class="edge">
<title>A10&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M80.8817,-1066.0442C77.4271,-1040.8951 74.2542,-1014.6889 72.348,-990 55.6021,-773.1036 64.7076,-516.6035 68.1168,-386.6269"/>
<polygon fill="#000000" stroke="#000000" points="82.2701,-1075.9973 76.4317,-1066.7149 81.5793,-1071.0453 80.8885,-1066.0932 80.8885,-1066.0932 80.8885,-1066.0932 81.5793,-1071.0453 85.3454,-1065.4715 82.2701,-1075.9973 82.2701,-1075.9973"/>
<polygon fill="#000000" stroke="#000000" points="68.1213,-386.4451 64.2759,-380.3449 68.4278,-374.449 72.2733,-380.5493 68.1213,-386.4451"/>
<text text-anchor="middle" x="76.4146" y="-389.7851" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
</g>
<!-- A10&#45;&gt;A4 -->
<g id="edge17" class="edge">
<title>A10&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M134.8842,-1075.7577C142.8622,-1051.494 151.3952,-1025.5424 159.8248,-999.9053"/>
<polygon fill="#000000" stroke="#000000" points="163.0491,-990.0992 164.2003,-1001.0045 161.4873,-994.849 159.9255,-999.5989 159.9255,-999.5989 159.9255,-999.5989 161.4873,-994.849 155.6506,-998.1932 163.0491,-990.0992 163.0491,-990.0992"/>
<text text-anchor="middle" x="132.5165" y="-1052.8983" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A13 -->
<g id="node14" class="node">
<title>A13</title>
<polygon fill="none" stroke="#000000" points="73.348,-844 73.348,-876 140.348,-876 140.348,-844 73.348,-844"/>
<text text-anchor="start" x="86.0065" y="-857" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text>
<polygon fill="none" stroke="#000000" points="73.348,-824 73.348,-844 140.348,-844 140.348,-824 73.348,-824"/>
<polygon fill="none" stroke="#000000" points="73.348,-780 73.348,-824 140.348,-824 140.348,-780 73.348,-780"/>
<text text-anchor="start" x="83.232" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="91.016" y="-793" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A10&#45;&gt;A13 -->
<g id="edge18" class="edge">
<title>A10&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M98.9422,-1075.7577C100.842,-1012.1995 103.0881,-937.0598 104.6045,-886.3285"/>
<polygon fill="#000000" stroke="#000000" points="104.9072,-876.2026 109.1063,-886.3326 104.7577,-881.2004 104.6083,-886.1981 104.6083,-886.1981 104.6083,-886.1981 104.7577,-881.2004 100.1103,-886.0636 104.9072,-876.2026 104.9072,-876.2026"/>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="159.348,-1284 159.348,-1316 262.348,-1316 262.348,-1284 159.348,-1284"/>
<text text-anchor="start" x="200.01" y="-1297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
<polygon fill="none" stroke="#000000" points="159.348,-1228 159.348,-1284 262.348,-1284 262.348,-1228 159.348,-1228"/>
<text text-anchor="start" x="202.7895" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text>
<text text-anchor="start" x="178.334" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
<text text-anchor="start" x="191.9515" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
<polygon fill="none" stroke="#000000" points="159.348,-1076 159.348,-1228 262.348,-1228 262.348,-1076 159.348,-1076"/>
<text text-anchor="start" x="186.6835" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
<text text-anchor="start" x="184.7325" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
<text text-anchor="start" x="181.6785" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="180.0085" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
<text text-anchor="start" x="178.058" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
<text text-anchor="start" x="193.061" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
<text text-anchor="start" x="185.842" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
<text text-anchor="start" x="187.2225" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
<text text-anchor="start" x="171.385" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text>
<text text-anchor="start" x="180.8335" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
<text text-anchor="start" x="169.1705" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text>
</g>
<!-- A11&#45;&gt;A12 -->
<g id="edge13" class="edge">
<title>A11&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M258.9382,-1066.3836C267.808,-1041.1849 276.6736,-1014.8736 284.348,-990 296.0407,-952.103 307.205,-908.562 315.0802,-876.1703"/>
<polygon fill="none" stroke="#000000" points="255.5783,-1065.3868 255.541,-1075.9815 262.1771,-1067.7225 255.5783,-1065.3868"/>
</g>
<!-- A11&#45;&gt;A13 -->
<g id="edge14" class="edge">
<title>A11&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M170.4636,-1066.2454C162.9411,-1041.0063 155.2684,-1014.7156 148.348,-990 137.6936,-951.9489 126.506,-908.6064 118.3587,-876.3336"/>
<polygon fill="none" stroke="#000000" points="167.1205,-1067.2825 173.3374,-1075.8616 173.8274,-1065.2781 167.1205,-1067.2825"/>
</g>
<!-- A14&#45;&gt;A9 -->
<g id="edge20" class="edge">
<title>A14&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M207.7336,-1465.2507C227.8317,-1432.1375 252.0435,-1390.492 271.348,-1352 274.3143,-1346.0855 277.269,-1340.0055 280.1912,-1333.8349"/>
<polygon fill="none" stroke="#000000" points="204.6683,-1463.555 202.4506,-1473.9151 210.6449,-1467.1992 204.6683,-1463.555"/>
</g>
<!-- A14&#45;&gt;A10 -->
<g id="edge21" class="edge">
<title>A14&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M154.3052,-1464.1339C145.0994,-1422.2665 132.7762,-1366.2214 121.7769,-1316.197"/>
<polygon fill="none" stroke="#000000" points="150.8909,-1464.9045 156.4568,-1473.9196 157.7276,-1463.4012 150.8909,-1464.9045"/>
</g>
<!-- A15 -->
<g id="node16" class="node">
<title>A15</title>
<polygon fill="none" stroke="#000000" points="263.348,-1622 263.348,-1654 338.348,-1654 338.348,-1622 263.348,-1622"/>
<text text-anchor="start" x="283.0655" y="-1635" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
<polygon fill="none" stroke="#000000" points="263.348,-1470 263.348,-1622 338.348,-1622 338.348,-1470 263.348,-1470"/>
<text text-anchor="start" x="292.5095" y="-1603" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
<text text-anchor="start" x="273.338" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
<text text-anchor="start" x="274.453" y="-1567" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
<text text-anchor="start" x="284.4565" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
<text text-anchor="start" x="274.7375" y="-1543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
<text text-anchor="start" x="282.79" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
<text text-anchor="start" x="294.7395" y="-1519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
<text text-anchor="start" x="281.4015" y="-1507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
<text text-anchor="start" x="279.727" y="-1495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
<text text-anchor="start" x="294.1845" y="-1483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
<polygon fill="none" stroke="#000000" points="263.348,-1402 263.348,-1470 338.348,-1470 338.348,-1402 263.348,-1402"/>
<text text-anchor="start" x="274.738" y="-1451" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
<text text-anchor="start" x="278.072" y="-1439" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
<text text-anchor="start" x="275.572" y="-1427" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
<text text-anchor="start" x="285.8505" y="-1415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A15&#45;&gt;A9 -->
<g id="edge23" class="edge">
<title>A15&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M315.5861,-1391.2689C317.0212,-1378.3918 318.4833,-1365.272 319.9365,-1352.2329"/>
<polygon fill="#000000" stroke="#000000" points="314.4417,-1401.5379 311.077,-1391.101 314.9955,-1396.5687 315.5493,-1391.5995 315.5493,-1391.5995 315.5493,-1391.5995 314.9955,-1396.5687 320.0217,-1392.0979 314.4417,-1401.5379 314.4417,-1401.5379"/>
<text text-anchor="middle" x="326.3292" y="-1368.1837" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="308.049" y="-1379.5871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A15&#45;&gt;A10 -->
<g id="edge22" class="edge">
<title>A15&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M257.1725,-1451.7757C245.6828,-1434.4941 232.4915,-1416.871 218.348,-1402 192.2483,-1374.5578 171.9188,-1382.411 149.348,-1352 141.2346,-1341.0684 134.273,-1328.8227 128.315,-1316.1269"/>
<polygon fill="#000000" stroke="#000000" points="262.6729,-1460.2184 253.4437,-1454.2962 259.9435,-1456.0291 257.2141,-1451.8397 257.2141,-1451.8397 257.2141,-1451.8397 259.9435,-1456.0291 260.9845,-1449.3833 262.6729,-1460.2184 262.6729,-1460.2184"/>
<text text-anchor="middle" x="143.7688" y="-1325.8225" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="245.6964" y="-1446.645" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 37 KiB

49
app/proxy_2.yuml Normal file
View File

@@ -0,0 +1,49 @@
// {type:class}
// {direction:topDown}
// {generate:true}
[note: You can stick notes on diagrams too!{bg:cornsilk}]
[IterRegistry||__iter__]
[InverterG3|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()]
[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()]
[<<AsyncIfc>>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_log();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()]
[AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb]
[AsyncStream|reader;writer;addr;r_addr;l_addr|;<async>loop;disc();close();healthy();;__async_read();__async_write();__async_forward()]
[AsyncStreamServer|create_remote|<async>server_loop();<async>_async_forward();<async>publish_outstanding_mqtt();close()]
[AsyncStreamClient||<async>client_loop();<async>_async_forward())]
[<<AsyncIfc>>]^-.-[AsyncIfcImpl]
[AsyncIfcImpl]^[AsyncStream]
[AsyncStream]^[AsyncStreamServer]
[AsyncStream]^[AsyncStreamClient]
[Talent|ifc:AsyncIfc;conn_no;addr;;await_conn_resp_cnt;id_str;contact_name;contact_mail;db:InfosG3;mb:Modbus;switch|msg_contact_info();msg_ota_update();msg_get_time();msg_collector_data();msg_inverter_data();msg_unknown();;healthy();close()]
[Talent]<remote-[InverterG3]
[InverterG3]-remote>[AsyncStreamClient]
[Talent]<-local++[InverterG3]
[InverterG3]++local->[AsyncStreamServer]
[SolarmanV5|ifc:AsyncIfc;conn_no;addr;;control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;healthy();close()]
[SolarmanV5]<remote-[InverterG3P]
[InverterG3P]-remote>[AsyncStreamClient]
[SolarmanV5]<-local++[InverterG3P]
[InverterG3P]++local->[AsyncStreamServer]
[Infos|stat;new_stat_data;info_dev|static_init();dev_value();inc_counter();dec_counter();ha_proxy_conf;ha_conf;ha_remove;update_db;set_db_def_value;get_db_value;ignore_this_device]
[Infos]^[InfosG3||ha_confs();parse()]
[Infos]^[InfosG3P||ha_confs();parse()]
[Talent]use->[<<AsyncIfc>>]
[Talent]->[InfosG3]
[SolarmanV5]use->[<<AsyncIfc>>]
[SolarmanV5]->[InfosG3P]
[IterRegistry]^[Message|node_id|inc_counter();dec_counter()]
[Message]^[Talent]
[Message]^[SolarmanV5]
[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()]
[Modbus]<1-has[SolarmanV5]
[Modbus]<1-has[Talent]

View File

@@ -56,21 +56,11 @@ class AsyncIfc(ABC):
''' add data to forward queue'''
pass # pragma: no cover
@abstractmethod
def fwd_flush(self):
''' send forward queue and clears it'''
pass # pragma: no cover
@abstractmethod
def fwd_log(self, level, info):
''' log the forward queue'''
pass # pragma: no cover
@abstractmethod
def fwd_clear(self):
''' clear forward queue'''
pass # pragma: no cover
#
# RX - QUEUE
#

View File

@@ -7,12 +7,12 @@ from typing import Self
from itertools import count
if __name__ == "app.src.async_stream":
from app.src.inverter import Inverter
from app.src.proxy import Proxy
from app.src.byte_fifo import ByteFifo
from app.src.async_ifc import AsyncIfc
from app.src.infos import Infos
else: # pragma: no cover
from inverter import Inverter
from proxy import Proxy
from byte_fifo import ByteFifo
from async_ifc import AsyncIfc
from infos import Infos
@@ -80,18 +80,10 @@ class AsyncIfcImpl(AsyncIfc):
''' add data to forward queue'''
self.fwd_fifo += data
def fwd_flush(self):
''' send forward queue and clears it'''
self.fwd_fifo()
def fwd_log(self, level, info):
''' log the forward queue'''
self.fwd_fifo.logging(level, info)
def fwd_clear(self):
''' clear forward queue'''
self.fwd_fifo.clear()
def rx_get(self, size: int = None) -> bytearray:
'''removes size numbers of bytes and return them'''
return self.rx_fifo.get(size)
@@ -127,13 +119,18 @@ class AsyncIfcImpl(AsyncIfc):
class StreamPtr():
'''Descr StreamPtr'''
def __init__(self, _stream):
def __init__(self, _stream, _ifc=None):
self.stream = _stream
self.ifc = _ifc
@property
def ifc(self):
return self._ifc
@ifc.setter
def ifc(self, value):
self._ifc = value
@property
def stream(self):
return self._stream
@@ -141,10 +138,6 @@ class StreamPtr():
@stream.setter
def stream(self, value):
self._stream = value
if value:
self._ifc = value.ifc
else:
self._ifc = None
class AsyncStream(AsyncIfcImpl):
@@ -177,8 +170,8 @@ class AsyncStream(AsyncIfcImpl):
self._writer.write(self.tx_fifo.get())
def __timeout(self) -> int:
if self.timeout_cb is callable:
return self.timeout_cb
if self.timeout_cb:
return self.timeout_cb()
return 360
async def loop(self) -> Self:
@@ -186,10 +179,7 @@ class AsyncStream(AsyncIfcImpl):
self.proc_start = time.time()
while True:
try:
proc = time.time() - self.proc_start
if proc > self.proc_max:
self.proc_max = proc
self.proc_start = None
self.__calc_proc_time()
dead_conn_to = self.__timeout()
await asyncio.wait_for(self.__async_read(),
dead_conn_to)
@@ -204,7 +194,6 @@ class AsyncStream(AsyncIfcImpl):
f'connection timeout ({dead_conn_to}s) '
f'for {self.l_addr}')
await self.disc()
self.close()
return self
except OSError as error:
@@ -212,14 +201,12 @@ class AsyncStream(AsyncIfcImpl):
f'{error} for l{self.l_addr} | '
f'r{self.r_addr}')
await self.disc()
self.close()
return self
except RuntimeError as error:
logger.info(f'[{self.node_id}:{self.conn_no}] '
f'{error} for {self.l_addr}')
await self.disc()
self.close()
return self
except Exception:
@@ -229,8 +216,16 @@ class AsyncStream(AsyncIfcImpl):
f"{traceback.format_exc()}")
await asyncio.sleep(0) # be cooperative to other task
def __calc_proc_time(self):
if self.proc_start:
proc = time.time() - self.proc_start
if proc > self.proc_max:
self.proc_max = proc
self.proc_start = None
async def disc(self) -> None:
"""Async disc handler for graceful disconnect"""
self.remote = None
if self._writer.is_closing():
return
logger.debug(f'AsyncStream.disc() l{self.l_addr} | r{self.r_addr}')
@@ -238,6 +233,7 @@ class AsyncStream(AsyncIfcImpl):
await self._writer.wait_closed()
def close(self) -> None:
logging.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
"""close handler for a no waiting disconnect
hint: must be called before releasing the connection instance
@@ -246,7 +242,6 @@ class AsyncStream(AsyncIfcImpl):
self._reader.feed_eof() # abort awaited read
if self._writer.is_closing():
return
logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
self._writer.close()
def healthy(self) -> bool:
@@ -271,7 +266,7 @@ class AsyncStream(AsyncIfcImpl):
self.proc_start = time.time()
self.rx_fifo += data
wait = self.rx_fifo() # call read in parent class
if wait > 0:
if wait and wait > 0:
await asyncio.sleep(wait)
else:
raise RuntimeError("Peer closed.")
@@ -292,21 +287,22 @@ class AsyncStream(AsyncIfcImpl):
except OSError as error:
if self.remote.stream:
rmt = self.remote.stream
self.remote.stream = None
logger.error(f'[{rmt.node_id}:{rmt.conn_no}] Fwd: {error} for '
f'l{rmt._ifc.l_addr} | r{rmt._ifc.r_addr}')
await rmt._ifc.disc()
rmt._ifc.close()
rmt = self.remote
logger.error(f'[{rmt.stream.node_id}:{rmt.stream.conn_no}] '
f'Fwd: {error} for '
f'l{rmt.ifc.l_addr} | r{rmt.ifc.r_addr}')
await rmt.ifc.disc()
if rmt.ifc.close_cb:
rmt.ifc.close_cb()
except RuntimeError as error:
if self.remote.stream:
rmt = self.remote.stream
self.remote.stream = None
logger.info(f'[{rmt.node_id}:{rmt.conn_no}] '
f'Fwd: {error} for {rmt._ifc.l_addr}')
await rmt._ifc.disc()
rmt._ifc.close()
rmt = self.remote
logger.info(f'[{rmt.stream.node_id}:{rmt.stream.conn_no}] '
f'Fwd: {error} for {rmt.ifc.l_addr}')
await rmt.ifc.disc()
if rmt.ifc.close_cb:
rmt.ifc.close_cb()
except Exception:
Infos.inc_counter('SW_Exception')
@@ -315,18 +311,24 @@ class AsyncStream(AsyncIfcImpl):
f"{traceback.format_exc()}")
def __del__(self):
logger.debug(
logger.info(
f"AsyncStream.__del__ l{self.l_addr} | r{self.r_addr}")
class AsyncStreamServer(AsyncStream):
def __init__(self, reader: StreamReader, writer: StreamWriter,
async_publ_mqtt, async_create_remote,
async_publ_mqtt, create_remote,
rstream: "StreamPtr") -> None:
AsyncStream.__init__(self, reader, writer, rstream)
self.async_create_remote = async_create_remote
self.create_remote = create_remote
self.async_publ_mqtt = async_publ_mqtt
def close(self) -> None:
logging.debug('AsyncStreamServer.close()')
self.create_remote = None
self.async_publ_mqtt = None
super().close()
async def server_loop(self) -> None:
'''Loop for receiving messages from the inverter (server-side)'''
logger.info(f'[{self.node_id}:{self.conn_no}] '
@@ -341,7 +343,7 @@ class AsyncStreamServer(AsyncStream):
# if the server connection closes, we also have to disconnect
# the connection to te TSUN cloud
if self.remote.stream:
if self.remote and self.remote.stream:
logger.info(f'[{self.node_id}:{self.conn_no}] disc client '
f'connection: [{self.remote.ifc.node_id}:'
f'{self.remote.ifc.conn_no}]')
@@ -350,7 +352,7 @@ class AsyncStreamServer(AsyncStream):
async def _async_forward(self) -> None:
"""forward handler transmits data over the remote connection"""
if not self.remote.stream:
await self.async_create_remote()
await self.create_remote()
if self.remote.stream and \
self.remote.ifc.init_new_client_conn_cb():
await self.remote.ifc._AsyncStream__async_write()
@@ -365,24 +367,21 @@ class AsyncStreamServer(AsyncStream):
'''Publish all outstanding MQTT topics'''
try:
await self.async_publ_mqtt()
await Inverter._async_publ_mqtt_proxy_stat('proxy')
await Proxy._async_publ_mqtt_proxy_stat('proxy')
except Exception:
pass
def close(self) -> None:
"""close handler for a no waiting disconnect
hint: must be called before releasing the connection instance
"""
self.async_create_remote = None
self.async_publ_mqtt = None
super().close()
class AsyncStreamClient(AsyncStream):
def __init__(self, reader: StreamReader, writer: StreamWriter,
rstream: "StreamPtr") -> None:
rstream: "StreamPtr", close_cb) -> None:
AsyncStream.__init__(self, reader, writer, rstream)
self.close_cb = close_cb
def close(self) -> None:
logging.debug('AsyncStreamClient.close()')
self.close_cb = None
super().close()
async def client_loop(self, _: str) -> None:
'''Loop for receiving messages from the TSUN cloud (client-side)'''
@@ -391,21 +390,8 @@ class AsyncStreamClient(AsyncStream):
'Client loop stopped for'
f' l{self.l_addr}')
server_stream = self.remote.stream
# if the client connection closes, we don't touch the server
# connection. Instead we erase the client connection stream,
# thus on the next received packet from the inverter, we can
# establish a new connection to the TSUN cloud
if server_stream.remote.ifc == self:
# logging.debug(f'Client l{client_stream.l_addr} refs:'
# f' {gc.get_referrers(client_stream)}')
# than erase client connection
server_stream.remote.stream = None # erases stream and ifc link
# erase backlink to inverter
self.remote.stream = None
if self.close_cb:
self.close_cb()
async def _async_forward(self) -> None:
"""forward handler transmits data over the remote connection"""

View File

@@ -18,10 +18,11 @@ class ByteFifo:
self.__buf.extend(data)
return self
def __call__(self) -> None:
def __call__(self):
'''triggers the observer'''
if callable(self.__trigger_cb):
return self.__trigger_cb()
return None
def get(self, size: int = None) -> bytearray:
'''removes size numbers of byte and return them'''

View File

@@ -1,16 +0,0 @@
import logging
if __name__ == "app.src.gen3.connection_g3":
from app.src.gen3.talent import Talent
else: # pragma: no cover
from gen3.talent import Talent
logger = logging.getLogger('conn')
class ConnectionG3(Talent):
def __init__(self, addr, ifc, server_side, id_str=b'') -> None:
super().__init__(addr, server_side, ifc, id_str)
def close(self):
super().close()

View File

@@ -1,41 +1,13 @@
import logging
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3.inverter_g3":
from app.src.inverter_base import InverterBase
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStreamServer
from app.src.gen3.connection_g3 import ConnectionG3
from app.src.gen3.talent import Talent
else: # pragma: no cover
from inverter_base import InverterBase
from async_stream import StreamPtr
from async_stream import AsyncStreamServer
from gen3.connection_g3 import ConnectionG3
logger_mqtt = logging.getLogger('mqtt')
from gen3.talent import Talent
class InverterG3(InverterBase):
def __init__(self, reader: StreamReader, writer: StreamWriter, addr):
super().__init__()
self.addr = addr
self.remote = StreamPtr(None)
ifc = AsyncStreamServer(reader, writer,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
self.remote = StreamPtr(None)
self.local = StreamPtr(
ConnectionG3(addr, ifc, True)
)
async def async_create_remote(self) -> None:
await InverterBase.async_create_remote(
self, 'tsun', ConnectionG3)
def close(self) -> None:
logging.debug(f'InverterG3.close() {self.addr}')
self.local.stream.close()
# logging.info(f'Inverter refs: {gc.get_referrers(self)}')
def __init__(self, reader: StreamReader, writer: StreamWriter):
super().__init__(reader, writer, 'tsun', Talent)

View File

@@ -46,7 +46,8 @@ class Talent(Message):
MB_REGULAR_TIMEOUT = 60
TXT_UNKNOWN_CTRL = 'Unknown Ctrl'
def __init__(self, addr, server_side: bool, ifc: "AsyncIfc", id_str=b''):
def __init__(self, addr, ifc: "AsyncIfc", server_side: bool,
client_mode: bool = False, id_str=b''):
super().__init__(server_side, self.send_modbus_cb, mb_timeout=15)
ifc.rx_set_cb(self.read)
ifc.prot_set_timeout_cb(self._timeout)
@@ -95,10 +96,6 @@ class Talent(Message):
'''
Our puplic methods
'''
def healthy(self) -> bool:
logger.debug('Talent healthy()')
return self.ifc.healthy()
def close(self) -> None:
logging.debug('Talent.close()')
if self.server_side:
@@ -116,11 +113,11 @@ class Talent(Message):
self.log_lvl.clear()
self.state = State.closed
self.mb_timer.close()
self.ifc.close()
self.ifc.rx_set_cb(None)
self.ifc.prot_set_timeout_cb(None)
self.ifc.prot_set_init_new_client_conn_cb(None)
self.ifc.prot_set_update_header_cb(None)
self.ifc = None
super().close()
def __set_serial_no(self, serial_no: str):
@@ -438,8 +435,8 @@ class Talent(Message):
result = struct.unpack_from('!q', self.ifc.rx_peek(),
self.header_len)
self.ts_offset = result[0]-ts
if self.remote.stream:
self.remote.stream.ts_offset = self.ts_offset
if self.ifc.remote.stream:
self.ifc.remote.stream.ts_offset = self.ts_offset
logger.debug(f'tsun-time: {int(result[0]):08x}'
f' proxy-time: {ts:08x}'
f' offset: {self.ts_offset}')
@@ -597,9 +594,8 @@ class Talent(Message):
self.header_len+self.data_len]
if self.ctrl.is_req():
if self.remote.stream.mb.recv_req(data[hdr_len:],
self.remote.stream.
msg_forward):
rstream = self.ifc.remote.stream
if rstream.mb.recv_req(data[hdr_len:], rstream.msg_forward):
self.inc_counter('Modbus_Command')
else:
self.inc_counter('Invalid_Msg_Format')

View File

@@ -1,18 +0,0 @@
import logging
if __name__ == "app.src.gen3plus.connection_g3p":
from app.src.gen3plus.solarman_v5 import SolarmanV5
else: # pragma: no cover
from gen3plus.solarman_v5 import SolarmanV5
logger = logging.getLogger('conn')
class ConnectionG3P(SolarmanV5):
def __init__(self, addr, ifc, server_side,
client_mode: bool = False) -> None:
super().__init__(addr, server_side, client_mode, ifc)
def close(self):
super().close()
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')

View File

@@ -1,41 +1,15 @@
import logging
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3plus.inverter_g3p":
from app.src.inverter_base import InverterBase
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStreamServer
from app.src.gen3plus.connection_g3p import ConnectionG3P
from app.src.gen3plus.solarman_v5 import SolarmanV5
else: # pragma: no cover
from inverter_base import InverterBase
from async_stream import StreamPtr
from async_stream import AsyncStreamServer
from gen3plus.connection_g3p import ConnectionG3P
logger_mqtt = logging.getLogger('mqtt')
from gen3plus.solarman_v5 import SolarmanV5
class InverterG3P(InverterBase):
def __init__(self, reader: StreamReader, writer: StreamWriter, addr,
def __init__(self, reader: StreamReader, writer: StreamWriter,
client_mode: bool = False):
super().__init__()
self.addr = addr
self.remote = StreamPtr(None)
ifc = AsyncStreamServer(reader, writer,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
self.local = StreamPtr(
ConnectionG3P(addr, ifc, True, client_mode)
)
async def async_create_remote(self) -> None:
await InverterBase.async_create_remote(
self, 'solarman', ConnectionG3P)
def close(self) -> None:
logging.debug(f'InverterG3P.close() {self.addr}')
self.local.stream.close()
# logger.debug (f'Inverter refs: {gc.get_referrers(self)}')
super().__init__(reader, writer, 'solarman',
SolarmanV5, client_mode)

View File

@@ -62,8 +62,8 @@ class SolarmanV5(Message):
HDR_FMT = '<BLLL'
'''format string for packing of the header'''
def __init__(self, addr, server_side: bool, client_mode: bool,
ifc: "AsyncIfc"):
def __init__(self, addr, ifc: "AsyncIfc",
server_side: bool, client_mode: bool):
super().__init__(server_side, self.send_modbus_cb, mb_timeout=8)
ifc.rx_set_cb(self.read)
ifc.prot_set_timeout_cb(self._timeout)
@@ -155,10 +155,6 @@ class SolarmanV5(Message):
'''
Our puplic methods
'''
def healthy(self) -> bool:
logger.debug('SolarmanV5 healthy()')
return self.ifc.healthy()
def close(self) -> None:
logging.debug('Solarman.close()')
if self.server_side:
@@ -176,11 +172,11 @@ class SolarmanV5(Message):
self.log_lvl.clear()
self.state = State.closed
self.mb_timer.close()
self.ifc.close()
self.ifc.rx_set_cb(None)
self.ifc.prot_set_timeout_cb(None)
self.ifc.prot_set_init_new_client_conn_cb(None)
self.ifc.prot_set_update_header_cb(None)
self.ifc = None
super().close()
async def send_start_cmd(self, snr: int, host: str,
@@ -633,9 +629,9 @@ class SolarmanV5(Message):
self.forward_at_cmd_resp = True
elif ftype == self.MB_RTU_CMD:
if self.remote.stream.mb.recv_req(data[15:],
self.remote.stream.
__forward_msg):
rstream = self.ifc.remote.stream
if rstream.mb.recv_req(data[15:],
rstream.__forward_msg):
self.inc_counter('Modbus_Command')
else:
logger.error('Invalid Modbus Msg')

View File

@@ -1,30 +1,99 @@
import weakref
import asyncio
import logging
import traceback
import json
from aiomqtt import MqttCodeError
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.inverter_base":
from app.src.inverter import Inverter
from app.src.inverter_ifc import InverterIfc
from app.src.proxy import Proxy
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStreamClient
from app.src.async_stream import AsyncStreamServer
from app.src.config import Config
from app.src.infos import Infos
else: # pragma: no cover
from inverter import Inverter
from inverter_ifc import InverterIfc
from proxy import Proxy
from async_stream import StreamPtr
from async_stream import AsyncStreamClient
from async_stream import AsyncStreamServer
from config import Config
from infos import Infos
logger_mqtt = logging.getLogger('mqtt')
class InverterBase(Inverter):
def __init__(self):
self.__ha_restarts = -1
class InverterBase(InverterIfc, Proxy):
async def async_create_remote(self, inv_prot: str, conn_class) -> None:
def __init__(self, reader: StreamReader, writer: StreamWriter,
config_id: str, prot_class,
client_mode: bool = False):
Proxy.__init__(self)
self._registry.append(weakref.ref(self))
self.addr = writer.get_extra_info('peername')
self.config_id = config_id
self.prot_class = prot_class
self.__ha_restarts = -1
self.remote = StreamPtr(None)
ifc = AsyncStreamServer(reader, writer,
self.async_publ_mqtt,
self.create_remote,
self.remote)
self.local = StreamPtr(
self.prot_class(self.addr, ifc, True, client_mode), ifc
)
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb) -> None:
logging.debug(f'InverterBase.__exit__() {self.addr}')
self.__del_remote()
self.local.stream.close()
self.local.stream = None
self.local.ifc.close()
self.local.ifc = None
def __del__(self) -> None:
logging.debug(f'InverterBase.__del__() {self.addr}')
def __del_remote(self):
if self.remote.stream:
self.remote.stream.close()
self.remote.stream = None
if self.remote.ifc:
self.remote.ifc.close()
self.remote.ifc = None
async def disc(self, shutdown_started=False) -> None:
if self.remote.stream:
self.remote.stream.shutdown_started = shutdown_started
if self.remote.ifc:
await self.remote.ifc.disc()
if self.local.stream:
self.local.stream.shutdown_started = shutdown_started
if self.local.ifc:
await self.local.ifc.disc()
def healthy(self) -> bool:
logging.debug('InverterBase healthy()')
if self.local.ifc and not self.local.ifc.healthy():
return False
if self.remote.ifc and not self.remote.ifc.healthy():
return False
return True
async def create_remote(self) -> None:
'''Establish a client connection to the TSUN cloud'''
tsun = Config.get(inv_prot)
tsun = Config.get(self.config_id)
host = tsun['host']
port = tsun['port']
addr = (host, port)
@@ -34,15 +103,18 @@ class InverterBase(Inverter):
logging.info(f'[{stream.node_id}] Connect to {addr}')
connect = asyncio.open_connection(host, port)
reader, writer = await connect
ifc = AsyncStreamClient(reader, writer,
self.remote)
ifc = AsyncStreamClient(
reader, writer, self.local, self.__del_remote)
self.remote.ifc = ifc
if hasattr(stream, 'id_str'):
self.remote.stream = conn_class(
addr, ifc, False, stream.id_str)
self.remote.stream = self.prot_class(
addr, ifc, server_side=False,
client_mode=False, id_str=stream.id_str)
else:
self.remote.stream = conn_class(
addr, ifc, False)
self.remote.stream = self.prot_class(
addr, ifc, server_side=False,
client_mode=False)
logging.info(f'[{self.remote.stream.node_id}:'
f'{self.remote.stream.conn_no}] '
@@ -60,7 +132,7 @@ class InverterBase(Inverter):
async def async_publ_mqtt(self) -> None:
'''publish data to MQTT broker'''
stream = self.local.stream
if not stream.unique_id:
if not stream or not stream.unique_id:
return
# check if new inverter or collector infos are available or when the
# home assistant has changed the status back to online
@@ -76,7 +148,7 @@ class InverterBase(Inverter):
for key in stream.new_data:
await self.__async_publ_mqtt_packet(stream, key)
for key in Infos.new_stat_data:
await Inverter._async_publ_mqtt_proxy_stat(key)
await Proxy._async_publ_mqtt_proxy_stat(key)
except MqttCodeError as error:
logging.error(f'Mqtt except: {error}')

40
app/src/inverter_ifc.py Normal file
View File

@@ -0,0 +1,40 @@
from abc import abstractmethod
import logging
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.inverter_ifc":
from app.src.iter_registry import AbstractIterMeta
else: # pragma: no cover
from iter_registry import AbstractIterMeta
logger_mqtt = logging.getLogger('mqtt')
class InverterIfc(metaclass=AbstractIterMeta):
_registry = []
@abstractmethod
def __init__(self, reader: StreamReader, writer: StreamWriter,
config_id: str, prot_class,
client_mode: bool):
pass # pragma: no cover
@abstractmethod
def __enter__(self):
pass # pragma: no cover
@abstractmethod
def __exit__(self, exc_type, exc, tb):
pass # pragma: no cover
@abstractmethod
def healthy(self) -> bool:
pass # pragma: no cover
@abstractmethod
async def disc(self, shutdown_started=False) -> None:
pass # pragma: no cover
@abstractmethod
async def create_remote(self) -> None:
pass # pragma: no cover

9
app/src/iter_registry.py Normal file
View File

@@ -0,0 +1,9 @@
from abc import ABCMeta
class AbstractIterMeta(ABCMeta):
def __iter__(cls):
for ref in cls._registry:
obj = ref()
if obj is not None:
yield obj

View File

@@ -1,13 +1,15 @@
import logging
import weakref
from typing import Callable, Generator
from typing import Callable
from enum import Enum
if __name__ == "app.src.messages":
from app.src.protocol_ifc import ProtocolIfc
from app.src.infos import Infos, Register
from app.src.modbus import Modbus
else: # pragma: no cover
from protocol_ifc import ProtocolIfc
from infos import Infos, Register
from modbus import Modbus
@@ -66,14 +68,6 @@ def hex_dump_memory(level, info, data, data_len):
tracer.log(level, '\n'.join(lines))
class IterRegistry(type):
def __iter__(cls) -> Generator['Message', None, None]:
for ref in cls._registry:
obj = ref()
if obj is not None:
yield obj
class State(Enum):
'''state of the logical connection'''
init = 0
@@ -88,8 +82,7 @@ class State(Enum):
'''connection closed'''
class Message(metaclass=IterRegistry):
_registry = []
class Message(ProtocolIfc):
MAX_START_TIME = 400
'''maximum time without a received msg in sec'''
MAX_INV_IDLE_TIME = 120

View File

@@ -25,8 +25,9 @@ class ModbusConn():
'''Establish a client connection to the TSUN cloud'''
connection = asyncio.open_connection(self.host, self.port)
reader, writer = await connection
self.inverter = InverterG3P(reader, writer, self.addr,
self.inverter = InverterG3P(reader, writer,
client_mode=True)
self.inverter.__enter__()
stream = self.inverter.local.stream
logging.info(f'[{stream.node_id}:{stream.conn_no}] '
f'Connected to {self.addr}')
@@ -37,7 +38,7 @@ class ModbusConn():
async def __aexit__(self, exc_type, exc, tb):
Infos.dec_counter('Inverter_Cnt')
await self.inverter.local.ifc.publish_outstanding_mqtt()
self.inverter.close()
self.inverter.__exit__(exc_type, exc, tb)
class ModbusTcp():

21
app/src/protocol_ifc.py Normal file
View File

@@ -0,0 +1,21 @@
from abc import abstractmethod
if __name__ == "app.src.protocol_ifc":
from app.src.iter_registry import AbstractIterMeta
from app.src.async_ifc import AsyncIfc
else: # pragma: no cover
from iter_registry import AbstractIterMeta
from async_ifc import AsyncIfc
class ProtocolIfc(metaclass=AbstractIterMeta):
_registry = []
@abstractmethod
def __init__(self, addr, ifc: "AsyncIfc", server_side: bool,
client_mode: bool = False, id_str=b''):
pass # pragma: no cover
@abstractmethod
def close(self):
pass # pragma: no cover

View File

@@ -2,7 +2,7 @@ import asyncio
import logging
import json
if __name__ == "app.src.inverter":
if __name__ == "app.src.proxy":
from app.src.config import Config
from app.src.mqtt import Mqtt
from app.src.infos import Infos
@@ -14,8 +14,8 @@ else: # pragma: no cover
logger_mqtt = logging.getLogger('mqtt')
class Inverter():
'''class Inverter is a baseclass
class Proxy():
'''class Proxy is a baseclass
The class has some class method for managing common resources like a
connection to the MQTT broker or proxy error counter which are common
@@ -34,12 +34,12 @@ class Inverter():
destroyed
methods:
async_create_remote(): Establish a client connection to the TSUN cloud
create_remote(): Establish a client connection to the TSUN cloud
async_publ_mqtt(): Publish data to MQTT broker
'''
@classmethod
def class_init(cls) -> None:
logging.debug('Inverter.class_init')
logging.debug('Proxy.class_init')
# initialize the proxy statistics
Infos.static_init()
cls.db_stat = Infos()
@@ -61,7 +61,7 @@ class Inverter():
# reset at midnight when you restart the proxy just before
# midnight!
inverters = Config.get('inverters')
# logger.debug(f'Inverters: {inverters}')
# logger.debug(f'Proxys: {inverters}')
for inv in inverters.values():
if (type(inv) is dict):
node_id = inv['node_id']
@@ -100,7 +100,7 @@ class Inverter():
@classmethod
def class_close(cls, loop) -> None: # pragma: no cover
logging.debug('Inverter.class_close')
logging.debug('Proxy.class_close')
logging.info('Close MQTT Task')
loop.run_until_complete(cls.mqtt.close())
cls.mqtt = None

View File

@@ -5,8 +5,8 @@ import os
from asyncio import StreamReader, StreamWriter
from aiohttp import web
from logging import config # noqa F401
from messages import Message
from inverter import Inverter
from proxy import Proxy
from inverter_ifc import InverterIfc
from gen3.inverter_g3 import InverterG3
from gen3plus.inverter_g3p import InverterG3P
from scheduler import Schedule
@@ -38,9 +38,9 @@ async def healthy(request):
if proxy_is_up:
# logging.info('web reqeust healthy()')
for stream in Message:
for inverter in InverterIfc:
try:
res = stream.healthy()
res = inverter.healthy()
if not res:
return web.Response(status=503, text="I have a problem")
except Exception as err:
@@ -73,8 +73,8 @@ async def webserver(addr, port):
async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
'''Handles a new incoming connection and starts an async loop'''
addr = writer.get_extra_info('peername')
await inv_class(reader, writer, addr).local.ifc.server_loop()
with inv_class(reader, writer) as inv:
await inv.local.ifc.server_loop()
async def handle_shutdown(web_task):
@@ -87,25 +87,13 @@ async def handle_shutdown(web_task):
#
# first, disc all open TCP connections gracefully
#
for stream in Message:
stream.shutdown_started = True
try:
await asyncio.wait_for(stream.disc(), 2)
except Exception:
pass
for inverter in InverterIfc:
await inverter.disc(True)
logging.info('Proxy disconnecting done')
#
# second, close all open TCP connections
#
for stream in Message:
stream.close()
await asyncio.sleep(0.1) # give time for closing
logging.info('Proxy closing done')
#
# third, cancel the web server
# second, cancel the web server
#
web_task.cancel()
await web_task
@@ -164,9 +152,9 @@ if __name__ == "__main__":
ConfigErr = Config.class_init()
if ConfigErr is not None:
logging.info(f'ConfigErr: {ConfigErr}')
Inverter.class_init()
Proxy.class_init()
Schedule.start()
mb_tcp = ModbusTcp(loop)
ModbusTcp(loop)
#
# Create tasks for our listening servers. These must be tasks! If we call
@@ -197,7 +185,7 @@ if __name__ == "__main__":
pass
finally:
logging.info("Event loop is stopped")
Inverter.class_close(loop)
Proxy.class_close(loop)
logging.debug('Close event loop')
loop.close()
logging.info(f'Finally, exit Server "{serv_name}"')

View File

@@ -0,0 +1,292 @@
# test_with_pytest.py
import pytest
import asyncio
import gc
import time
from app.src.infos import Infos
from app.src.inverter_base import InverterBase
from app.src.async_stream import AsyncStreamServer, AsyncStreamClient
from app.tests.test_modbus_tcp import FakeReader, FakeWriter
from app.tests.test_inverter_base import config_conn, patch_open_connection
pytest_plugins = ('pytest_asyncio',)
# initialize the proxy statistics
Infos.static_init()
def test_timeout_cb():
reader = FakeReader()
writer = FakeWriter()
def timeout():
return 13
ifc = AsyncStreamClient(reader, writer, None, None)
assert 360 == ifc._AsyncStream__timeout()
ifc.prot_set_timeout_cb(timeout)
assert 13 == ifc._AsyncStream__timeout()
ifc.prot_set_timeout_cb(None)
assert 360 == ifc._AsyncStream__timeout()
# call healthy outside the contexter manager (__exit__() was called)
assert ifc.healthy()
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
def test_health():
reader = FakeReader()
writer = FakeWriter()
ifc = AsyncStreamClient(reader, writer, None, None)
ifc.proc_start = time.time()
assert ifc.healthy()
ifc.proc_start = time.time() -10
assert not ifc.healthy()
ifc.proc_start = None
assert ifc.healthy()
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_close_cb():
assert asyncio.get_running_loop()
reader = FakeReader()
writer = FakeWriter()
cnt = 0
def timeout():
return 0.1
def closed():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, closed)
ifc.prot_set_timeout_cb(timeout)
await ifc.client_loop('')
assert cnt == 1
ifc.prot_set_timeout_cb(timeout)
await ifc.client_loop('')
assert cnt == 1 # check that the closed method would not be called
del ifc
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, None)
ifc.prot_set_timeout_cb(timeout)
await ifc.client_loop('')
assert cnt == 0
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_read():
global test
assert asyncio.get_running_loop()
reader = FakeReader()
reader.test = FakeReader.RD_TEST_13_BYTES
reader.on_recv.set()
writer = FakeWriter()
cnt = 0
def timeout():
return 1
def closed():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
def app_read():
nonlocal ifc
ifc.proc_start -= 3
return 0.01 # async wait of 0.01
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, closed)
ifc.prot_set_timeout_cb(timeout)
ifc.rx_set_cb(app_read)
await ifc.client_loop('')
print('End loop')
assert ifc.proc_max >= 3
assert 13 == ifc.rx_len()
assert cnt == 1
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_write():
global test
assert asyncio.get_running_loop()
reader = FakeReader()
reader.test = FakeReader.RD_TEST_13_BYTES
reader.on_recv.set()
writer = FakeWriter()
cnt = 0
def timeout():
return 1
def closed():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, closed)
ifc.prot_set_timeout_cb(timeout)
ifc.tx_add(b'test-data-resp')
assert 14 == ifc.tx_len()
await ifc.client_loop('')
print('End loop')
assert 13 == ifc.rx_len()
assert 0 == ifc.tx_len()
assert cnt == 1
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_publ_mqtt_cb():
assert asyncio.get_running_loop()
reader = FakeReader()
reader.test = FakeReader.RD_TEST_13_BYTES
reader.on_recv.set()
writer = FakeWriter()
cnt = 0
def timeout():
return 0.1
async def publ_mqtt():
nonlocal cnt
nonlocal ifc
cnt += 1
cnt = 0
ifc = AsyncStreamServer(reader, writer, publ_mqtt, None, None)
assert ifc.async_publ_mqtt
ifc.prot_set_timeout_cb(timeout)
await ifc.server_loop()
assert cnt == 3 # 2 calls in server_loop() and 1 in loop()
assert ifc.async_publ_mqtt
ifc.close() # clears the closed callback
assert not ifc.async_publ_mqtt
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_create_remote_cb():
assert asyncio.get_running_loop()
reader = FakeReader()
writer = FakeWriter()
cnt = 0
def timeout():
return 0.1
async def create_remote():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
cnt = 0
ifc = AsyncStreamServer(reader, writer, None, create_remote, None)
assert ifc.create_remote
await ifc.create_remote()
assert cnt == 1
ifc.prot_set_timeout_cb(timeout)
await ifc.server_loop()
assert not ifc.create_remote
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_sw_exception():
global test
assert asyncio.get_running_loop()
reader = FakeReader()
reader.test = FakeReader.RD_TEST_SW_EXCEPT
reader.on_recv.set()
writer = FakeWriter()
cnt = 0
def timeout():
return 1
def closed():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, closed)
ifc.prot_set_timeout_cb(timeout)
await ifc.client_loop('')
print('End loop')
assert cnt == 1
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_os_error():
global test
assert asyncio.get_running_loop()
reader = FakeReader()
reader.test = FakeReader.RD_TEST_OS_ERROR
reader.on_recv.set()
writer = FakeWriter()
cnt = 0
def timeout():
return 1
def closed():
nonlocal cnt
nonlocal ifc
ifc.close() # clears the closed callback
cnt += 1
cnt = 0
ifc = AsyncStreamClient(reader, writer, None, closed)
ifc.prot_set_timeout_cb(timeout)
await ifc.client_loop('')
print('End loop')
assert cnt == 1
del ifc
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0

View File

@@ -1,100 +0,0 @@
# test_with_pytest.py
import pytest
import asyncio
from itertools import count
from mock import patch
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStream, AsyncStreamServer, AsyncIfcImpl
from app.src.gen3.connection_g3 import ConnectionG3
from app.src.gen3.talent import Talent
class FakeInverter():
async def async_publ_mqtt(self) -> None:
pass # dummy funcion
async def async_create_remote(self, inv_prot: str, conn_class) -> None:
pass # dummy function
def __init__ (self):
self.remote = StreamPtr(None)
self.local = StreamPtr(None)
@pytest.fixture
def patch_async_init():
with patch.object(AsyncStream, '__init__') as conn:
yield conn
@pytest.fixture
def patch_talent_init():
with patch.object(Talent, '__init__') as conn:
yield conn
@pytest.fixture
def patch_healthy():
with patch.object(AsyncStream, 'healthy') as conn:
yield conn
@pytest.fixture
def patch_async_close():
with patch.object(AsyncStream, 'close') as conn:
yield conn
@pytest.fixture
def patch_talent_close():
with patch.object(Talent, 'close') as conn:
yield conn
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
async def read(self, max_len: int):
await self.on_recv.wait()
return b''
def feed_eof(self):
return
class FakeWriter():
def write(self, buf: bytes):
return
def get_extra_info(self, sel: str):
if sel == 'peername':
return 'remote.intern'
elif sel == 'sockname':
return 'sock:1234'
assert False
def is_closing(self):
return False
def close(self):
return
async def wait_closed(self):
return
def test_method_calls(patch_healthy, patch_async_close):
AsyncIfcImpl._ids = count(5)
spy3 = patch_healthy
spy4 = patch_async_close
reader = FakeReader()
writer = FakeWriter()
id_str = "id_string"
addr = ('proxy.local', 10000)
inv = FakeInverter()
ifc = AsyncStreamServer(reader, writer,
inv.async_publ_mqtt,
inv.async_create_remote,
inv.remote)
conn = ConnectionG3(addr, ifc, server_side=True, id_str=id_str)
assert 5 == conn.conn_no
assert 5 == conn.ifc.get_conn_no()
conn.healthy()
spy3.assert_called_once()
conn.close()
spy4.assert_called_once()

View File

@@ -1,105 +0,0 @@
# test_with_pytest.py
import pytest
import asyncio
from itertools import count
from mock import patch
from app.src.singleton import Singleton
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStream, AsyncStreamServer, AsyncIfcImpl
from app.src.gen3plus.connection_g3p import ConnectionG3P
from app.src.gen3plus.solarman_v5 import SolarmanV5
class FakeInverter():
async def async_publ_mqtt(self) -> None:
pass # dummy funcion
async def async_create_remote(self, inv_prot: str, conn_class) -> None:
pass # dummy function
def __init__ (self):
self.remote = StreamPtr(None)
self.local = StreamPtr(None)
@pytest.fixture
def patch_async_init():
with patch.object(AsyncStream, '__init__', return_value= None) as conn:
yield conn
@pytest.fixture
def patch_solarman_init():
with patch.object(SolarmanV5, '__init__') as conn:
yield conn
@pytest.fixture(scope="module", autouse=True)
def module_init():
Singleton._instances.clear()
yield
@pytest.fixture
def patch_healthy():
with patch.object(AsyncStream, 'healthy') as conn:
yield conn
@pytest.fixture
def patch_async_close():
with patch.object(AsyncStream, 'close') as conn:
yield conn
@pytest.fixture
def patch_solarman_close():
with patch.object(SolarmanV5, 'close') as conn:
yield conn
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
async def read(self, max_len: int):
await self.on_recv.wait()
return b''
def feed_eof(self):
return
class FakeWriter():
def write(self, buf: bytes):
return
def get_extra_info(self, sel: str):
if sel == 'peername':
return 'remote.intern'
elif sel == 'sockname':
return 'sock:1234'
assert False
def is_closing(self):
return False
def close(self):
return
async def wait_closed(self):
return
def test_method_calls(patch_healthy, patch_async_close):
AsyncIfcImpl._ids = count(5)
spy3 = patch_healthy
spy4 = patch_async_close
reader = FakeReader()
writer = FakeWriter()
addr = ('proxy.local', 10000)
inv = FakeInverter()
ifc = AsyncStreamServer(reader, writer,
inv.async_publ_mqtt,
inv.async_create_remote,
inv.remote)
conn = ConnectionG3P(addr, ifc, server_side=True, client_mode=False)
assert 5 == conn.conn_no
assert 5 == conn.ifc.get_conn_no()
conn.healthy()
spy3.assert_called_once()
conn.close()
spy4.assert_called_once()

View File

@@ -0,0 +1,304 @@
# test_with_pytest.py
import pytest
import asyncio
import gc
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.gen3.talent import Talent
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.async_stream import AsyncStream, AsyncStreamClient
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
pytest_plugins = ('pytest_asyncio',)
# initialize the proxy statistics
Infos.static_init()
@pytest.fixture
def config_conn():
Config.act_config = {
'mqtt':{
'host': test_hostname,
'port': test_port,
'user': '',
'passwd': ''
},
'ha':{
'auto_conf_prefix': 'homeassistant',
'discovery_prefix': 'homeassistant',
'entity_prefix': 'tsun',
'proxy_node_id': 'test_1',
'proxy_unique_id': ''
},
'tsun':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234}, 'inverters':{'allow_all':True}
}
@pytest.fixture(scope="module", autouse=True)
def module_init():
Singleton._instances.clear()
yield
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
async def read(self, max_len: int):
await self.on_recv.wait()
return b''
def feed_eof(self):
return
class FakeWriter():
def write(self, buf: bytes):
return
def get_extra_info(self, sel: str):
if sel == 'peername':
return 'remote.intern'
elif sel == 'sockname':
return 'sock:1234'
assert False
def is_closing(self):
return False
def close(self):
return
async def wait_closed(self):
return
class TestType(Enum):
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_EXCEPT = 3
test = TestType.RD_TEST_0_BYTES
@pytest.fixture
def patch_open_connection():
async def new_conn(conn):
await asyncio.sleep(0)
return FakeReader(), FakeWriter()
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
raise ConnectionRefusedError
elif test == TestType.RD_TEST_EXCEPT:
raise ValueError("Value cannot be negative") # Compliant
return new_conn(None)
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@pytest.fixture
def patch_healthy():
with patch.object(AsyncStream, 'healthy') as conn:
yield conn
@pytest.fixture
def patch_unhealthy():
def new_healthy(self):
return False
with patch.object(AsyncStream, 'healthy', new_healthy) as conn:
yield conn
@pytest.fixture
def patch_unhealthy_remote():
def new_healthy(self):
return False
with patch.object(AsyncStreamClient, 'healthy', new_healthy) as conn:
yield conn
def test_inverter_iter():
InverterBase._registry.clear()
cnt = 0
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
for inv in InverterBase:
assert inv == inverter
cnt += 1
del inv
del inverter
assert cnt == 1
for inv in InverterBase:
assert False
def test_method_calls(patch_healthy):
spy = patch_healthy
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
# call healthy inside the contexter manager
for inv in InverterBase:
assert inv.healthy()
del inv
spy.assert_called_once()
# outside context manager the health function of AsyncStream is not reachable
cnt = 0
for inv in InverterBase:
assert inv.healthy()
cnt += 1
del inv
assert cnt == 1
spy.assert_called_once() # counter don't increase and keep one!
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
def test_unhealthy(patch_unhealthy):
_ = patch_unhealthy
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
# call healthy inside the contexter manager
assert not inverter.healthy()
# outside context manager the unhealth AsyncStream is released
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
def test_unhealthy_remote(patch_unhealthy_remote):
_ = patch_unhealthy
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
# call healthy inside the contexter manager
assert not inverter.healthy()
# outside context manager the unhealth AsyncStream is released
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_conn(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
assert inverter.remote.ifc
# call healthy inside the contexter manager
assert inverter.healthy()
# call healthy outside the contexter manager (__exit__() was called)
assert inverter.healthy()
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote):
_ = config_conn
_ = patch_open_connection
_ = patch_unhealthy_remote
assert asyncio.get_running_loop()
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
assert inverter.remote.ifc
assert inverter.local.ifc.healthy()
assert not inverter.remote.ifc.healthy()
# call healthy inside the contexter manager
assert not inverter.healthy()
# outside context manager the unhealth AsyncStream is released
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_disc(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
# call disc inside the contexter manager
await inverter.disc()
# call disc outside the contexter manager (__exit__() was called)
await inverter.disc()
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0

View File

@@ -1,15 +1,17 @@
# test_with_pytest.py
import pytest
import asyncio
import sys,gc
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.inverter import Inverter
from app.src.proxy import Proxy
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3.connection_g3 import ConnectionG3
from app.src.gen3.inverter_g3 import InverterG3
from app.src.async_stream import AsyncStream
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
@@ -42,16 +44,6 @@ def module_init():
Singleton._instances.clear()
yield
@pytest.fixture
def patch_conn_init():
with patch.object(ConnectionG3, '__init__', return_value= None) as conn:
yield conn
@pytest.fixture
def patch_conn_close():
with patch.object(ConnectionG3, 'close') as conn:
yield conn
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
@@ -103,132 +95,132 @@ def patch_open_connection():
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@pytest.fixture
def patch_healthy():
with patch.object(AsyncStream, 'healthy') as conn:
yield conn
def test_method_calls(patch_conn_close):
spy2 = patch_conn_close
def test_method_calls(patch_healthy):
spy = patch_healthy
reader = FakeReader()
writer = FakeWriter()
addr = ('proxy.local', 10000)
inverter = InverterG3(reader, writer, addr)
assert inverter.local.stream
assert inverter.local.ifc
InverterBase._registry.clear()
inverter.close()
spy2.assert_called_once()
with InverterG3(reader, writer) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
for inv in InverterBase:
inv.healthy()
del inv
spy.assert_called_once()
del inverter
cnt = 0
for inv in InverterBase:
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close):
async def test_remote_conn(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
with InverterG3(FakeReader(), FakeWriter()) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
del inverter
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
inverter.close()
spy1.assert_called_once()
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_except(config_conn, patch_open_connection, patch_conn_close):
async def test_remote_except(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
global test
test = TestType.RD_TEST_TIMEOUT
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
with InverterG3(FakeReader(), FakeWriter()) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
del inverter
test = TestType.RD_TEST_EXCEPT
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
inverter.close()
spy1.assert_called_once()
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close):
async def test_mqtt_publish(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
stream = inverter.local.stream
await inverter.async_publ_mqtt() # check call with invalid unique_id
stream._Talent__set_serial_no(serial_no= "123344")
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == False
with InverterG3(FakeReader(), FakeWriter()) as inverter:
stream = inverter.local.stream
await inverter.async_publ_mqtt() # check call with invalid unique_id
stream._Talent__set_serial_no(serial_no= "123344")
stream.new_data['env'] = True
stream.db.db['env'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['env'] == False
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == False
Infos.new_stat_data['proxy'] = True
await inverter.async_publ_mqtt()
assert Infos.new_stat_data['proxy'] == False
stream.new_data['env'] = True
stream.db.db['env'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['env'] == False
inverter.close()
spy1.assert_called_once()
Infos.new_stat_data['proxy'] = True
await inverter.async_publ_mqtt()
assert Infos.new_stat_data['proxy'] == False
@pytest.mark.asyncio
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err, patch_conn_close):
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err):
_ = config_conn
_ = patch_open_connection
_ = patch_mqtt_err
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
stream = inverter.local.stream
stream._Talent__set_serial_no(serial_no= "123344")
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
inverter.close()
spy1.assert_called_once()
with InverterG3(FakeReader(), FakeWriter()) as inverter:
stream = inverter.local.stream
stream._Talent__set_serial_no(serial_no= "123344")
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
@pytest.mark.asyncio
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except, patch_conn_close):
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except):
_ = config_conn
_ = patch_open_connection
_ = patch_mqtt_except
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
stream = inverter.local.stream
stream._Talent__set_serial_no(serial_no= "123344")
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
with InverterG3(FakeReader(), FakeWriter()) as inverter:
stream = inverter.local.stream
stream._Talent__set_serial_no(serial_no= "123344")
inverter.close()
spy1.assert_called_once()
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True

View File

@@ -6,9 +6,9 @@ from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.inverter import Inverter
from app.src.proxy import Proxy
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3plus.connection_g3p import ConnectionG3P
from app.src.gen3plus.inverter_g3p import InverterG3P
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
@@ -43,16 +43,6 @@ def module_init():
Singleton._instances.clear()
yield
@pytest.fixture
def patch_conn_init():
with patch.object(ConnectionG3P, '__init__', return_value= None) as conn:
yield conn
@pytest.fixture
def patch_conn_close():
with patch.object(ConnectionG3P, 'close') as conn:
yield conn
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
@@ -104,132 +94,103 @@ def patch_open_connection():
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
def test_method_calls(patch_conn_close):
spy2 = patch_conn_close
def test_method_calls():
reader = FakeReader()
writer = FakeWriter()
addr = ('proxy.local', 10000)
inverter = InverterG3P(reader, writer, addr, client_mode=False)
assert inverter.local.stream
assert inverter.local.ifc
InverterBase._registry.clear()
inverter.close()
spy2.assert_called_once()
with InverterG3P(reader, writer, client_mode=False) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
@pytest.mark.asyncio
async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close):
async def test_remote_conn(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
inverter.close()
spy1.assert_called_once()
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream
@pytest.mark.asyncio
async def test_remote_except(config_conn, patch_open_connection, patch_conn_close):
async def test_remote_except(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
global test
test = TestType.RD_TEST_TIMEOUT
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT
await inverter.async_create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
inverter.close()
spy1.assert_called_once()
test = TestType.RD_TEST_EXCEPT
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
@pytest.mark.asyncio
async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close):
async def test_mqtt_publish(config_conn, patch_open_connection):
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
stream = inverter.local.stream
await inverter.async_publ_mqtt() # check call with invalid unique_id
stream._SolarmanV5__set_serial_no(snr= 123344)
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == False
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
stream = inverter.local.stream
await inverter.async_publ_mqtt() # check call with invalid unique_id
stream._SolarmanV5__set_serial_no(snr= 123344)
stream.new_data['env'] = True
stream.db.db['env'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['env'] == False
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == False
Infos.new_stat_data['proxy'] = True
await inverter.async_publ_mqtt()
assert Infos.new_stat_data['proxy'] == False
stream.new_data['env'] = True
stream.db.db['env'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['env'] == False
inverter.close()
spy1.assert_called_once()
Infos.new_stat_data['proxy'] = True
await inverter.async_publ_mqtt()
assert Infos.new_stat_data['proxy'] == False
@pytest.mark.asyncio
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err, patch_conn_close):
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err):
_ = config_conn
_ = patch_open_connection
_ = patch_mqtt_err
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
stream = inverter.local.stream
stream._SolarmanV5__set_serial_no(snr= 123344)
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
inverter.close()
spy1.assert_called_once()
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
stream = inverter.local.stream
stream._SolarmanV5__set_serial_no(snr= 123344)
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
@pytest.mark.asyncio
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except, patch_conn_close):
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except):
_ = config_conn
_ = patch_open_connection
_ = patch_mqtt_except
assert asyncio.get_running_loop()
spy1 = patch_conn_close
Inverter.class_init()
Proxy.class_init()
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
stream = inverter.local.stream
stream._SolarmanV5__set_serial_no(snr= 123344)
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
stream = inverter.local.stream
stream._SolarmanV5__set_serial_no(snr= 123344)
inverter.close()
spy1.assert_called_once()
stream.new_data['inverter'] = True
stream.db.db['inverter'] = {}
await inverter.async_publ_mqtt()
assert stream.new_data['inverter'] == True

View File

@@ -10,7 +10,7 @@ from app.src.config import Config
from app.src.infos import Infos
from app.src.mqtt import Mqtt
from app.src.messages import Message, State
from app.src.inverter import Inverter
from app.src.proxy import Proxy
from app.src.modbus_tcp import ModbusConn, ModbusTcp
@@ -71,55 +71,77 @@ def config_conn(test_hostname, test_port):
}
class TestType(Enum):
class FakeReader():
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_13_BYTES = 3
RD_TEST_SW_EXCEPT = 4
RD_TEST_OS_ERROR = 5
test = TestType.RD_TEST_0_BYTES
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
self.test = self.RD_TEST_0_BYTES
async def read(self, max_len: int):
print(f'fakeReader test: {self.test}')
await self.on_recv.wait()
if test == TestType.RD_TEST_0_BYTES:
if self.test == self.RD_TEST_0_BYTES:
return b''
elif test == TestType.RD_TEST_TIMEOUT:
elif self.test == self.RD_TEST_13_BYTES:
print('fakeReader return 13 bytes')
self.test = self.RD_TEST_0_BYTES
return b'test-data-req'
elif self.test == self.RD_TEST_TIMEOUT:
raise TimeoutError
elif self.test == self.RD_TEST_SW_EXCEPT:
self.test = self.RD_TEST_0_BYTES
self.unknown_var += 1
elif self.test == self.RD_TEST_OS_ERROR:
self.test = self.RD_TEST_0_BYTES
raise ConnectionRefusedError
def feed_eof(self):
return
class FakeWriter():
def __init__(self, conn='remote.intern'):
self.conn = conn
self.closing = False
def write(self, buf: bytes):
return
async def drain(self):
await asyncio.sleep(0)
def get_extra_info(self, sel: str):
if sel == 'peername':
return 'remote.intern'
return self.conn
elif sel == 'sockname':
return 'sock:1234'
assert False
def is_closing(self):
return False
return self.closing
def close(self):
return
self.closing = True
async def wait_closed(self):
return
await asyncio.sleep(0)
@pytest.fixture
def patch_open():
async def new_conn(conn):
await asyncio.sleep(0)
return FakeReader(), FakeWriter()
return FakeReader(), FakeWriter(conn)
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
raise TimeoutError
return new_conn(None)
return new_conn(f'{host}:{port}')
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@pytest.fixture
def patch_open_timeout():
def new_open(host: str, port: int):
raise TimeoutError
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@@ -153,7 +175,7 @@ async def test_modbus_conn(patch_open):
async with ModbusConn('test.local', 1234) as inverter:
stream = inverter.local.stream
assert stream.node_id == 'G3P'
assert stream.addr == ('test.local', 1234)
assert stream.addr == ('test.local:1234')
assert type(stream.ifc._reader) is FakeReader
assert type(stream.ifc._writer) is FakeWriter
assert Infos.stat['proxy']['Inverter_Cnt'] == 1
@@ -168,13 +190,11 @@ async def test_modbus_no_cnf():
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
@pytest.mark.asyncio
async def test_modbus_cnf1(config_conn, patch_open):
async def test_modbus_cnf1(config_conn, patch_open_timeout):
_ = config_conn
_ = patch_open
global test
_ = patch_open_timeout
assert asyncio.get_running_loop()
Inverter.class_init()
test = TestType.RD_TEST_TIMEOUT
Proxy.class_init()
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
loop = asyncio.get_event_loop()
@@ -192,10 +212,8 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
_ = config_conn
_ = patch_open
_ = patch_no_mqtt
global test
assert asyncio.get_running_loop()
Inverter.class_init()
test = TestType.RD_TEST_0_BYTES
Proxy.class_init()
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop())
@@ -218,10 +236,8 @@ async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
_ = config_conn
_ = patch_open
_ = patch_no_mqtt
global test
assert asyncio.get_running_loop()
Inverter.class_init()
test = TestType.RD_TEST_0_BYTES
Proxy.class_init()
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
@@ -251,10 +267,8 @@ async def test_mqtt_err(config_conn, patch_mqtt_err, patch_open):
_ = config_conn
_ = patch_open
_ = patch_mqtt_err
global test
assert asyncio.get_running_loop()
Inverter.class_init()
test = TestType.RD_TEST_0_BYTES
Proxy.class_init()
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
@@ -284,10 +298,8 @@ async def test_mqtt_except(config_conn, patch_mqtt_except, patch_open):
_ = config_conn
_ = patch_open
_ = patch_mqtt_except
global test
assert asyncio.get_running_loop()
Inverter.class_init()
test = TestType.RD_TEST_0_BYTES
Proxy.class_init()
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)

View File

@@ -6,7 +6,7 @@ import logging
from mock import patch, Mock
from app.src.singleton import Singleton
from app.src.inverter import Inverter
from app.src.proxy import Proxy
from app.src.mqtt import Mqtt
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config
@@ -63,13 +63,13 @@ def config_conn(test_hostname, test_port):
async def test_inverter_cb(config_conn):
_ = config_conn
with patch.object(Inverter, '_cb_mqtt_is_up', wraps=Inverter._cb_mqtt_is_up) as spy:
print('call Inverter.class_init')
Inverter.class_init()
assert 'homeassistant/' == Inverter.discovery_prfx
assert 'tsun/' == Inverter.entity_prfx
assert 'test_1/' == Inverter.proxy_node_id
await Inverter._cb_mqtt_is_up()
with patch.object(Proxy, '_cb_mqtt_is_up', wraps=Proxy._cb_mqtt_is_up) as spy:
print('call Proxy.class_init')
Proxy.class_init()
assert 'homeassistant/' == Proxy.discovery_prfx
assert 'tsun/' == Proxy.entity_prfx
assert 'test_1/' == Proxy.proxy_node_id
await Proxy._cb_mqtt_is_up()
spy.assert_called_once()
@pytest.mark.asyncio
@@ -77,8 +77,8 @@ async def test_mqtt_is_up(config_conn):
_ = config_conn
with patch.object(Mqtt, 'publish') as spy:
Inverter.class_init()
await Inverter._cb_mqtt_is_up()
Proxy.class_init()
await Proxy._cb_mqtt_is_up()
spy.assert_called()
@pytest.mark.asyncio
@@ -86,6 +86,6 @@ async def test_mqtt_proxy_statt_invalid(config_conn):
_ = config_conn
with patch.object(Mqtt, 'publish') as spy:
Inverter.class_init()
await Inverter._async_publ_mqtt_proxy_stat('InValId_kEy')
Proxy.class_init()
await Proxy._async_publ_mqtt_proxy_stat('InValId_kEy')
spy.assert_not_called()

View File

@@ -32,13 +32,17 @@ class Mqtt():
self.data = data
class FakeIfc(AsyncIfcImpl):
def __init__(self):
super().__init__()
self.remote = StreamPtr(None)
class MemoryStream(SolarmanV5):
def __init__(self, msg, chunks = (0,), server_side: bool = True):
_ifc = AsyncIfcImpl()
super().__init__(('test.local', 1234), server_side, client_mode=False, ifc=_ifc)
_ifc = FakeIfc()
super().__init__(('test.local', 1234), _ifc, server_side, client_mode=False)
if server_side:
self.mb.timeout = 0.4 # overwrite for faster testing
self.remote = StreamPtr(None)
self.mb_first_timeout = 0.5
self.mb_timeout = 0.5
self.sent_pdu = b''
@@ -101,8 +105,8 @@ class MemoryStream(SolarmanV5):
def createClientStream(self, msg, chunks = (0,)):
c = MemoryStream(msg, chunks, False)
self.remote.stream = c
c. remote.stream = self
self.ifc.remote.stream = c
c.ifc.remote.stream = self
return c
def _SolarmanV5__flush_recv_msg(self) -> None:
@@ -678,6 +682,7 @@ def config_tsun_inv1():
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}}
def test_read_message(device_ind_msg):
Config.act_config = {'solarman':{'enabled': True}}
m = MemoryStream(device_ind_msg, (0,))
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
@@ -1236,9 +1241,9 @@ def test_build_logger_modell(config_tsun_allow_all, device_ind_msg):
def test_msg_iterator():
Message._registry.clear()
m1 = SolarmanV5(('test1.local', 1234), server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m2 = SolarmanV5(('test2.local', 1234), server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m3 = SolarmanV5(('test3.local', 1234), server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m1 = SolarmanV5(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
m2 = SolarmanV5(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
m3 = SolarmanV5(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
m3.close()
del m3
test1 = 0
@@ -1256,7 +1261,7 @@ def test_msg_iterator():
assert test2 == 1
def test_proxy_counter():
m = SolarmanV5(('test.local', 1234), server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m = SolarmanV5(('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
assert m.new_data == {}
m.db.stat['proxy']['Unknown_Msg'] = 0
Infos.new_stat_data['proxy'] = False

View File

@@ -16,14 +16,17 @@ Infos.static_init()
tracer = logging.getLogger('tracer')
class FakeIfc(AsyncIfcImpl):
def __init__(self):
super().__init__()
self.remote = StreamPtr(None)
class MemoryStream(Talent):
def __init__(self, msg, chunks = (0,), server_side: bool = True):
self.ifc = AsyncIfcImpl()
super().__init__(('test.local', 1234), server_side, self.ifc)
self.ifc = FakeIfc()
super().__init__(('test.local', 1234), self.ifc, server_side)
if server_side:
self.mb.timeout = 0.4 # overwrite for faster testing
self.remote = StreamPtr(None)
self.mb_first_timeout = 0.5
self.mb_timeout = 0.5
self.sent_pdu = b''
@@ -37,7 +40,6 @@ class MemoryStream(Talent):
self.addr = 'Test: SrvSide'
self.send_msg_ofs = 0
self.msg_recvd = []
self.remote.stream = None
def write_cb(self):
self.sent_pdu = self.ifc.tx_fifo.get()
@@ -73,8 +75,8 @@ class MemoryStream(Talent):
def createClientStream(self, msg, chunks = (0,)):
c = MemoryStream(msg, chunks, False)
self.remote.stream = c
c. remote.stream = self
self.ifc.remote.stream = c
c.ifc.remote.stream = self
return c
def _Talent__flush_recv_msg(self) -> None:
@@ -1059,7 +1061,7 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
m = MemoryStream(msg_time_rsp, (0,), False)
s = MemoryStream(b'', (0,), True)
assert s.ts_offset==0
m.remote.stream = s
m.ifc.remote.stream = s
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
@@ -1075,7 +1077,7 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.remote.stream = None
m.ifc.remote.stream = None
s.close()
m.close()
@@ -1639,9 +1641,9 @@ def test_ctrl_byte():
def test_msg_iterator():
m1 = Talent(('test1.local', 1234), server_side=True, ifc=AsyncIfcImpl())
m2 = Talent(('test2.local', 1234), server_side=True, ifc=AsyncIfcImpl())
m3 = Talent(('test3.local', 1234), server_side=True, ifc=AsyncIfcImpl())
m1 = Talent(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
m2 = Talent(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
m3 = Talent(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
m3.close()
del m3
test1 = 0