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-09-27 19:28:39 +02:00
25 changed files with 1624 additions and 1079 deletions

View File

@@ -4,408 +4,579 @@
<!-- Generated by graphviz version 2.40.1 (20161225.0304) <!-- Generated by graphviz version 2.40.1 (20161225.0304)
--> -->
<!-- Title: G Pages: 1 --> <!-- Title: G Pages: 1 -->
<svg width="720pt" height="1360pt" <svg width="846pt" height="1784pt"
viewBox="0.00 0.00 719.50 1360.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 846.00 1784.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 1356)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1780)">
<title>G</title> <title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1356 715.5,-1356 715.5,4 -4,4"/> <polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1780 842,-1780 842,4 -4,4"/>
<!-- A0 --> <!-- A0 -->
<g id="node1" class="node"> <g id="node1" class="node">
<title>A0</title> <title>A0</title>
<polygon fill="#fff8dc" stroke="#000000" points="153.6964,-1250 45.3036,-1250 45.3036,-1214 159.6964,-1214 159.6964,-1244 153.6964,-1250"/> <polygon fill="#fff8dc" stroke="#000000" points="495.6964,-1752 387.3036,-1752 387.3036,-1716 501.6964,-1716 501.6964,-1746 495.6964,-1752"/>
<polyline fill="none" stroke="#000000" points="153.6964,-1250 153.6964,-1244 "/> <polyline fill="none" stroke="#000000" points="495.6964,-1752 495.6964,-1746 "/>
<polyline fill="none" stroke="#000000" points="159.6964,-1244 153.6964,-1244 "/> <polyline fill="none" stroke="#000000" points="501.6964,-1746 495.6964,-1746 "/>
<text text-anchor="middle" x="102.5" y="-1235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text> <text text-anchor="middle" x="444.5" y="-1737" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
<text text-anchor="middle" x="102.5" y="-1223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text> <text text-anchor="middle" x="444.5" y="-1725" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
</g> </g>
<!-- A1 --> <!-- A1 -->
<g id="node2" class="node"> <g id="node2" class="node">
<title>A1</title> <title>A1</title>
<polygon fill="none" stroke="#000000" points="685.1817,-942 615.8183,-942 615.8183,-906 685.1817,-906 685.1817,-942"/> <polygon fill="none" stroke="#000000" points="118.5,-572 118.5,-616 240.5,-616 240.5,-572 118.5,-572"/>
<text text-anchor="middle" x="650.5" y="-921" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Singleton</text> <text text-anchor="start" x="169.777" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
<text text-anchor="start" x="146.9815" y="-585" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;Singleton&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="118.5,-516 118.5,-572 240.5,-572 240.5,-516 118.5,-516"/>
<text text-anchor="start" x="136.9875" y="-553" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;ha_restarts</text>
<text text-anchor="start" x="144.7665" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__client</text>
<text text-anchor="start" x="128.3735" y="-529" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__cb_MqttIsUp</text>
<polygon fill="none" stroke="#000000" points="118.5,-472 118.5,-516 240.5,-516 240.5,-472 118.5,-472"/>
<text text-anchor="start" x="141.436" y="-497" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish()</text>
<text text-anchor="start" x="145.6045" y="-485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;close()</text>
</g> </g>
<!-- A2 --> <!-- A2 -->
<g id="node3" class="node"> <g id="node3" class="node">
<title>A2</title> <title>A2</title>
<polygon fill="none" stroke="#000000" points="589.5,-644 589.5,-676 711.5,-676 711.5,-644 589.5,-644"/> <polygon fill="none" stroke="#000000" points="125.5,-342 125.5,-374 233.5,-374 233.5,-342 125.5,-342"/>
<text text-anchor="start" x="640.777" y="-657" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text> <text text-anchor="start" x="162.5535" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Inverter</text>
<polygon fill="none" stroke="#000000" points="589.5,-588 589.5,-644 711.5,-644 711.5,-588 589.5,-588"/> <polygon fill="none" stroke="#000000" points="125.5,-250 125.5,-342 233.5,-342 233.5,-250 125.5,-250"/>
<text text-anchor="start" x="607.9875" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;ha_restarts</text> <text text-anchor="start" x="155.604" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.db_stat</text>
<text text-anchor="start" x="615.7665" y="-613" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__client</text> <text text-anchor="start" x="148.9405" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.entity_prfx</text>
<text text-anchor="start" x="599.3735" y="-601" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__cb_MqttIsUp</text> <text text-anchor="start" x="139.7755" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.discovery_prfx</text>
<polygon fill="none" stroke="#000000" points="589.5,-544 589.5,-588 711.5,-588 711.5,-544 589.5,-544"/> <text text-anchor="start" x="139.2115" y="-287" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_node_id</text>
<text text-anchor="start" x="612.436" y="-569" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish()</text> <text text-anchor="start" x="135.3225" y="-275" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_unique_id</text>
<text text-anchor="start" x="616.6045" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;close()</text> <text text-anchor="start" x="151.1655" y="-263" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.mqtt:Mqtt</text>
<polygon fill="none" stroke="#000000" points="125.5,-230 125.5,-250 233.5,-250 233.5,-230 125.5,-230"/>
</g> </g>
<!-- A1&#45;&gt;A2 --> <!-- A1&#45;&gt;A2 -->
<g id="edge1" class="edge"> <g id="edge3" class="edge">
<title>A1&#45;&gt;A2</title> <title>A1&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M650.5,-895.5395C650.5,-846.311 650.5,-744.0351 650.5,-676.2069"/> <path fill="none" stroke="#000000" d="M179.5,-461.4521C179.5,-437.2513 179.5,-410.7602 179.5,-386.443"/>
<polygon fill="none" stroke="#000000" points="647.0001,-895.7608 650.5,-905.7608 654.0001,-895.7608 647.0001,-895.7608"/> <polygon fill="#000000" stroke="#000000" points="179.5,-471.7198 175.0001,-461.7198 179.5,-466.7198 179.5001,-461.7198 179.5001,-461.7198 179.5001,-461.7198 179.5,-466.7198 184.0001,-461.7199 179.5,-471.7198 179.5,-471.7198"/>
</g> <polygon fill="#000000" stroke="#000000" points="179.5001,-386.3256 175.5,-380.3256 179.5,-374.3256 183.5,-380.3256 179.5001,-386.3256"/>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="596.5,-348 596.5,-380 704.5,-380 704.5,-348 596.5,-348"/>
<text text-anchor="start" x="633.5535" y="-361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Inverter</text>
<polygon fill="none" stroke="#000000" points="596.5,-256 596.5,-348 704.5,-348 704.5,-256 596.5,-256"/>
<text text-anchor="start" x="626.604" y="-329" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.db_stat</text>
<text text-anchor="start" x="619.9405" y="-317" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.entity_prfx</text>
<text text-anchor="start" x="610.7755" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.discovery_prfx</text>
<text text-anchor="start" x="610.2115" y="-293" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_node_id</text>
<text text-anchor="start" x="606.3225" y="-281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.proxy_unique_id</text>
<text text-anchor="start" x="622.1655" y="-269" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.mqtt:Mqtt</text>
<polygon fill="none" stroke="#000000" points="596.5,-236 596.5,-256 704.5,-256 704.5,-236 596.5,-236"/>
</g>
<!-- A2&#45;&gt;A11 -->
<g id="edge13" class="edge">
<title>A2&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M650.5,-543.7248C650.5,-495.3688 650.5,-429.8734 650.5,-380.1918"/>
</g> </g>
<!-- A3 --> <!-- A3 -->
<g id="node4" class="node"> <g id="node4" class="node">
<title>A3</title> <title>A3</title>
<polygon fill="none" stroke="#000000" points="318.5,-402 318.5,-434 393.5,-434 393.5,-402 318.5,-402"/> <polygon fill="none" stroke="#000000" points="284.5,-100 284.5,-132 406.5,-132 406.5,-100 284.5,-100"/>
<text text-anchor="start" x="338.2175" y="-415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text> <text text-anchor="start" x="321.8845" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
<polygon fill="none" stroke="#000000" points="318.5,-250 318.5,-402 393.5,-402 393.5,-250 318.5,-250"/> <polygon fill="none" stroke="#000000" points="284.5,-68 284.5,-100 406.5,-100 406.5,-68 284.5,-68"/>
<text text-anchor="start" x="347.6615" y="-383" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text> <text text-anchor="start" x="314.9355" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<text text-anchor="start" x="328.49" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text> <polygon fill="none" stroke="#000000" points="284.5,0 284.5,-68 406.5,-68 406.5,0 284.5,0"/>
<text text-anchor="start" x="329.605" y="-347" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text> <text text-anchor="start" x="294.1035" y="-49" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote()</text>
<text text-anchor="start" x="339.6085" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text> <text text-anchor="start" x="304.382" y="-37" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_publ_mqtt()</text>
<text text-anchor="start" x="329.8895" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text> <text text-anchor="start" x="330.5025" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="337.942" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text> </g>
<text text-anchor="start" x="349.8915" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text> <!-- A2&#45;&gt;A3 -->
<text text-anchor="start" x="336.5535" y="-287" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text> <g id="edge1" class="edge">
<text text-anchor="start" x="334.879" y="-275" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text> <title>A2&#45;&gt;A3</title>
<text text-anchor="start" x="349.3365" y="-263" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text> <path fill="none" stroke="#000000" d="M218.7146,-220.7402C226.0884,-207.4676 234.13,-194.1098 242.5,-182 254.9036,-164.0543 269.7696,-145.8518 284.2981,-129.3516"/>
<polygon fill="none" stroke="#000000" points="318.5,-182 318.5,-250 393.5,-250 393.5,-182 318.5,-182"/> <polygon fill="none" stroke="#000000" points="215.4351,-219.4436 213.7205,-229.8988 221.5808,-222.7948 215.4351,-219.4436"/>
<text text-anchor="start" x="329.89" y="-231" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
<text text-anchor="start" x="333.224" y="-219" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
<text text-anchor="start" x="330.724" y="-207" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
<text text-anchor="start" x="341.0025" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g> </g>
<!-- A4 --> <!-- A4 -->
<g id="node5" class="node"> <g id="node5" class="node">
<title>A4</title> <title>A4</title>
<polygon fill="none" stroke="#000000" points="308.5,-1242 308.5,-1274 379.5,-1274 379.5,-1242 308.5,-1242"/> <polygon fill="none" stroke="#000000" points="120.5,-94 120.5,-126 239.5,-126 239.5,-94 120.5,-94"/>
<text text-anchor="start" x="318.445" y="-1255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">IterRegistry</text> <text text-anchor="start" x="153.05" y="-107" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
<polygon fill="none" stroke="#000000" points="308.5,-1222 308.5,-1242 379.5,-1242 379.5,-1222 308.5,-1222"/> <polygon fill="none" stroke="#000000" points="120.5,-62 120.5,-94 239.5,-94 239.5,-62 120.5,-62"/>
<polygon fill="none" stroke="#000000" points="308.5,-1190 308.5,-1222 379.5,-1222 379.5,-1190 308.5,-1190"/> <text text-anchor="start" x="149.4355" y="-75" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<text text-anchor="start" x="325.939" y="-1203" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__</text> <polygon fill="none" stroke="#000000" points="120.5,-6 120.5,-62 239.5,-62 239.5,-6 120.5,-6"/>
<text text-anchor="start" x="130.268" y="-43" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote(</text>
<text text-anchor="start" x="137.2175" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">)async_publ_mqtt()</text>
<text text-anchor="start" x="165.0025" y="-19" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A2&#45;&gt;A4 -->
<g id="edge2" class="edge">
<title>A2&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M179.5,-219.6269C179.5,-188.8617 179.5,-154.5312 179.5,-126.1395"/>
<polygon fill="none" stroke="#000000" points="176.0001,-219.9098 179.5,-229.9099 183.0001,-219.9099 176.0001,-219.9098"/>
</g> </g>
<!-- A5 --> <!-- A5 -->
<g id="node6" class="node"> <g id="node6" class="node">
<title>A5</title> <title>A5</title>
<polygon fill="none" stroke="#000000" points="276.5,-1030 276.5,-1062 410.5,-1062 410.5,-1030 276.5,-1030"/> <polygon fill="none" stroke="#000000" points="520.5,-1744 520.5,-1776 591.5,-1776 591.5,-1744 520.5,-1744"/>
<text text-anchor="start" x="323.2175" y="-1043" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text> <text text-anchor="start" x="530.445" y="-1757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">IterRegistry</text>
<polygon fill="none" stroke="#000000" points="276.5,-854 276.5,-1030 410.5,-1030 410.5,-854 276.5,-854"/> <polygon fill="none" stroke="#000000" points="520.5,-1724 520.5,-1744 591.5,-1744 591.5,-1724 520.5,-1724"/>
<text text-anchor="start" x="306.8265" y="-1011" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text> <polygon fill="none" stroke="#000000" points="520.5,-1692 520.5,-1724 591.5,-1724 591.5,-1692 520.5,-1692"/>
<text text-anchor="start" x="304.043" y="-999" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text> <text text-anchor="start" x="537.939" y="-1705" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__</text>
<text text-anchor="start" x="296.814" y="-987" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len:unsigned</text>
<text text-anchor="start" x="302.648" y="-975" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len:unsigned</text>
<text text-anchor="start" x="321.8245" y="-963" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
<text text-anchor="start" x="325.7135" y="-951" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="322.6585" y="-939" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area</text>
<text text-anchor="start" x="293.489" y="-927" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_recv_buffer:bytearray</text>
<text text-anchor="start" x="292.0945" y="-915" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_buffer:bytearray</text>
<text text-anchor="start" x="286.2665" y="-903" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_forward_buffer:bytearray</text>
<text text-anchor="start" x="325.7135" y="-891" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:Infos</text>
<text text-anchor="start" x="314.326" y="-879" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:list</text>
<text text-anchor="start" x="332.662" y="-867" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state</text>
<polygon fill="none" stroke="#000000" points="276.5,-786 276.5,-854 410.5,-854 410.5,-786 276.5,-786"/>
<text text-anchor="start" x="293.2095" y="-835" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_read():void&lt;abstract&gt;</text>
<text text-anchor="start" x="317.9445" y="-823" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close():void</text>
<text text-anchor="start" x="303.7725" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter():void</text>
<text text-anchor="start" x="302.1025" y="-799" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter():void</text>
</g>
<!-- A4&#45;&gt;A5 -->
<g id="edge2" class="edge">
<title>A4&#45;&gt;A5</title>
<path fill="none" stroke="#000000" d="M343.5,-1179.6793C343.5,-1147.2188 343.5,-1103.8616 343.5,-1062.0836"/>
<polygon fill="none" stroke="#000000" points="340.0001,-1179.8197 343.5,-1189.8197 347.0001,-1179.8198 340.0001,-1179.8197"/>
</g> </g>
<!-- A6 --> <!-- A6 -->
<g id="node7" class="node"> <g id="node7" class="node">
<title>A6</title> <title>A6</title>
<polygon fill="none" stroke="#000000" points="415.5,-704 415.5,-736 529.5,-736 529.5,-704 415.5,-704"/> <polygon fill="none" stroke="#000000" points="488.5,-1610 488.5,-1642 622.5,-1642 622.5,-1610 488.5,-1610"/>
<text text-anchor="start" x="458.608" y="-717" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text> <text text-anchor="start" x="535.2175" y="-1623" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
<polygon fill="none" stroke="#000000" points="415.5,-600 415.5,-704 529.5,-704 529.5,-600 415.5,-600"/> <polygon fill="none" stroke="#000000" points="488.5,-1434 488.5,-1610 622.5,-1610 622.5,-1434 488.5,-1434"/>
<text text-anchor="start" x="425.263" y="-685" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text> <text text-anchor="start" x="518.8265" y="-1591" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
<text text-anchor="start" x="460.2775" y="-673" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text> <text text-anchor="start" x="516.043" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
<text text-anchor="start" x="441.1" y="-661" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text> <text text-anchor="start" x="508.814" y="-1567" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len:unsigned</text>
<text text-anchor="start" x="444.44" y="-649" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text> <text text-anchor="start" x="514.648" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len:unsigned</text>
<text text-anchor="start" x="448.0445" y="-637" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text> <text text-anchor="start" x="533.8245" y="-1543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
<text text-anchor="start" x="446.384" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text> <text text-anchor="start" x="537.7135" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="458.612" y="-613" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text> <text text-anchor="start" x="534.6585" y="-1519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area</text>
<polygon fill="none" stroke="#000000" points="415.5,-484 415.5,-600 529.5,-600 529.5,-484 415.5,-484"/> <text text-anchor="start" x="505.489" y="-1507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_recv_buffer:bytearray</text>
<text text-anchor="start" x="429.9925" y="-581" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text> <text text-anchor="start" x="504.0945" y="-1495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_buffer:bytearray</text>
<text text-anchor="start" x="431.9325" y="-569" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text> <text text-anchor="start" x="498.2665" y="-1483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_forward_buffer:bytearray</text>
<text text-anchor="start" x="437.7765" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text> <text text-anchor="start" x="537.7135" y="-1471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:Infos</text>
<text text-anchor="start" x="425.8285" y="-545" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text> <text text-anchor="start" x="526.326" y="-1459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:list</text>
<text text-anchor="start" x="427.7735" y="-533" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text> <text text-anchor="start" x="544.662" y="-1447" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state</text>
<text text-anchor="start" x="436.9405" y="-521" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text> <polygon fill="none" stroke="#000000" points="488.5,-1366 488.5,-1434 622.5,-1434 622.5,-1366 488.5,-1366"/>
<text text-anchor="start" x="457.5025" y="-497" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text> <text text-anchor="start" x="505.2095" y="-1415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_read():void&lt;abstract&gt;</text>
<text text-anchor="start" x="529.9445" y="-1403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close():void</text>
<text text-anchor="start" x="515.7725" y="-1391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter():void</text>
<text text-anchor="start" x="514.1025" y="-1379" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter():void</text>
</g> </g>
<!-- A5&#45;&gt;A6 --> <!-- A5&#45;&gt;A6 -->
<g id="edge3" class="edge"> <g id="edge4" class="edge">
<title>A5&#45;&gt;A6</title> <title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" d="M404.0814,-776.5383C409.5999,-763.1056 415.1569,-749.5794 420.5898,-736.355"/> <path fill="none" stroke="#000000" d="M555.5,-1681.5582C555.5,-1669.4749 555.5,-1656.067 555.5,-1642.189"/>
<polygon fill="none" stroke="#000000" points="400.8317,-775.2382 400.269,-785.8181 407.3066,-777.8983 400.8317,-775.2382"/> <polygon fill="none" stroke="#000000" points="552.0001,-1681.8144 555.5,-1691.8145 559.0001,-1681.8145 552.0001,-1681.8144"/>
</g>
<!-- A21 -->
<g id="node22" class="node">
<title>A21</title>
<polygon fill="none" stroke="#000000" points="498.5,-1284 498.5,-1316 612.5,-1316 612.5,-1284 498.5,-1284"/>
<text text-anchor="start" x="541.608" y="-1297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text>
<polygon fill="none" stroke="#000000" points="498.5,-1180 498.5,-1284 612.5,-1284 612.5,-1180 498.5,-1180"/>
<text text-anchor="start" x="508.263" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text>
<text text-anchor="start" x="543.2775" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text>
<text text-anchor="start" x="524.1" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text>
<text text-anchor="start" x="527.44" y="-1229" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text>
<text text-anchor="start" x="531.0445" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text>
<text text-anchor="start" x="529.384" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="541.612" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="498.5,-1064 498.5,-1180 612.5,-1180 612.5,-1064 498.5,-1064"/>
<text text-anchor="start" x="512.9925" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text>
<text text-anchor="start" x="514.9325" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text>
<text text-anchor="start" x="520.7765" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text>
<text text-anchor="start" x="508.8285" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text>
<text text-anchor="start" x="510.7735" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
<text text-anchor="start" x="519.9405" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="540.5025" y="-1077" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A6&#45;&gt;A21 -->
<g id="edge29" class="edge">
<title>A6&#45;&gt;A21</title>
<path fill="none" stroke="#000000" d="M555.5,-1355.7632C555.5,-1342.5854 555.5,-1329.3251 555.5,-1316.355"/>
<polygon fill="none" stroke="#000000" points="552.0001,-1355.8181 555.5,-1365.8181 559.0001,-1355.8181 552.0001,-1355.8181"/>
</g>
<!-- A22 -->
<g id="node23" class="node">
<title>A22</title>
<polygon fill="none" stroke="#000000" points="387.5,-1248 387.5,-1280 478.5,-1280 478.5,-1248 387.5,-1248"/>
<text text-anchor="start" x="405.495" y="-1261" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text>
<polygon fill="none" stroke="#000000" points="387.5,-1156 387.5,-1248 478.5,-1248 478.5,-1156 387.5,-1156"/>
<text text-anchor="start" x="417.998" y="-1229" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
<text text-anchor="start" x="421.0575" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text>
<text text-anchor="start" x="426.056" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text>
<text text-anchor="start" x="405.21" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text>
<text text-anchor="start" x="406.884" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="419.112" y="-1169" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="387.5,-1100 387.5,-1156 478.5,-1156 478.5,-1100 387.5,-1100"/>
<text text-anchor="start" x="397.4405" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="418.0025" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A6&#45;&gt;A22 -->
<g id="edge30" class="edge">
<title>A6&#45;&gt;A22</title>
<path fill="none" stroke="#000000" d="M497.6392,-1356.2903C487.487,-1330.3733 477.2021,-1304.1174 467.8962,-1280.3611"/>
<polygon fill="none" stroke="#000000" points="494.4651,-1357.7836 501.3714,-1365.8181 500.9829,-1355.2304 494.4651,-1357.7836"/>
</g> </g>
<!-- A7 --> <!-- A7 -->
<g id="node8" class="node"> <g id="node8" class="node">
<title>A7</title> <title>A7</title>
<polygon fill="none" stroke="#000000" points="172.5,-668 172.5,-700 263.5,-700 263.5,-668 172.5,-668"/> <polygon fill="none" stroke="#000000" points="552.5,-982 552.5,-1014 669.5,-1014 669.5,-982 552.5,-982"/>
<text text-anchor="start" x="190.495" y="-681" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text> <text text-anchor="start" x="580.429" y="-995" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AsyncIfc&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="172.5,-576 172.5,-668 263.5,-668 263.5,-576 172.5,-576"/> <polygon fill="none" stroke="#000000" points="552.5,-962 552.5,-982 669.5,-982 669.5,-962 552.5,-962"/>
<text text-anchor="start" x="202.998" y="-649" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text> <polygon fill="none" stroke="#000000" points="552.5,-666 552.5,-962 669.5,-962 669.5,-666 552.5,-666"/>
<text text-anchor="start" x="206.0575" y="-637" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text> <text text-anchor="start" x="580.436" y="-943" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_node_id()</text>
<text text-anchor="start" x="211.056" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text> <text text-anchor="start" x="578.766" y="-931" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_conn_no()</text>
<text text-anchor="start" x="190.21" y="-613" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text> <text text-anchor="start" x="592.6635" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_add()</text>
<text text-anchor="start" x="191.884" y="-601" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text> <text text-anchor="start" x="590.444" y="-895" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_flush()</text>
<text text-anchor="start" x="204.112" y="-589" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text> <text text-anchor="start" x="594.0535" y="-883" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_get()</text>
<polygon fill="none" stroke="#000000" points="172.5,-520 172.5,-576 263.5,-576 263.5,-520 172.5,-520"/> <text text-anchor="start" x="590.1635" y="-871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_peek()</text>
<text text-anchor="start" x="182.4405" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text> <text text-anchor="start" x="594.3335" y="-859" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_log()</text>
<text text-anchor="start" x="203.0025" y="-533" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text> <text text-anchor="start" x="590.169" y="-847" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_clear()</text>
</g> <text text-anchor="start" x="594.3335" y="-835" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_len()</text>
<!-- A5&#45;&gt;A7 --> <text text-anchor="start" x="588.7745" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_add()</text>
<g id="edge4" class="edge"> <text text-anchor="start" x="586.555" y="-799" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_flush()</text>
<title>A5&#45;&gt;A7</title> <text text-anchor="start" x="590.4445" y="-787" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_log()</text>
<path fill="none" stroke="#000000" d="M284.228,-776.2903C273.8281,-750.3733 263.2923,-724.1174 253.7595,-700.3611"/> <text text-anchor="start" x="586.28" y="-775" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_clear()</text>
<polygon fill="none" stroke="#000000" points="281.0788,-777.8409 288.0512,-785.8181 287.5753,-775.2339 281.0788,-777.8409"/> <text text-anchor="start" x="593.7785" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_get()</text>
</g> <text text-anchor="start" x="589.8885" y="-751" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_peek()</text>
<!-- A6&#45;&gt;A3 --> <text text-anchor="start" x="594.0585" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_log()</text>
<g id="edge6" class="edge"> <text text-anchor="start" x="589.894" y="-727" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_clear()</text>
<title>A6&#45;&gt;A3</title> <text text-anchor="start" x="594.0585" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_len()</text>
<path fill="none" stroke="#000000" d="M420.9917,-483.781C414.3472,-467.074 407.7026,-450.1475 401.5,-434 399.8828,-429.7898 398.247,-425.4956 396.6047,-421.154"/> <text text-anchor="start" x="585.999" y="-703" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_set_cb()</text>
<polygon fill="#000000" stroke="#000000" points="393.0037,-411.5882 400.7383,-419.3616 394.7652,-416.2676 396.5268,-420.947 396.5268,-420.947 396.5268,-420.947 394.7652,-416.2676 392.3154,-422.5325 393.0037,-411.5882 393.0037,-411.5882"/> <text text-anchor="start" x="562.3795" y="-679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_set_timeout_cb()</text>
<text text-anchor="middle" x="407.3001" y="-422.5743" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="406.4454" y="-467.0549" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g> </g>
<!-- A8 --> <!-- A8 -->
<g id="node9" class="node"> <g id="node9" class="node">
<title>A8</title> <title>A8</title>
<polygon fill="none" stroke="#000000" points="410.5,-330 410.5,-362 560.5,-362 560.5,-330 410.5,-330"/> <polygon fill="none" stroke="#000000" points="575.5,-574 575.5,-606 668.5,-606 668.5,-574 575.5,-574"/>
<text text-anchor="start" x="453.5455" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3</text> <text text-anchor="start" x="593.664" y="-587" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncIfcImpl</text>
<polygon fill="none" stroke="#000000" points="410.5,-298 410.5,-330 560.5,-330 560.5,-298 410.5,-298"/> <polygon fill="none" stroke="#000000" points="575.5,-482 575.5,-574 668.5,-574 668.5,-482 575.5,-482"/>
<text text-anchor="start" x="420.487" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote_stream:ConnectionG3</text> <text text-anchor="start" x="585.048" y="-555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_fifo:ByteFifo</text>
<polygon fill="none" stroke="#000000" points="410.5,-254 410.5,-298 560.5,-298 560.5,-254 410.5,-254"/> <text text-anchor="start" x="588.937" y="-543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_fifo:ByteFifo</text>
<text text-anchor="start" x="466.054" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text> <text text-anchor="start" x="588.662" y="-531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_fifo:ByteFifo</text>
<text text-anchor="start" x="470.5025" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text> <text text-anchor="start" x="588.096" y="-519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no:Count</text>
<text text-anchor="start" x="604.2135" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="597.5495" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout_cb</text>
</g> </g>
<!-- A6&#45;&gt;A8 --> <!-- A7&#45;&gt;A8 -->
<g id="edge5" class="edge"> <g id="edge5" class="edge">
<title>A6&#45;&gt;A8</title> <title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M478.3685,-473.6691C480.0687,-434.1731 481.827,-393.3258 483.1723,-362.0732"/> <path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M617.3637,-655.3047C618.0135,-637.8196 618.6361,-621.0638 619.1922,-606.0999"/>
<polygon fill="none" stroke="#000000" points="474.8712,-473.5333 477.9378,-483.6747 481.8648,-473.8345 474.8712,-473.5333"/> <polygon fill="none" stroke="#000000" points="613.8521,-655.5528 616.9783,-665.6759 620.8473,-655.8128 613.8521,-655.5528"/>
</g>
<!-- A7&#45;&gt;A3 -->
<g id="edge8" class="edge">
<title>A7&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M254.1161,-519.7083C259.8714,-507.5039 266.0613,-495.3029 272.5,-484 286.0537,-460.2067 295.6001,-458.1541 308.5,-434 310.2529,-430.7178 311.9697,-427.3559 313.6482,-423.937"/>
<polygon fill="#000000" stroke="#000000" points="317.9998,-414.7692 317.7769,-425.7328 315.8557,-419.2862 313.7116,-423.8032 313.7116,-423.8032 313.7116,-423.8032 315.8557,-419.2862 309.6463,-421.8735 317.9998,-414.7692 317.9998,-414.7692"/>
<text text-anchor="middle" x="317.8629" y="-431.7687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="254.262" y="-496.7088" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g> </g>
<!-- A9 --> <!-- A9 -->
<g id="node10" class="node"> <g id="node10" class="node">
<title>A9</title> <title>A9</title>
<polygon fill="none" stroke="#000000" points="125.5,-330 125.5,-362 281.5,-362 281.5,-330 125.5,-330"/> <polygon fill="none" stroke="#000000" points="635.5,-390 635.5,-422 737.5,-422 737.5,-390 635.5,-390"/>
<text text-anchor="start" x="168.211" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3P</text> <text text-anchor="start" x="656.774" y="-403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
<polygon fill="none" stroke="#000000" points="125.5,-298 125.5,-330 281.5,-330 281.5,-298 125.5,-298"/> <polygon fill="none" stroke="#000000" points="635.5,-310 635.5,-390 737.5,-390 737.5,-310 635.5,-310"/>
<text text-anchor="start" x="135.1525" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote_stream:ConnectionG3P</text> <text text-anchor="start" x="672.053" y="-371" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">reader</text>
<polygon fill="none" stroke="#000000" points="125.5,-254 125.5,-298 281.5,-298 281.5,-254 125.5,-254"/> <text text-anchor="start" x="674.283" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text>
<text text-anchor="start" x="184.054" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text> <text text-anchor="start" x="676.497" y="-347" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="188.5025" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text> <text text-anchor="start" x="672.053" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text>
<text text-anchor="start" x="672.608" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</text>
<polygon fill="none" stroke="#000000" points="635.5,-182 635.5,-310 737.5,-310 737.5,-182 635.5,-182"/>
<text text-anchor="start" x="658.154" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;loop</text>
<text text-anchor="start" x="674.282" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">disc()</text>
<text text-anchor="start" x="671.5025" y="-255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="667.054" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="651.7705" y="-219" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_read()</text>
<text text-anchor="start" x="651.221" y="-207" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_write()</text>
<text text-anchor="start" x="645.107" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_forward()</text>
</g> </g>
<!-- A7&#45;&gt;A9 --> <!-- A8&#45;&gt;A9 -->
<g id="edge7" class="edge"> <g id="edge6" class="edge">
<title>A7&#45;&gt;A9</title> <title>A8&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M212.8528,-509.7531C210.5717,-460.5471 207.9134,-403.2043 206.0152,-362.2565"/> <path fill="none" stroke="#000000" d="M640.7492,-472.3339C644.9969,-456.5191 649.6091,-439.3476 654.2155,-422.1976"/>
<polygon fill="none" stroke="#000000" points="209.3591,-509.972 213.3185,-519.7991 216.3516,-509.6477 209.3591,-509.972"/> <polygon fill="#000000" stroke="#000000" points="638.1547,-481.9932 636.4028,-471.1682 639.4518,-477.1644 640.7488,-472.3355 640.7488,-472.3355 640.7488,-472.3355 639.4518,-477.1644 645.0948,-473.5029 638.1547,-481.9932 638.1547,-481.9932"/>
</g>
<!-- A8&#45;&gt;A8 -->
<g id="edge15" class="edge">
<title>A8&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M560.6684,-348.7195C571.3394,-342.1337 578.5,-328.5605 578.5,-308 578.5,-292.9008 574.6382,-281.57 568.3604,-274.0076"/>
<polygon fill="#000000" stroke="#000000" points="560.6684,-267.2805 571.1583,-270.4763 564.4321,-270.5721 568.1958,-273.8637 568.1958,-273.8637 568.1958,-273.8637 564.4321,-270.5721 565.2334,-277.251 560.6684,-267.2805 560.6684,-267.2805"/>
<text text-anchor="middle" x="579.877" y="-269.8507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
<text text-anchor="middle" x="570.593" y="-328.3557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g>
<!-- A12 -->
<g id="node13" class="node">
<title>A12</title>
<polygon fill="none" stroke="#000000" points="506.5,-100 506.5,-132 628.5,-132 628.5,-100 506.5,-100"/>
<text text-anchor="start" x="543.8845" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
<polygon fill="none" stroke="#000000" points="506.5,-68 506.5,-100 628.5,-100 628.5,-68 506.5,-68"/>
<text text-anchor="start" x="536.9355" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<polygon fill="none" stroke="#000000" points="506.5,0 506.5,-68 628.5,-68 628.5,0 506.5,0"/>
<text text-anchor="start" x="516.1035" y="-49" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote()</text>
<text text-anchor="start" x="526.382" y="-37" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_publ_mqtt()</text>
<text text-anchor="start" x="552.5025" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A8&#45;&gt;A12 -->
<g id="edge14" class="edge">
<title>A8&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M507.022,-244.4839C518.719,-209.9635 533.1714,-167.3112 545.0148,-132.3588"/>
<polygon fill="none" stroke="#000000" points="503.6947,-243.3974 503.8003,-253.9917 510.3245,-245.6439 503.6947,-243.3974"/>
</g>
<!-- A9&#45;&gt;A9 -->
<g id="edge17" class="edge">
<title>A9&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M281.8471,-348.2542C292.4443,-341.506 299.5,-328.0879 299.5,-308 299.5,-293.248 295.6948,-282.093 289.4763,-274.5351"/>
<polygon fill="#000000" stroke="#000000" points="281.8471,-267.7458 292.3089,-271.0321 285.5822,-271.0697 289.3174,-274.3937 289.3174,-274.3937 289.3174,-274.3937 285.5822,-271.0697 286.3258,-277.7553 281.8471,-267.7458 281.8471,-267.7458"/>
<text text-anchor="middle" x="301.0069" y="-270.4817" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
<text text-anchor="middle" x="291.5637" y="-327.7732" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g>
<!-- A13 -->
<g id="node14" class="node">
<title>A13</title>
<polygon fill="none" stroke="#000000" points="144.5,-94 144.5,-126 263.5,-126 263.5,-94 144.5,-94"/>
<text text-anchor="start" x="177.05" y="-107" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
<polygon fill="none" stroke="#000000" points="144.5,-62 144.5,-94 263.5,-94 263.5,-62 144.5,-62"/>
<text text-anchor="start" x="173.4355" y="-75" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
<polygon fill="none" stroke="#000000" points="144.5,-6 144.5,-62 263.5,-62 263.5,-6 144.5,-6"/>
<text text-anchor="start" x="154.268" y="-43" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote(</text>
<text text-anchor="start" x="161.2175" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">)async_publ_mqtt()</text>
<text text-anchor="start" x="189.0025" y="-19" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A9&#45;&gt;A13 -->
<g id="edge16" class="edge">
<title>A9&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M203.5,-243.955C203.5,-207.4743 203.5,-162.045 203.5,-126.2187"/>
<polygon fill="none" stroke="#000000" points="200.0001,-243.9917 203.5,-253.9917 207.0001,-243.9917 200.0001,-243.9917"/>
</g> </g>
<!-- A10 --> <!-- A10 -->
<g id="node11" class="node"> <g id="node11" class="node">
<title>A10</title> <title>A10</title>
<polygon fill="none" stroke="#000000" points="281.5,-698 281.5,-730 397.5,-730 397.5,-698 281.5,-698"/> <polygon fill="none" stroke="#000000" points="436.5,-100 436.5,-132 614.5,-132 614.5,-100 436.5,-100"/>
<text text-anchor="start" x="309.774" y="-711" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text> <text text-anchor="start" x="481.0515" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamServer</text>
<polygon fill="none" stroke="#000000" points="281.5,-618 281.5,-698 397.5,-698 397.5,-618 281.5,-618"/> <polygon fill="none" stroke="#000000" points="436.5,-68 436.5,-100 614.5,-100 614.5,-68 436.5,-68"/>
<text text-anchor="start" x="325.053" y="-679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">reader</text> <text text-anchor="start" x="477.4325" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote</text>
<text text-anchor="start" x="327.283" y="-667" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text> <polygon fill="none" stroke="#000000" points="436.5,0 436.5,-68 614.5,-68 614.5,0 436.5,0"/>
<text text-anchor="start" x="329.497" y="-655" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text> <text text-anchor="start" x="477.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="325.053" y="-643" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text> <text text-anchor="start" x="467.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="325.608" y="-631" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</text> <text text-anchor="start" x="446.309" y="-25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish_outstanding_mqtt()</text>
<polygon fill="none" stroke="#000000" points="281.5,-490 281.5,-618 397.5,-618 397.5,-490 281.5,-490"/> <text text-anchor="start" x="510.5025" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="291.1575" y="-599" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;server_loop()</text>
<text text-anchor="start" x="293.378" y="-587" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;client_loop()</text>
<text text-anchor="start" x="311.154" y="-575" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;loop</text>
<text text-anchor="start" x="327.282" y="-563" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">disc()</text>
<text text-anchor="start" x="324.5025" y="-551" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="304.7705" y="-527" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_read()</text>
<text text-anchor="start" x="309.78" y="-515" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_write()</text>
<text text-anchor="start" x="298.107" y="-503" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_forward()</text>
</g> </g>
<!-- A10&#45;&gt;A8 --> <!-- A9&#45;&gt;A10 -->
<g id="edge7" class="edge">
<title>A9&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M629.9027,-187.1874C628.7765,-185.436 627.642,-183.7056 626.5,-182 615.1346,-165.026 601.5426,-147.838 588.0698,-132.0797"/>
<polygon fill="#000000" stroke="#000000" points="635.4599,-196.1027 626.3512,-189.9968 632.8149,-191.8595 630.17,-187.6163 630.17,-187.6163 630.17,-187.6163 632.8149,-191.8595 633.9889,-185.2359 635.4599,-196.1027 635.4599,-196.1027"/>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="632.5,-82 632.5,-114 770.5,-114 770.5,-82 632.5,-82"/>
<text text-anchor="start" x="658.997" y="-95" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamClient</text>
<polygon fill="none" stroke="#000000" points="632.5,-62 632.5,-82 770.5,-82 770.5,-62 632.5,-62"/>
<polygon fill="none" stroke="#000000" points="632.5,-18 632.5,-62 770.5,-62 770.5,-18 632.5,-18"/>
<text text-anchor="start" x="655.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="642.324" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;_async_forward())</text>
</g>
<!-- A9&#45;&gt;A11 -->
<g id="edge8" class="edge">
<title>A9&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M694.7749,-171.8077C696.0812,-151.2556 697.3558,-131.2022 698.4379,-114.1772"/>
<polygon fill="#000000" stroke="#000000" points="694.1318,-181.9259 690.2753,-171.6605 694.449,-176.9359 694.7662,-171.946 694.7662,-171.946 694.7662,-171.946 694.449,-176.9359 699.2572,-172.2315 694.1318,-181.9259 694.1318,-181.9259"/>
</g>
<!-- A12 -->
<g id="node13" class="node">
<title>A12</title>
<polygon fill="none" stroke="#000000" points="687.5,-856 687.5,-888 838.5,-888 838.5,-856 687.5,-856"/>
<text text-anchor="start" x="731.0455" y="-869" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3</text>
<polygon fill="none" stroke="#000000" points="687.5,-824 687.5,-856 838.5,-856 838.5,-824 687.5,-824"/>
<text text-anchor="start" x="697.432" y="-837" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3</text>
<polygon fill="none" stroke="#000000" points="687.5,-792 687.5,-824 838.5,-824 838.5,-792 687.5,-792"/>
<text text-anchor="start" x="743.554" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
</g>
<!-- A13 -->
<g id="node14" class="node">
<title>A13</title>
<polygon fill="none" stroke="#000000" points="687.5,-560 687.5,-592 838.5,-592 838.5,-560 687.5,-560"/>
<text text-anchor="start" x="718.2685" y="-573" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3Client</text>
<polygon fill="none" stroke="#000000" points="687.5,-528 687.5,-560 838.5,-560 838.5,-528 687.5,-528"/>
<text text-anchor="start" x="697.432" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3</text>
<polygon fill="none" stroke="#000000" points="687.5,-496 687.5,-528 838.5,-528 838.5,-496 687.5,-496"/>
<text text-anchor="start" x="748.0025" y="-509" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A12&#45;&gt;A13 -->
<g id="edge9" class="edge"> <g id="edge9" class="edge">
<title>A10&#45;&gt;A8</title> <title>A12&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M402.0319,-480.6532C422.1536,-439.0316 443.3588,-395.1687 459.3606,-362.0691"/> <path fill="none" stroke="#000000" d="M762.5,-781.767C762.5,-726.3695 762.5,-644.0054 762.5,-592.1957"/>
<polygon fill="none" stroke="#000000" points="398.824,-479.2474 397.6226,-489.7739 405.1262,-482.2941 398.824,-479.2474"/> <polygon fill="none" stroke="#000000" points="759.0001,-781.7837 762.5,-791.7837 766.0001,-781.7837 759.0001,-781.7837"/>
</g>
<!-- A10&#45;&gt;A9 -->
<g id="edge10" class="edge">
<title>A10&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M281.2511,-480.6532C262.5076,-439.0316 242.7548,-395.1687 227.849,-362.0691"/>
<polygon fill="none" stroke="#000000" points="278.0609,-482.0929 285.3584,-489.7739 284.4435,-479.2186 278.0609,-482.0929"/>
</g>
<!-- A11&#45;&gt;A12 -->
<g id="edge11" class="edge">
<title>A11&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M622.3544,-225.9369C611.8702,-195.3687 600.1133,-161.0894 590.181,-132.1301"/>
<polygon fill="none" stroke="#000000" points="619.1547,-227.3962 625.7097,-235.7198 625.7761,-225.1252 619.1547,-227.3962"/>
</g>
<!-- A11&#45;&gt;A13 -->
<g id="edge12" class="edge">
<title>A11&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M622.0673,-226.7211C613.147,-210.1001 601.7805,-194.0346 587.5,-182 538.0407,-140.3192 358.189,-98.0533 263.2057,-77.9953"/>
<polygon fill="none" stroke="#000000" points="619.1257,-228.6587 626.7743,-235.9901 625.367,-225.4892 619.1257,-228.6587"/>
</g> </g>
<!-- A14 --> <!-- A14 -->
<g id="node15" class="node"> <g id="node15" class="node">
<title>A14</title> <title>A14</title>
<polygon fill="none" stroke="#000000" points="178.5,-1320 178.5,-1352 281.5,-1352 281.5,-1320 178.5,-1320"/> <polygon fill="none" stroke="#000000" points="466.5,-324 466.5,-356 617.5,-356 617.5,-324 466.5,-324"/>
<text text-anchor="start" x="219.162" y="-1333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text> <text text-anchor="start" x="495.323" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3Server</text>
<polygon fill="none" stroke="#000000" points="178.5,-1264 178.5,-1320 281.5,-1320 281.5,-1264 178.5,-1264"/> <polygon fill="none" stroke="#000000" points="466.5,-292 466.5,-324 617.5,-324 617.5,-292 466.5,-292"/>
<text text-anchor="start" x="221.9415" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text> <text text-anchor="start" x="476.432" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3</text>
<text text-anchor="start" x="197.486" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text> <polygon fill="none" stroke="#000000" points="466.5,-248 466.5,-292 617.5,-292 617.5,-248 466.5,-248"/>
<text text-anchor="start" x="211.1035" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text> <text text-anchor="start" x="527.0025" y="-261" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<polygon fill="none" stroke="#000000" points="178.5,-1112 178.5,-1264 281.5,-1264 281.5,-1112 178.5,-1112"/> </g>
<text text-anchor="start" x="205.8355" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text> <!-- A12&#45;&gt;A14 -->
<text text-anchor="start" x="203.8845" y="-1233" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text> <g id="edge10" class="edge">
<text text-anchor="start" x="200.8305" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text> <title>A12&#45;&gt;A14</title>
<text text-anchor="start" x="199.1605" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text> <path fill="none" stroke="#000000" d="M745.971,-781.9742C732.8528,-744.797 711.2028,-697.74 678.5,-666 639.382,-628.0337 597.9598,-660.5189 566.5,-616 537.8593,-575.4705 537.2638,-434.5757 539.3427,-356.0029"/>
<text text-anchor="start" x="197.21" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text> <polygon fill="none" stroke="#000000" points="742.7431,-783.3525 749.2852,-791.6862 749.368,-781.0917 742.7431,-783.3525"/>
<text text-anchor="start" x="212.213" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text> </g>
<text text-anchor="start" x="204.994" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text> <!-- A13&#45;&gt;A11 -->
<text text-anchor="start" x="206.3745" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text> <g id="edge12" class="edge">
<text text-anchor="start" x="190.537" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text> <title>A13&#45;&gt;A11</title>
<text text-anchor="start" x="199.9855" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text> <path fill="none" stroke="#000000" d="M765.7562,-483.9177C768.3319,-411.4858 768.4508,-286.4642 746.5,-182 742.3769,-162.3782 735.3279,-141.7356 728.0167,-123.3919"/>
<text text-anchor="start" x="188.3225" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text> <polygon fill="#000000" stroke="#000000" points="765.7548,-483.9554 769.5185,-490.1064 765.2883,-495.9463 761.5246,-489.7953 765.7548,-483.9554"/>
<polygon fill="#000000" stroke="#000000" points="724.1836,-114.0264 732.1361,-121.5768 726.0775,-118.6539 727.9715,-123.2813 727.9715,-123.2813 727.9715,-123.2813 726.0775,-118.6539 723.8068,-124.9858 724.1836,-114.0264 724.1836,-114.0264"/>
<text text-anchor="middle" x="738.872" y="-124.6003" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A13&#45;&gt;A14 -->
<g id="edge11" class="edge">
<title>A13&#45;&gt;A14</title>
<path fill="none" stroke="#000000" d="M698.7055,-489.1877C669.7319,-463.8077 639.1408,-436.1834 626.5,-422 608.4095,-401.7018 590.66,-377.5241 576.0987,-356.2077"/>
<polygon fill="#000000" stroke="#000000" points="706.4202,-495.9268 695.9285,-492.737 702.6546,-492.6374 698.889,-489.348 698.889,-489.348 698.889,-489.348 702.6546,-492.6374 701.8495,-485.9589 706.4202,-495.9268 706.4202,-495.9268"/>
</g>
<!-- A14&#45;&gt;A3 -->
<g id="edge13" class="edge">
<title>A14&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M490.0309,-240.027C462.4537,-206.8219 428.4748,-165.9084 400.3932,-132.0959"/>
<polygon fill="none" stroke="#000000" points="487.4521,-242.4001 496.5336,-247.8568 492.8371,-237.9278 487.4521,-242.4001"/>
</g>
<!-- A14&#45;&gt;A10 -->
<g id="edge14" class="edge">
<title>A14&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M537.0108,-235.7838C535.0371,-206.6727 532.7079,-172.3163 530.6628,-142.1514"/>
<polygon fill="#000000" stroke="#000000" points="537.0175,-235.8843 541.4142,-241.6 537.8293,-247.8568 533.4325,-242.1412 537.0175,-235.8843"/>
<polygon fill="#000000" stroke="#000000" points="529.9811,-132.0959 535.1473,-141.7686 530.3193,-137.0845 530.6576,-142.073 530.6576,-142.073 530.6576,-142.073 530.3193,-137.0845 526.1679,-142.3774 529.9811,-132.0959 529.9811,-132.0959"/>
<text text-anchor="middle" x="539.6402" y="-146.6088" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g> </g>
<!-- A15 --> <!-- A15 -->
<g id="node16" class="node"> <g id="node16" class="node">
<title>A15</title> <title>A15</title>
<polygon fill="none" stroke="#000000" points="431.5,-940 431.5,-972 498.5,-972 498.5,-940 431.5,-940"/> <polygon fill="none" stroke="#000000" points="292.5,-862 292.5,-894 449.5,-894 449.5,-862 292.5,-862"/>
<text text-anchor="start" x="447.493" y="-953" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text> <text text-anchor="start" x="335.711" y="-875" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3P</text>
<polygon fill="none" stroke="#000000" points="431.5,-920 431.5,-940 498.5,-940 498.5,-920 431.5,-920"/> <polygon fill="none" stroke="#000000" points="292.5,-830 292.5,-862 449.5,-862 449.5,-830 292.5,-830"/>
<polygon fill="none" stroke="#000000" points="431.5,-876 431.5,-920 498.5,-920 498.5,-876 431.5,-876"/> <text text-anchor="start" x="302.0975" y="-843" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3P</text>
<text text-anchor="start" x="441.384" y="-901" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text> <polygon fill="none" stroke="#000000" points="292.5,-786 292.5,-830 449.5,-830 449.5,-786 292.5,-786"/>
<text text-anchor="start" x="449.168" y="-889" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text> <text text-anchor="start" x="351.554" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
</g> <text text-anchor="start" x="356.0025" y="-799" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<!-- A14&#45;&gt;A15 -->
<g id="edge18" class="edge">
<title>A14&#45;&gt;A15</title>
<path fill="none" stroke="#000000" d="M287.6238,-1123.7067C291.4001,-1119.5529 295.359,-1115.6229 299.5,-1112 342.985,-1073.9563 380.3024,-1104.4478 419.5,-1062 442.1524,-1037.4693 453.4109,-1001.3633 459.0018,-972.2357"/>
<polygon fill="none" stroke="#000000" points="284.8741,-1121.5366 281.0238,-1131.4071 290.1891,-1126.0921 284.8741,-1121.5366"/>
</g> </g>
<!-- A16 --> <!-- A16 -->
<g id="node17" class="node"> <g id="node17" class="node">
<title>A16</title> <title>A16</title>
<polygon fill="none" stroke="#000000" points="187.5,-940 187.5,-972 254.5,-972 254.5,-940 187.5,-940"/> <polygon fill="none" stroke="#000000" points="359.5,-560 359.5,-592 516.5,-592 516.5,-560 359.5,-560"/>
<text text-anchor="start" x="200.1585" y="-953" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text> <text text-anchor="start" x="389.934" y="-573" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3PClient</text>
<polygon fill="none" stroke="#000000" points="187.5,-920 187.5,-940 254.5,-940 254.5,-920 187.5,-920"/> <polygon fill="none" stroke="#000000" points="359.5,-528 359.5,-560 516.5,-560 516.5,-528 359.5,-528"/>
<polygon fill="none" stroke="#000000" points="187.5,-876 187.5,-920 254.5,-920 254.5,-876 187.5,-876"/> <text text-anchor="start" x="369.0975" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3P</text>
<text text-anchor="start" x="197.384" y="-901" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text> <polygon fill="none" stroke="#000000" points="359.5,-496 359.5,-528 516.5,-528 516.5,-496 359.5,-496"/>
<text text-anchor="start" x="205.168" y="-889" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text> <text text-anchor="start" x="423.0025" y="-509" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g> </g>
<!-- A14&#45;&gt;A16 --> <!-- A15&#45;&gt;A16 -->
<g id="edge19" class="edge"> <g id="edge15" class="edge">
<title>A14&#45;&gt;A16</title> <title>A15&#45;&gt;A16</title>
<path fill="none" stroke="#000000" d="M225.6878,-1101.5366C224.3454,-1055.5988 222.9195,-1006.7991 221.9029,-972.012"/> <path fill="none" stroke="#000000" d="M385.0601,-775.6747C397.5656,-720.4264 415.3156,-642.0086 426.6189,-592.0719"/>
<polygon fill="none" stroke="#000000" points="222.191,-1101.7024 225.9817,-1111.5959 229.188,-1101.4979 222.191,-1101.7024"/> <polygon fill="none" stroke="#000000" points="381.5829,-775.1831 382.7888,-785.709 388.4102,-776.7285 381.5829,-775.1831"/>
</g>
<!-- A15&#45;&gt;A6 -->
<g id="edge21" class="edge">
<title>A15&#45;&gt;A6</title>
<path fill="none" stroke="#000000" d="M465.7237,-875.9684C466.6086,-841.2366 467.8512,-792.4655 469.031,-746.1572"/>
<polygon fill="#000000" stroke="#000000" points="469.2896,-736.0098 473.5333,-746.1212 469.1622,-741.0082 469.0348,-746.0066 469.0348,-746.0066 469.0348,-746.0066 469.1622,-741.0082 464.5362,-745.8919 469.2896,-736.0098 469.2896,-736.0098"/>
</g>
<!-- A16&#45;&gt;A7 -->
<g id="edge20" class="edge">
<title>A16&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M220.0411,-875.9684C219.6216,-832.0581 218.9877,-765.7079 218.4579,-710.2644"/>
<polygon fill="#000000" stroke="#000000" points="218.3603,-700.0467 222.9557,-710.0032 218.4081,-705.0465 218.456,-710.0463 218.456,-710.0463 218.456,-710.0463 218.4081,-705.0465 213.9562,-710.0893 218.3603,-700.0467 218.3603,-700.0467"/>
</g> </g>
<!-- A17 --> <!-- A17 -->
<g id="node18" class="node"> <g id="node18" class="node">
<title>A17</title> <title>A17</title>
<polygon fill="none" stroke="#000000" points=".5,-336 .5,-368 107.5,-368 107.5,-336 .5,-336"/> <polygon fill="none" stroke="#000000" points="252.5,-324 252.5,-356 409.5,-356 409.5,-324 252.5,-324"/>
<text text-anchor="start" x="24.2695" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text> <text text-anchor="start" x="280.9885" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3PServer</text>
<polygon fill="none" stroke="#000000" points=".5,-268 .5,-336 107.5,-336 107.5,-268 .5,-268"/> <polygon fill="none" stroke="#000000" points="252.5,-292 252.5,-324 409.5,-324 409.5,-292 252.5,-292"/>
<text text-anchor="start" x="44.5515" y="-317" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text> <text text-anchor="start" x="262.0975" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3P</text>
<text text-anchor="start" x="45.387" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text> <polygon fill="none" stroke="#000000" points="252.5,-248 252.5,-292 409.5,-292 409.5,-248 252.5,-248"/>
<text text-anchor="start" x="43.997" y="-293" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text> <text text-anchor="start" x="316.0025" y="-261" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
<text text-anchor="start" x="10.383" y="-281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
<polygon fill="none" stroke="#000000" points=".5,-248 .5,-268 107.5,-268 107.5,-248 .5,-248"/>
</g> </g>
<!-- A17&#45;&gt;A13 --> <!-- A15&#45;&gt;A17 -->
<g id="edge16" class="edge">
<title>A15&#45;&gt;A17</title>
<path fill="none" stroke="#000000" d="M363.8918,-775.579C359.4421,-730.8408 353.651,-669.833 349.5,-616 342.4777,-524.9291 336.5213,-418.8313 333.2353,-356.213"/>
<polygon fill="none" stroke="#000000" points="360.4287,-776.124 364.9058,-785.7264 367.394,-775.4279 360.4287,-776.124"/>
</g>
<!-- A16&#45;&gt;A11 -->
<g id="edge18" class="edge">
<title>A16&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M432.5814,-483.65C426.5012,-392.955 420.8979,-227.2936 456.5,-182 504.3791,-121.0873 555.3805,-168.9121 623.5,-132 629.8263,-128.572 636.106,-124.5733 642.1952,-120.2709"/>
<polygon fill="#000000" stroke="#000000" points="432.5842,-483.6913 436.9918,-489.3986 433.4189,-495.6623 429.0112,-489.955 432.5842,-483.6913"/>
<polygon fill="#000000" stroke="#000000" points="650.3668,-114.2407 644.9924,-123.7993 646.3436,-117.2096 642.3205,-120.1784 642.3205,-120.1784 642.3205,-120.1784 646.3436,-117.2096 639.6485,-116.5576 650.3668,-114.2407 650.3668,-114.2407"/>
<text text-anchor="middle" x="640.8006" y="-128.8045" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A16&#45;&gt;A17 -->
<g id="edge17" class="edge">
<title>A16&#45;&gt;A17</title>
<path fill="none" stroke="#000000" d="M412.1504,-486.6673C394.7061,-447.2138 371.6786,-395.1329 354.5264,-356.3401"/>
<polygon fill="#000000" stroke="#000000" points="416.2323,-495.8992 408.0728,-488.5731 414.2104,-491.3263 412.1884,-486.7533 412.1884,-486.7533 412.1884,-486.7533 414.2104,-491.3263 416.3041,-484.9336 416.2323,-495.8992 416.2323,-495.8992"/>
</g>
<!-- A17&#45;&gt;A4 -->
<g id="edge19" class="edge">
<title>A17&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M290.2801,-239.1397C267.862,-204.1022 240.0902,-160.6973 217.9615,-126.112"/>
<polygon fill="none" stroke="#000000" points="287.5198,-241.3198 295.8575,-247.8568 293.4162,-237.5471 287.5198,-241.3198"/>
</g>
<!-- A17&#45;&gt;A10 -->
<g id="edge20" class="edge">
<title>A17&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M378.7264,-238.2577C392.9202,-219.9137 408.6333,-200.0024 423.5,-182 434.8823,-168.217 447.2524,-153.7473 459.3242,-139.8828"/>
<polygon fill="#000000" stroke="#000000" points="378.561,-238.4723 378.0657,-245.6664 371.2345,-247.9761 371.7298,-240.782 378.561,-238.4723"/>
<polygon fill="#000000" stroke="#000000" points="465.9919,-132.2508 462.8014,-142.7423 462.7022,-136.0162 459.4126,-139.7816 459.4126,-139.7816 459.4126,-139.7816 462.7022,-136.0162 456.0237,-136.8209 465.9919,-132.2508 465.9919,-132.2508"/>
<text text-anchor="middle" x="460.4315" y="-148.4623" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A18 -->
<g id="node19" class="node">
<title>A18</title>
<polygon fill="none" stroke="#000000" points="255.5,-1278 255.5,-1310 358.5,-1310 358.5,-1278 255.5,-1278"/>
<text text-anchor="start" x="296.162" y="-1291" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
<polygon fill="none" stroke="#000000" points="255.5,-1222 255.5,-1278 358.5,-1278 358.5,-1222 255.5,-1222"/>
<text text-anchor="start" x="298.9415" y="-1259" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text>
<text text-anchor="start" x="274.486" y="-1247" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
<text text-anchor="start" x="288.1035" y="-1235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
<polygon fill="none" stroke="#000000" points="255.5,-1070 255.5,-1222 358.5,-1222 358.5,-1070 255.5,-1070"/>
<text text-anchor="start" x="282.8355" y="-1203" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
<text text-anchor="start" x="280.8845" y="-1191" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
<text text-anchor="start" x="277.8305" y="-1179" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="276.1605" y="-1167" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
<text text-anchor="start" x="274.21" y="-1155" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
<text text-anchor="start" x="289.213" y="-1143" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
<text text-anchor="start" x="281.994" y="-1131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
<text text-anchor="start" x="283.3745" y="-1119" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
<text text-anchor="start" x="267.537" y="-1107" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text>
<text text-anchor="start" x="276.9855" y="-1095" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
<text text-anchor="start" x="265.3225" y="-1083" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text>
</g>
<!-- A19 -->
<g id="node20" class="node">
<title>A19</title>
<polygon fill="none" stroke="#000000" points="467.5,-856 467.5,-888 534.5,-888 534.5,-856 467.5,-856"/>
<text text-anchor="start" x="483.493" y="-869" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text>
<polygon fill="none" stroke="#000000" points="467.5,-836 467.5,-856 534.5,-856 534.5,-836 467.5,-836"/>
<polygon fill="none" stroke="#000000" points="467.5,-792 467.5,-836 534.5,-836 534.5,-792 467.5,-792"/>
<text text-anchor="start" x="477.384" y="-817" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="485.168" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A18&#45;&gt;A19 -->
<g id="edge21" class="edge">
<title>A18&#45;&gt;A19</title>
<path fill="none" stroke="#000000" d="M364.1135,-1079.5989C368.3725,-1074.1218 372.8381,-1068.8788 377.5,-1064 406.4667,-1033.6857 432.8916,-1047.9478 457.5,-1014 484.2551,-977.0907 494.4701,-925.4423 498.3138,-888.0116"/>
<polygon fill="none" stroke="#000000" points="361.1895,-1077.6653 358.0224,-1087.7757 366.8031,-1081.8471 361.1895,-1077.6653"/>
</g>
<!-- A20 -->
<g id="node21" class="node">
<title>A20</title>
<polygon fill="none" stroke="#000000" points="207.5,-856 207.5,-888 274.5,-888 274.5,-856 207.5,-856"/>
<text text-anchor="start" x="220.1585" y="-869" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text>
<polygon fill="none" stroke="#000000" points="207.5,-836 207.5,-856 274.5,-856 274.5,-836 207.5,-836"/>
<polygon fill="none" stroke="#000000" points="207.5,-792 207.5,-836 274.5,-836 274.5,-792 207.5,-792"/>
<text text-anchor="start" x="217.384" y="-817" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="225.168" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A18&#45;&gt;A20 -->
<g id="edge22" class="edge"> <g id="edge22" class="edge">
<title>A17&#45;&gt;A13</title> <title>A18&#45;&gt;A20</title>
<path fill="none" stroke="#000000" d="M80.8473,-247.8342C91.2165,-226.5814 103.6422,-202.8044 116.5,-182 126.2708,-166.1905 137.6417,-149.852 148.8772,-134.6044"/> <path fill="none" stroke="#000000" d="M281.9132,-1059.6156C270.6119,-999.6842 257.9,-932.2726 249.5769,-888.1352"/>
<polygon fill="#000000" stroke="#000000" points="155.0942,-126.2561 152.7306,-136.9642 152.1078,-130.2663 149.1214,-134.2765 149.1214,-134.2765 149.1214,-134.2765 152.1078,-130.2663 145.5123,-131.5887 155.0942,-126.2561 155.0942,-126.2561"/> <polygon fill="none" stroke="#000000" points="278.494,-1060.3716 283.7865,-1069.5499 285.3728,-1059.0744 278.494,-1060.3716"/>
<text text-anchor="middle" x="151.047" y="-142.8423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text> </g>
<text text-anchor="middle" x="81.2636" y="-224.8385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text> <!-- A21&#45;&gt;A7 -->
<g id="edge24" class="edge">
<title>A21&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M575.3563,-1063.642C577.3669,-1050.8472 579.4367,-1037.6756 581.5179,-1024.4317"/>
<polygon fill="#000000" stroke="#000000" points="583.1108,-1014.295 586.0038,-1024.8724 582.3345,-1019.2344 581.5583,-1024.1738 581.5583,-1024.1738 581.5583,-1024.1738 582.3345,-1019.2344 577.1129,-1023.4752 583.1108,-1014.295 583.1108,-1014.295"/>
<text text-anchor="middle" x="569.8202" y="-1041.4234" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A21&#45;&gt;A12 -->
<g id="edge23" class="edge">
<title>A21&#45;&gt;A12</title>
<path fill="none" stroke="#000000" d="M618.6601,-1105.4878C638.7912,-1077.0562 660.4946,-1044.7401 678.5,-1014 702.5743,-972.8987 725.7861,-923.8582 741.6852,-888.3319"/>
<polygon fill="none" stroke="#000000" points="615.6988,-1103.6125 612.7495,-1113.7886 621.4009,-1107.6728 615.6988,-1103.6125"/>
</g>
<!-- A21&#45;&gt;A19 -->
<g id="edge25" class="edge">
<title>A21&#45;&gt;A19</title>
<path fill="none" stroke="#000000" d="M535.6437,-1063.642C526.7659,-1007.1466 516.7335,-943.3043 509.6671,-898.3363"/>
<polygon fill="#000000" stroke="#000000" points="508.0718,-888.1839 514.0697,-897.3641 508.848,-893.1233 509.6242,-898.0627 509.6242,-898.0627 509.6242,-898.0627 508.848,-893.1233 505.1788,-898.7613 508.0718,-888.1839 508.0718,-888.1839"/>
</g>
<!-- A22&#45;&gt;A7 -->
<g id="edge27" class="edge">
<title>A22&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M468.0427,-1099.8226C474.4445,-1087.349 481.6217,-1075.043 489.5,-1064 508.3074,-1037.6376 524.3881,-1040.8449 542.5,-1014 543.9285,-1011.8827 545.3335,-1009.731 546.715,-1007.5491"/>
<polygon fill="#000000" stroke="#000000" points="551.9277,-998.9917 550.5685,-1009.873 549.3265,-1003.2618 546.7254,-1007.532 546.7254,-1007.532 546.7254,-1007.532 549.3265,-1003.2618 542.8822,-1005.1909 551.9277,-998.9917 551.9277,-998.9917"/>
<text text-anchor="middle" x="468.9105" y="-1076.8414" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A22&#45;&gt;A15 -->
<g id="edge26" class="edge">
<title>A22&#45;&gt;A15</title>
<path fill="none" stroke="#000000" d="M414.749,-1089.7927C403.4687,-1026.1139 389.3056,-946.1607 380.0853,-894.1104"/>
<polygon fill="none" stroke="#000000" points="411.3428,-1090.6301 416.5335,-1099.8663 418.2355,-1089.4091 411.3428,-1090.6301"/>
</g>
<!-- A22&#45;&gt;A20 -->
<g id="edge28" class="edge">
<title>A22&#45;&gt;A20</title>
<path fill="none" stroke="#000000" d="M393.1989,-1099.7863C385.4244,-1086.9738 376.5183,-1074.5787 366.5,-1064 336.6255,-1032.4545 308.2844,-1048.9681 282.5,-1014 257.8033,-980.507 247.4516,-934.4437 243.1951,-898.1251"/>
<polygon fill="#000000" stroke="#000000" points="242.1493,-888.1583 247.6684,-897.6341 242.6711,-893.131 243.1929,-898.1037 243.1929,-898.1037 243.1929,-898.1037 242.6711,-893.131 238.7175,-898.5734 242.1493,-888.1583 242.1493,-888.1583"/>
</g>
<!-- A23 -->
<g id="node24" class="node">
<title>A23</title>
<polygon fill="none" stroke="#000000" points="395.5,-1598 395.5,-1630 470.5,-1630 470.5,-1598 395.5,-1598"/>
<text text-anchor="start" x="415.2175" y="-1611" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
<polygon fill="none" stroke="#000000" points="395.5,-1446 395.5,-1598 470.5,-1598 470.5,-1446 395.5,-1446"/>
<text text-anchor="start" x="424.6615" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
<text text-anchor="start" x="405.49" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
<text text-anchor="start" x="406.605" y="-1543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
<text text-anchor="start" x="416.6085" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
<text text-anchor="start" x="406.8895" y="-1519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
<text text-anchor="start" x="414.942" y="-1507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
<text text-anchor="start" x="426.8915" y="-1495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
<text text-anchor="start" x="413.5535" y="-1483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
<text text-anchor="start" x="411.879" y="-1471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
<text text-anchor="start" x="426.3365" y="-1459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
<polygon fill="none" stroke="#000000" points="395.5,-1378 395.5,-1446 470.5,-1446 470.5,-1378 395.5,-1378"/>
<text text-anchor="start" x="406.89" y="-1427" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
<text text-anchor="start" x="410.224" y="-1415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
<text text-anchor="start" x="407.724" y="-1403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
<text text-anchor="start" x="418.0025" y="-1391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A23&#45;&gt;A21 -->
<g id="edge32" class="edge">
<title>A23&#45;&gt;A21</title>
<path fill="none" stroke="#000000" d="M473.6171,-1381.5836C475.5712,-1376.3055 477.537,-1371.0921 479.5,-1366 485.7253,-1349.8514 492.5573,-1333.0014 499.4927,-1316.4014"/>
<polygon fill="#000000" stroke="#000000" points="470.1153,-1391.1417 469.3301,-1380.2039 471.8353,-1386.4468 473.5554,-1381.752 473.5554,-1381.752 473.5554,-1381.752 471.8353,-1386.4468 477.7808,-1383.3 470.1153,-1391.1417 470.1153,-1391.1417"/>
<text text-anchor="middle" x="500.3258" y="-1333.384" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="468.4143" y="-1368.2141" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A23&#45;&gt;A22 -->
<g id="edge31" class="edge">
<title>A23&#45;&gt;A22</title>
<path fill="none" stroke="#000000" d="M432.5,-1367.2886C432.5,-1337.886 432.5,-1307.5179 432.5,-1280.403"/>
<polygon fill="#000000" stroke="#000000" points="432.5,-1377.6415 428.0001,-1367.6414 432.5,-1372.6415 432.5001,-1367.6415 432.5001,-1367.6415 432.5001,-1367.6415 432.5,-1372.6415 437.0001,-1367.6415 432.5,-1377.6415 432.5,-1377.6415"/>
<text text-anchor="middle" x="440.9524" y="-1295.5292" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="424.0476" y="-1356.5153" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A24 -->
<g id="node25" class="node">
<title>A24</title>
<polygon fill="none" stroke="#000000" points=".5,-330 .5,-362 107.5,-362 107.5,-330 .5,-330"/>
<text text-anchor="start" x="24.2695" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
<polygon fill="none" stroke="#000000" points=".5,-262 .5,-330 107.5,-330 107.5,-262 .5,-262"/>
<text text-anchor="start" x="44.5515" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text>
<text text-anchor="start" x="45.387" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text>
<text text-anchor="start" x="43.997" y="-287" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="10.383" y="-275" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
<polygon fill="none" stroke="#000000" points=".5,-242 .5,-262 107.5,-262 107.5,-242 .5,-242"/>
</g>
<!-- A24&#45;&gt;A4 -->
<g id="edge33" class="edge">
<title>A24&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M84.8997,-241.788C94.9393,-222.6449 106.1365,-201.4067 116.5,-182 124.6218,-166.7911 133.4111,-150.5028 141.774,-135.0831"/>
<polygon fill="#000000" stroke="#000000" points="146.6405,-126.1193 145.824,-137.0548 144.2548,-130.5135 141.8692,-134.9077 141.8692,-134.9077 141.8692,-134.9077 144.2548,-130.5135 137.9144,-132.7607 146.6405,-126.1193 146.6405,-126.1193"/>
<text text-anchor="middle" x="145.4203" y="-143.0821" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="85.8379" y="-218.81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -3,28 +3,65 @@
// {generate:true} // {generate:true}
[note: You can stick notes on diagrams too!{bg:cornsilk}] [note: You can stick notes on diagrams too!{bg:cornsilk}]
[Singleton]^[Mqtt|<static>ha_restarts;<static>__client;<static>__cb_MqttIsUp|<async>publish();<async>close()]
[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()]
[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]
[Message]^[Talent|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();;close()]
[Message]^[SolarmanV5|control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;close()]
[Talent]^[ConnectionG3|remote_stream:ConnectionG3|healthy();close()]
[Talent]has-1>[Modbus]
[SolarmanV5]^[ConnectionG3P|remote_stream:ConnectionG3P|healthy();close()]
[SolarmanV5]has-1>[Modbus]
[AsyncStream|reader;writer;addr;r_addr;l_addr|<async>server_loop();<async>client_loop();<async>loop;disc();close();;__async_read();async_write();__async_forward()]^[ConnectionG3]
[AsyncStream]^[ConnectionG3P]
[Inverter|cls.db_stat;cls.entity_prfx;cls.discovery_prfx;cls.proxy_node_id;cls.proxy_unique_id;cls.mqtt:Mqtt|]^[InverterG3|__ha_restarts|async_create_remote();async_publ_mqtt();;close()]
[Inverter]^[InverterG3P|__ha_restarts|async_create_remote(;)async_publ_mqtt();close()]
[Mqtt]-[Inverter]
[ConnectionG3]^[InverterG3]
[ConnectionG3]has-0..1>[ConnectionG3]
[ConnectionG3P]^[InverterG3P]
[ConnectionG3P]has-0..1>[ConnectionG3P]
[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]^[InfosG3||ha_confs();parse()] [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|]
[Inverter]^[InverterG3|__ha_restarts|async_create_remote();async_publ_mqtt();;close()]
[Inverter]^[InverterG3P|__ha_restarts|async_create_remote(;)async_publ_mqtt();close()]
[Mqtt;<<Singleton>>]<-++[Inverter]
[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]
[<<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]
[ConnectionG3|remote.stream:ConnectionG3|healthy()]
[ConnectionG3Client|remote.stream:ConnectionG3|close()]
[ConnectionG3Server|remote.stream:ConnectionG3|;close()]
[ConnectionG3]^[ConnectionG3Client]
[ConnectionG3]^[ConnectionG3Server]
[ConnectionG3Client]<-[ConnectionG3Server]
[ConnectionG3Client]++-1>[AsyncStreamClient]
[ConnectionG3Server]^[InverterG3]
[ConnectionG3Server]++-1>[AsyncStreamServer]
[ConnectionG3P|remote.stream:ConnectionG3P|healthy();close()]
[ConnectionG3PClient|remote.stream:ConnectionG3P|close()]
[ConnectionG3PServer|remote.stream:ConnectionG3P|;close()]
[ConnectionG3P]^[ConnectionG3PClient]
[ConnectionG3P]^[ConnectionG3PServer]
[ConnectionG3PClient]<-[ConnectionG3PServer]
[ConnectionG3PClient]++-1>[AsyncStreamClient]
[ConnectionG3PServer]^[InverterG3P]
[ConnectionG3PServer]++-1>[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()] [Infos]^[InfosG3P||ha_confs();parse()]
[InfosG3P]->[SolarmanV5]
[InfosG3]->[Talent] [Talent|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();;close()]
[Talent]^[ConnectionG3]
[Talent]use->[<<AsyncIfc>>]
[Talent]->[InfosG3]
[SolarmanV5|control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;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] [ModbusConn|host;port;addr;stream:InverterG3P;|]has-1>[InverterG3P]

View File

@@ -1,11 +1,119 @@
if __name__ == "app.src.async_ifc": from abc import ABC, abstractmethod
from app.src.byte_fifo import ByteFifo
else: # pragma: no cover
from byte_fifo import ByteFifo
class AsyncIfc(): class AsyncIfc(ABC):
def __init__(self): @abstractmethod
self.read = ByteFifo() def get_conn_no(self):
self.write = ByteFifo() pass # pragma: no cover
self.forward = ByteFifo()
@abstractmethod
def set_node_id(self, value: str):
pass # pragma: no cover
#
# TX - QUEUE
#
@abstractmethod
def tx_add(self, data: bytearray):
''' add data to transmit queue'''
pass # pragma: no cover
@abstractmethod
def tx_flush(self):
''' send transmit queue and clears it'''
pass # pragma: no cover
@abstractmethod
def tx_get(self, size: int = None) -> bytearray:
'''removes size numbers of bytes and return them'''
pass # pragma: no cover
@abstractmethod
def tx_peek(self, size: int = None) -> bytearray:
'''returns size numbers of byte without removing them'''
pass # pragma: no cover
@abstractmethod
def tx_log(self, level, info):
''' log the transmit queue'''
pass # pragma: no cover
@abstractmethod
def tx_clear(self):
''' clear transmit queue'''
pass # pragma: no cover
@abstractmethod
def tx_len(self):
''' get numner of bytes in the transmit queue'''
pass # pragma: no cover
#
# FORWARD - QUEUE
#
@abstractmethod
def fwd_add(self, data: bytearray):
''' 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
#
@abstractmethod
def rx_get(self, size: int = None) -> bytearray:
'''removes size numbers of bytes and return them'''
pass # pragma: no cover
@abstractmethod
def rx_peek(self, size: int = None) -> bytearray:
'''returns size numbers of byte without removing them'''
pass # pragma: no cover
@abstractmethod
def rx_log(self, level, info):
''' logs the receive queue'''
pass # pragma: no cover
@abstractmethod
def rx_clear(self):
''' clear receive queue'''
pass # pragma: no cover
@abstractmethod
def rx_len(self):
''' get numner of bytes in the receive queue'''
pass # pragma: no cover
@abstractmethod
def rx_set_cb(self, callback):
pass # pragma: no cover
#
# Protocol Callbacks
#
@abstractmethod
def prot_set_timeout_cb(self, callback):
pass # pragma: no cover
@abstractmethod
def prot_set_init_new_client_conn_cb(self, callback):
pass # pragma: no cover
@abstractmethod
def prot_set_update_header_cb(self, callback):
pass # pragma: no cover

View File

@@ -7,19 +7,147 @@ from typing import Self
from itertools import count from itertools import count
if __name__ == "app.src.async_stream": if __name__ == "app.src.async_stream":
from app.src.inverter import Inverter
from app.src.byte_fifo import ByteFifo
from app.src.async_ifc import AsyncIfc from app.src.async_ifc import AsyncIfc
from app.src.messages import hex_dump_memory, State from app.src.infos import Infos
else: # pragma: no cover else: # pragma: no cover
from inverter import Inverter
from byte_fifo import ByteFifo
from async_ifc import AsyncIfc from async_ifc import AsyncIfc
from messages import hex_dump_memory, State from infos import Infos
import gc import gc
logger = logging.getLogger('conn') logger = logging.getLogger('conn')
class AsyncStream(): class AsyncIfcImpl(AsyncIfc):
_ids = count(0) _ids = count(0)
def __init__(self) -> None:
logger.debug('AsyncIfcImpl.__init__')
self.fwd_fifo = ByteFifo()
self.tx_fifo = ByteFifo()
self.rx_fifo = ByteFifo()
self.conn_no = next(self._ids)
self.node_id = ''
self.timeout_cb = None
self.init_new_client_conn_cb = None
self.update_header_cb = None
def close(self):
self.timeout_cb = None
self.fwd_fifo.reg_trigger(None)
self.tx_fifo.reg_trigger(None)
self.rx_fifo.reg_trigger(None)
def set_node_id(self, value: str):
self.node_id = value
def get_conn_no(self):
return self.conn_no
def tx_add(self, data: bytearray):
''' add data to transmit queue'''
self.tx_fifo += data
def tx_flush(self):
''' send transmit queue and clears it'''
self.tx_fifo()
def tx_get(self, size: int = None) -> bytearray:
'''removes size numbers of bytes and return them'''
return self.tx_fifo.get(size)
def tx_peek(self, size: int = None) -> bytearray:
'''returns size numbers of byte without removing them'''
return self.tx_fifo.peek(size)
def tx_log(self, level, info):
''' log the transmit queue'''
self.tx_fifo.logging(level, info)
def tx_clear(self):
''' clear transmit queue'''
self.tx_fifo.clear()
def tx_len(self):
''' get numner of bytes in the transmit queue'''
return len(self.tx_fifo)
def fwd_add(self, data: bytearray):
''' 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)
def rx_peek(self, size: int = None) -> bytearray:
'''returns size numbers of byte without removing them'''
return self.rx_fifo.peek(size)
def rx_log(self, level, info):
''' logs the receive queue'''
self.rx_fifo.logging(level, info)
def rx_clear(self):
''' clear receive queue'''
self.rx_fifo.clear()
def rx_len(self):
''' get numner of bytes in the receive queue'''
return len(self.rx_fifo)
def rx_set_cb(self, callback):
self.rx_fifo.reg_trigger(callback)
def prot_set_timeout_cb(self, callback):
self.timeout_cb = callback
def prot_set_init_new_client_conn_cb(self, callback):
self.init_new_client_conn_cb = callback
def prot_set_update_header_cb(self, callback):
self.update_header_cb = callback
class StreamPtr():
'''Descr StreamPtr'''
def __init__(self, _stream):
self.stream = _stream
@property
def ifc(self):
return self._ifc
@property
def stream(self):
return self._stream
@stream.setter
def stream(self, value):
self._stream = value
if value:
self._ifc = value.ifc
else:
self._ifc = None
class AsyncStream(AsyncIfcImpl):
MAX_PROC_TIME = 2 MAX_PROC_TIME = 2
'''maximum processing time for a received msg in sec''' '''maximum processing time for a received msg in sec'''
MAX_START_TIME = 400 MAX_START_TIME = 400
@@ -30,81 +158,29 @@ class AsyncStream():
'''maximum default time without a received msg in sec''' '''maximum default time without a received msg in sec'''
def __init__(self, reader: StreamReader, writer: StreamWriter, def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, ifc: "AsyncIfc") -> None: addr, rstream: "StreamPtr") -> None:
AsyncIfcImpl.__init__(self)
logger.debug('AsyncStream.__init__') logger.debug('AsyncStream.__init__')
ifc.write.reg_trigger(self.__write_cb)
self.ifc = ifc self.remote = rstream
self.tx_fifo.reg_trigger(self.__write_cb)
self._reader = reader self._reader = reader
self._writer = writer self._writer = writer
self.addr = addr self.addr = addr
self.r_addr = '' self.r_addr = ''
self.l_addr = '' self.l_addr = ''
self.conn_no = next(self._ids)
self.proc_start = None # start processing start timestamp self.proc_start = None # start processing start timestamp
self.proc_max = 0 self.proc_max = 0
self.async_publ_mqtt = None # will be set AsyncStreamServer only
def __write_cb(self): def __write_cb(self):
self._writer.write(self.ifc.write.get()) self._writer.write(self.tx_fifo.get())
def __timeout(self) -> int: def __timeout(self) -> int:
if self.state == State.init or self.state == State.received: if self.timeout_cb is callable:
to = self.MAX_START_TIME return self.timeout_cb
elif self.state == State.up and \ return 360
self.server_side and self.modbus_polling:
to = self.MAX_INV_IDLE_TIME
else:
to = self.MAX_DEF_IDLE_TIME
return to
async def publish_outstanding_mqtt(self):
'''Publish all outstanding MQTT topics'''
try:
if self.unique_id:
await self.async_publ_mqtt()
await self._async_publ_mqtt_proxy_stat('proxy')
except Exception:
pass
async def server_loop(self, addr: str) -> None:
'''Loop for receiving messages from the inverter (server-side)'''
logger.info(f'[{self.node_id}:{self.conn_no}] '
f'Accept connection from {addr}')
self.inc_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt()
await self.loop()
self.dec_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt()
logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for'
f' r{self.r_addr}')
# if the server connection closes, we also have to disconnect
# the connection to te TSUN cloud
if self.remote_stream:
logger.info(f'[{self.node_id}:{self.conn_no}] disc client '
f'connection: [{self.remote_stream.node_id}:'
f'{self.remote_stream.conn_no}]')
await self.remote_stream.disc()
async def client_loop(self, _: str) -> None:
'''Loop for receiving messages from the TSUN cloud (client-side)'''
client_stream = await self.remote_stream.loop()
logger.info(f'[{client_stream.node_id}:{client_stream.conn_no}] '
'Client loop stopped for'
f' l{client_stream.l_addr}')
# 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
# erase backlink to inverter
client_stream.remote_stream = None
if self.remote_stream == client_stream:
# logging.debug(f'Client l{client_stream.l_addr} refs:'
# f' {gc.get_referrers(client_stream)}')
# than erase client connection
self.remote_stream = None
async def loop(self) -> Self: async def loop(self) -> Self:
"""Async loop handler for precessing all received messages""" """Async loop handler for precessing all received messages"""
@@ -121,9 +197,9 @@ class AsyncStream():
await asyncio.wait_for(self.__async_read(), await asyncio.wait_for(self.__async_read(),
dead_conn_to) dead_conn_to)
if self.unique_id: await self.__async_write()
await self.async_write()
await self.__async_forward() await self.__async_forward()
if self.async_publ_mqtt:
await self.async_publ_mqtt() await self.async_publ_mqtt()
except asyncio.TimeoutError: except asyncio.TimeoutError:
@@ -150,19 +226,12 @@ class AsyncStream():
return self return self
except Exception: except Exception:
self.inc_counter('SW_Exception') Infos.inc_counter('SW_Exception')
logger.error( logger.error(
f"Exception for {self.addr}:\n" f"Exception for {self.addr}:\n"
f"{traceback.format_exc()}") f"{traceback.format_exc()}")
await asyncio.sleep(0) # be cooperative to other task await asyncio.sleep(0) # be cooperative to other task
async def async_write(self, headline: str = 'Transmit to ') -> None:
"""Async write handler to transmit the send_buffer"""
if len(self.ifc.write) > 0:
self.ifc.write.logging(logging.INFO, f'{headline}{self.addr}:')
self._writer.write(self.ifc.write.get())
await self._writer.drain()
async def disc(self) -> None: async def disc(self) -> None:
"""Async disc handler for graceful disconnect""" """Async disc handler for graceful disconnect"""
if self._writer.is_closing(): if self._writer.is_closing():
@@ -176,18 +245,18 @@ class AsyncStream():
hint: must be called before releasing the connection instance hint: must be called before releasing the connection instance
""" """
super().close()
self._reader.feed_eof() # abort awaited read self._reader.feed_eof() # abort awaited read
if self._writer.is_closing(): if self._writer.is_closing():
return return
logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}') logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
self.ifc.write.reg_trigger(None)
self._writer.close() self._writer.close()
def healthy(self) -> bool: def healthy(self) -> bool:
elapsed = 0 elapsed = 0
if self.proc_start is not None: if self.proc_start is not None:
elapsed = time.time() - self.proc_start elapsed = time.time() - self.proc_start
if self.state == State.closed or elapsed > self.MAX_PROC_TIME: if elapsed > self.MAX_PROC_TIME:
logging.debug(f'[{self.node_id}:{self.conn_no}:' logging.debug(f'[{self.node_id}:{self.conn_no}:'
f'{type(self).__name__}]' f'{type(self).__name__}]'
f' act:{round(1000*elapsed)}ms' f' act:{round(1000*elapsed)}ms'
@@ -203,54 +272,47 @@ class AsyncStream():
data = await self._reader.read(4096) data = await self._reader.read(4096)
if data: if data:
self.proc_start = time.time() self.proc_start = time.time()
self.ifc.read += data self.rx_fifo += data
wait = self.ifc.read() # call read in parent class wait = self.rx_fifo() # call read in parent class
if wait > 0: if wait > 0:
await asyncio.sleep(wait) await asyncio.sleep(wait)
else: else:
raise RuntimeError("Peer closed.") raise RuntimeError("Peer closed.")
async def __async_write(self, headline: str = 'Transmit to ') -> None:
"""Async write handler to transmit the send_buffer"""
if len(self.tx_fifo) > 0:
self.tx_fifo.logging(logging.INFO, f'{headline}{self.addr}:')
self._writer.write(self.tx_fifo.get())
await self._writer.drain()
async def __async_forward(self) -> None: async def __async_forward(self) -> None:
"""forward handler transmits data over the remote connection""" """forward handler transmits data over the remote connection"""
if not self._forward_buffer: if len(self.fwd_fifo) == 0:
return return
try: try:
if not self.remote_stream: await self._async_forward()
await self.async_create_remote()
if self.remote_stream:
if self.remote_stream._init_new_client_conn():
await self.remote_stream.async_write()
if self.remote_stream:
self.remote_stream._update_header(self._forward_buffer)
hex_dump_memory(logging.INFO,
f'Forward to {self.remote_stream.addr}:',
self._forward_buffer,
len(self._forward_buffer))
self.remote_stream._writer.write(self._forward_buffer)
await self.remote_stream._writer.drain()
self._forward_buffer = bytearray(0)
except OSError as error: except OSError as error:
if self.remote_stream: if self.remote.stream:
rmt = self.remote_stream rmt = self.remote.stream
self.remote_stream = None self.remote.stream = None
logger.error(f'[{rmt.node_id}:{rmt.conn_no}] Fwd: {error} for ' logger.error(f'[{rmt.node_id}:{rmt.conn_no}] Fwd: {error} for '
f'l{rmt.l_addr} | r{rmt.r_addr}') f'l{rmt._ifc.l_addr} | r{rmt._ifc.r_addr}')
await rmt.disc() await rmt._ifc.disc()
rmt.close() rmt._ifc.close()
except RuntimeError as error: except RuntimeError as error:
if self.remote_stream: if self.remote.stream:
rmt = self.remote_stream rmt = self.remote.stream
self.remote_stream = None self.remote.stream = None
logger.info(f'[{rmt.node_id}:{rmt.conn_no}] ' logger.info(f'[{rmt.node_id}:{rmt.conn_no}] '
f'Fwd: {error} for {rmt.l_addr}') f'Fwd: {error} for {rmt._ifc.l_addr}')
await rmt.disc() await rmt._ifc.disc()
rmt.close() rmt._ifc.close()
except Exception: except Exception:
self.inc_counter('SW_Exception') Infos.inc_counter('SW_Exception')
logger.error( logger.error(
f"Fwd Exception for {self.addr}:\n" f"Fwd Exception for {self.addr}:\n"
f"{traceback.format_exc()}") f"{traceback.format_exc()}")
@@ -258,3 +320,103 @@ class AsyncStream():
def __del__(self): def __del__(self):
logger.debug( logger.debug(
f"AsyncStream.__del__ l{self.l_addr} | r{self.r_addr}") f"AsyncStream.__del__ l{self.l_addr} | r{self.r_addr}")
class AsyncStreamServer(AsyncStream):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, async_publ_mqtt, async_create_remote,
rstream: "StreamPtr") -> None:
AsyncStream.__init__(self, reader, writer, addr,
rstream)
self.async_create_remote = async_create_remote
self.async_publ_mqtt = async_publ_mqtt
async def server_loop(self, addr: str) -> None:
'''Loop for receiving messages from the inverter (server-side)'''
logger.info(f'[{self.node_id}:{self.conn_no}] '
f'Accept connection from {addr}')
Infos.inc_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt()
await self.loop()
Infos.dec_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt()
logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for'
f' r{self.r_addr}')
# if the server connection closes, we also have to disconnect
# the connection to te TSUN cloud
if 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}]')
await self.remote.ifc.disc()
async def _async_forward(self) -> None:
"""forward handler transmits data over the remote connection"""
if not self.remote.stream:
await self.async_create_remote()
if self.remote.stream and \
self.remote.ifc.init_new_client_conn_cb():
await self.remote.ifc._AsyncStream__async_write()
if self.remote.stream:
self.remote.ifc.update_header_cb(self.fwd_fifo.peek())
self.fwd_fifo.logging(logging.INFO, 'Forward to '
f'{self.remote.ifc.addr}:')
self.remote.ifc._writer.write(self.fwd_fifo.get())
await self.remote.ifc._writer.drain()
async def publish_outstanding_mqtt(self):
'''Publish all outstanding MQTT topics'''
try:
await self.async_publ_mqtt()
await Inverter._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,
addr, rstream: "StreamPtr") -> None:
AsyncStream.__init__(self, reader, writer, addr,
rstream)
async def client_loop(self, _: str) -> None:
'''Loop for receiving messages from the TSUN cloud (client-side)'''
await self.loop()
logger.info(f'[{self.node_id}:{self.conn_no}] '
'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
async def _async_forward(self) -> None:
"""forward handler transmits data over the remote connection"""
if self.remote.stream:
self.remote.ifc.update_header_cb(self.fwd_fifo.peek())
self.fwd_fifo.logging(logging.INFO, 'Forward to '
f'{self.remote.ifc.addr}:')
self.remote.ifc._writer.write(self.fwd_fifo.get())
await self.remote.ifc._writer.drain()

View File

@@ -2,36 +2,18 @@ import logging
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3.connection_g3": if __name__ == "app.src.gen3.connection_g3":
from app.src.async_ifc import AsyncIfc from app.src.async_stream import AsyncStreamServer
from app.src.async_stream import AsyncStream from app.src.async_stream import AsyncStreamClient, StreamPtr
from app.src.gen3.talent import Talent from app.src.gen3.talent import Talent
else: # pragma: no cover else: # pragma: no cover
from async_ifc import AsyncIfc from async_stream import AsyncStreamServer
from async_stream import AsyncStream from async_stream import AsyncStreamClient, StreamPtr
from gen3.talent import Talent from gen3.talent import Talent
logger = logging.getLogger('conn') logger = logging.getLogger('conn')
class ConnectionG3(AsyncStream, Talent): class ConnectionG3(Talent):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, remote_stream: 'ConnectionG3', server_side: bool,
id_str=b'') -> None:
self._ifc = AsyncIfc()
AsyncStream.__init__(self, reader, writer, addr, self._ifc)
Talent.__init__(self, server_side, self._ifc, id_str)
self.remote_stream: 'ConnectionG3' = remote_stream
'''
Our puplic methods
'''
def close(self):
AsyncStream.close(self)
Talent.close(self)
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
pass # virtual interface # pragma: no cover pass # virtual interface # pragma: no cover
@@ -40,10 +22,40 @@ class ConnectionG3(AsyncStream, Talent):
def healthy(self) -> bool: def healthy(self) -> bool:
logger.debug('ConnectionG3 healthy()') logger.debug('ConnectionG3 healthy()')
return AsyncStream.healthy(self) return self._ifc.healthy()
''' def close(self):
Our private methods self._ifc.close()
''' Talent.close(self)
def __del__(self): # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
super().__del__()
class ConnectionG3Server(ConnectionG3):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, rstream: 'ConnectionG3Client',
id_str=b'') -> None:
server_side = True
self.remote = StreamPtr(rstream)
self._ifc = AsyncStreamServer(reader, writer, addr,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
self.conn_no = self._ifc.get_conn_no()
self.addr = addr
Talent.__init__(self, server_side, self._ifc, id_str)
class ConnectionG3Client(ConnectionG3):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, rstream: 'ConnectionG3Server',
id_str=b'') -> None:
server_side = False
self.remote = StreamPtr(rstream)
self._ifc = AsyncStreamClient(reader, writer, addr,
self.remote)
self.conn_no = self._ifc.get_conn_no()
self.addr = addr
Talent.__init__(self, server_side, self._ifc, id_str)

View File

@@ -8,19 +8,21 @@ from aiomqtt import MqttCodeError
if __name__ == "app.src.gen3.inverter_g3": if __name__ == "app.src.gen3.inverter_g3":
from app.src.config import Config from app.src.config import Config
from app.src.inverter import Inverter from app.src.inverter import Inverter
from app.src.gen3.connection_g3 import ConnectionG3 from app.src.gen3.connection_g3 import ConnectionG3Server
from app.src.gen3.connection_g3 import ConnectionG3Client
from app.src.infos import Infos from app.src.infos import Infos
else: # pragma: no cover else: # pragma: no cover
from config import Config from config import Config
from inverter import Inverter from inverter import Inverter
from gen3.connection_g3 import ConnectionG3 from gen3.connection_g3 import ConnectionG3Server
from gen3.connection_g3 import ConnectionG3Client
from infos import Infos from infos import Infos
logger_mqtt = logging.getLogger('mqtt') logger_mqtt = logging.getLogger('mqtt')
class InverterG3(Inverter, ConnectionG3): class InverterG3(Inverter, ConnectionG3Server):
'''class Inverter is a derivation of an Async_Stream '''class Inverter is a derivation of an Async_Stream
The class has some class method for managing common resources like a The class has some class method for managing common resources like a
@@ -51,8 +53,9 @@ class InverterG3(Inverter, ConnectionG3):
''' '''
def __init__(self, reader: StreamReader, writer: StreamWriter, addr): def __init__(self, reader: StreamReader, writer: StreamWriter, addr):
super().__init__(reader, writer, addr, None, True) super().__init__(reader, writer, addr, None)
self.__ha_restarts = -1 self.__ha_restarts = -1
self.addr = addr
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
'''Establish a client connection to the TSUN cloud''' '''Establish a client connection to the TSUN cloud'''
@@ -65,12 +68,12 @@ class InverterG3(Inverter, ConnectionG3):
logging.info(f'[{self.node_id}] Connect to {addr}') logging.info(f'[{self.node_id}] Connect to {addr}')
connect = asyncio.open_connection(host, port) connect = asyncio.open_connection(host, port)
reader, writer = await connect reader, writer = await connect
self.remote_stream = ConnectionG3(reader, writer, addr, self, self.remote.stream = ConnectionG3Client(reader, writer, addr, self,
False, self.id_str) self.id_str)
logging.info(f'[{self.remote_stream.node_id}:' logging.info(f'[{self.remote.stream.node_id}:'
f'{self.remote_stream.conn_no}] ' f'{self.remote.stream.conn_no}] '
f'Connected to {addr}') f'Connected to {addr}')
asyncio.create_task(self.client_loop(addr)) asyncio.create_task(self.remote.ifc.client_loop(addr))
except (ConnectionRefusedError, TimeoutError) as error: except (ConnectionRefusedError, TimeoutError) as error:
logging.info(f'{error}') logging.info(f'{error}')
@@ -82,6 +85,8 @@ class InverterG3(Inverter, ConnectionG3):
async def async_publ_mqtt(self) -> None: async def async_publ_mqtt(self) -> None:
'''publish data to MQTT broker''' '''publish data to MQTT broker'''
if not self.unique_id:
return
# check if new inverter or collector infos are available or when the # check if new inverter or collector infos are available or when the
# home assistant has changed the status back to online # home assistant has changed the status back to online
try: try:
@@ -96,7 +101,7 @@ class InverterG3(Inverter, ConnectionG3):
for key in self.new_data: for key in self.new_data:
await self.__async_publ_mqtt_packet(key) await self.__async_publ_mqtt_packet(key)
for key in Infos.new_stat_data: for key in Infos.new_stat_data:
await self._async_publ_mqtt_proxy_stat(key) await Inverter._async_publ_mqtt_proxy_stat(key)
except MqttCodeError as error: except MqttCodeError as error:
logging.error(f'Mqtt except: {error}') logging.error(f'Mqtt except: {error}')
@@ -128,10 +133,6 @@ class InverterG3(Inverter, ConnectionG3):
self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}') self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}')
def close(self) -> None: def close(self) -> None:
logging.debug(f'InverterG3.close() l{self.l_addr} | r{self.r_addr}') logging.debug(f'InverterG3.close() {self.addr}')
super().close() # call close handler in the parent class super().close() # call close handler in the parent class
# logging.info(f'Inverter refs: {gc.get_referrers(self)}') # logging.info(f'Inverter refs: {gc.get_referrers(self)}')
def __del__(self):
logging.debug("InverterG3.__del__")
super().__del__()

View File

@@ -6,7 +6,7 @@ from tzlocal import get_localzone
if __name__ == "app.src.gen3.talent": if __name__ == "app.src.gen3.talent":
from app.src.async_ifc import AsyncIfc from app.src.async_ifc import AsyncIfc
from app.src.messages import hex_dump_memory, Message, State from app.src.messages import Message, State
from app.src.modbus import Modbus from app.src.modbus import Modbus
from app.src.my_timer import Timer from app.src.my_timer import Timer
from app.src.config import Config from app.src.config import Config
@@ -14,7 +14,7 @@ if __name__ == "app.src.gen3.talent":
from app.src.infos import Register from app.src.infos import Register
else: # pragma: no cover else: # pragma: no cover
from async_ifc import AsyncIfc from async_ifc import AsyncIfc
from messages import hex_dump_memory, Message, State from messages import Message, State
from modbus import Modbus from modbus import Modbus
from my_timer import Timer from my_timer import Timer
from config import Config from config import Config
@@ -48,7 +48,10 @@ class Talent(Message):
def __init__(self, server_side: bool, ifc: "AsyncIfc", id_str=b''): def __init__(self, server_side: bool, ifc: "AsyncIfc", id_str=b''):
super().__init__(server_side, self.send_modbus_cb, mb_timeout=15) super().__init__(server_side, self.send_modbus_cb, mb_timeout=15)
ifc.read.reg_trigger(self.read) ifc.rx_set_cb(self.read)
ifc.prot_set_timeout_cb(self._timeout)
ifc.prot_set_init_new_client_conn_cb(self._init_new_client_conn)
ifc.prot_set_update_header_cb(self._update_header)
self.ifc = ifc self.ifc = ifc
self.await_conn_resp_cnt = 0 self.await_conn_resp_cnt = 0
self.id_str = id_str self.id_str = id_str
@@ -107,7 +110,10 @@ class Talent(Message):
self.log_lvl.clear() self.log_lvl.clear()
self.state = State.closed self.state = State.closed
self.mb_timer.close() self.mb_timer.close()
self.ifc.read.reg_trigger(None) 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)
super().close() super().close()
def __set_serial_no(self, serial_no: str): def __set_serial_no(self, serial_no: str):
@@ -143,10 +149,10 @@ class Talent(Message):
self._read() self._read()
while True: while True:
if not self.header_valid: if not self.header_valid:
self.__parse_header(self.ifc.read.peek(), len(self.ifc.read)) self.__parse_header(self.ifc.rx_peek(), self.ifc.rx_len())
if self.header_valid and \ if self.header_valid and \
len(self.ifc.read) >= (self.header_len + self.data_len): self.ifc.rx_len() >= (self.header_len + self.data_len):
if self.state == State.init: if self.state == State.init:
self.state = State.received # received 1st package self.state = State.received # received 1st package
@@ -154,8 +160,8 @@ class Talent(Message):
if callable(log_lvl): if callable(log_lvl):
log_lvl = log_lvl() log_lvl = log_lvl()
self.ifc.read.logging(log_lvl, f'Received from {self.addr}:' self.ifc.rx_log(log_lvl, f'Received from {self.addr}:'
f' BufLen: {len(self.ifc.read)}' f' BufLen: {self.ifc.rx_len()}'
f' HdrLen: {self.header_len}' f' HdrLen: {self.header_len}'
f' DtaLen: {self.data_len}') f' DtaLen: {self.data_len}')
@@ -170,10 +176,9 @@ class Talent(Message):
tsun = Config.get('tsun') tsun = Config.get('tsun')
if tsun['enabled']: if tsun['enabled']:
buflen = self.header_len+self.data_len buflen = self.header_len+self.data_len
buffer = self.ifc.read.peek(buflen) buffer = self.ifc.rx_peek(buflen)
self._forward_buffer += buffer self.ifc.fwd_add(buffer)
hex_dump_memory(logging.DEBUG, 'Store for forwarding:', self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
buffer, buflen)
fnc = self.switch.get(self.msg_id, self.msg_unknown) fnc = self.switch.get(self.msg_id, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'forwrd') + logger.info(self.__flow_str(self.server_side, 'forwrd') +
@@ -182,20 +187,18 @@ class Talent(Message):
def forward_snd(self) -> None: def forward_snd(self) -> None:
'''add the build send msg to the forwarding queue''' '''add the build send msg to the forwarding queue'''
tsun = Config.get('tsun') tsun = Config.get('tsun')
rest = self.ifc.write.get(self.send_msg_ofs) rest = self.ifc.tx_get(self.send_msg_ofs)
buffer = self.ifc.write.get(len(self.ifc.write)) buffer = self.ifc.tx_get()
if tsun['enabled']: if tsun['enabled']:
_len = len(buffer) _len = len(buffer)
struct.pack_into('!l', buffer, 0, _len-4) struct.pack_into('!l', buffer, 0, _len-4)
buflen = _len self.ifc.fwd_add(buffer)
self._forward_buffer += buffer self.ifc.fwd_log(logging.INFO, 'Store for forwarding:')
hex_dump_memory(logging.INFO, 'Store for forwarding:',
buffer, buflen)
fnc = self.switch.get(self.msg_id, self.msg_unknown) fnc = self.switch.get(self.msg_id, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'forwrd') + logger.info(self.__flow_str(self.server_side, 'forwrd') +
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}') f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
self.ifc.write += rest self.ifc.tx_add(rest)
def send_modbus_cb(self, modbus_pdu: bytearray, log_lvl: int, state: str): def send_modbus_cb(self, modbus_pdu: bytearray, log_lvl: int, state: str):
if self.state != State.up: if self.state != State.up:
@@ -204,13 +207,13 @@ class Talent(Message):
return return
self.__build_header(0x70, 0x77) self.__build_header(0x70, 0x77)
self.ifc.write += b'\x00\x01\xa3\x28' # magic ? self.ifc.tx_add(b'\x00\x01\xa3\x28') # magic ?
self.ifc.write += struct.pack('!B', len(modbus_pdu)) self.ifc.tx_add(struct.pack('!B', len(modbus_pdu)))
self.ifc.write += modbus_pdu self.ifc.tx_add(modbus_pdu)
self.__finish_send_msg() self.__finish_send_msg()
self.ifc.write.logging(log_lvl, f'Send Modbus {state}:{self.addr}:') self.ifc.tx_log(log_lvl, f'Send Modbus {state}:{self.addr}:')
self.ifc.write() self.ifc.tx_flush()
def _send_modbus_cmd(self, func, addr, val, log_lvl) -> None: def _send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
if self.state != State.up: if self.state != State.up:
@@ -238,9 +241,9 @@ class Talent(Message):
self.msg_id = 0 self.msg_id = 0
self.await_conn_resp_cnt += 1 self.await_conn_resp_cnt += 1
self.__build_header(0x91) self.__build_header(0x91)
self.ifc.write += struct.pack(f'!{len(contact_name)+1}p' self.ifc.tx_add(struct.pack(f'!{len(contact_name)+1}p'
f'{len(contact_mail)+1}p', f'{len(contact_mail)+1}p',
contact_name, contact_mail) contact_name, contact_mail))
self.__finish_send_msg() self.__finish_send_msg()
return True return True
@@ -324,7 +327,7 @@ class Talent(Message):
self.inc_counter('Invalid_Msg_Format') self.inc_counter('Invalid_Msg_Format')
# erase broken recv buffer # erase broken recv buffer
self.ifc.read.clear() self.ifc.rx_clear()
return return
hdr_len = 5+id_len+2 hdr_len = 5+id_len+2
@@ -345,16 +348,16 @@ class Talent(Message):
def __build_header(self, ctrl, msg_id=None) -> None: def __build_header(self, ctrl, msg_id=None) -> None:
if not msg_id: if not msg_id:
msg_id = self.msg_id msg_id = self.msg_id
self.send_msg_ofs = len(self.ifc.write) self.send_msg_ofs = self.ifc.tx_len()
self.ifc.write += struct.pack(f'!l{len(self.id_str)+1}pBB', self.ifc.tx_add(struct.pack(f'!l{len(self.id_str)+1}pBB',
0, self.id_str, ctrl, msg_id) 0, self.id_str, ctrl, msg_id))
fnc = self.switch.get(msg_id, self.msg_unknown) fnc = self.switch.get(msg_id, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'tx') + logger.info(self.__flow_str(self.server_side, 'tx') +
f' Ctl: {int(ctrl):#02x} Msg: {fnc.__name__!r}') f' Ctl: {int(ctrl):#02x} Msg: {fnc.__name__!r}')
def __finish_send_msg(self) -> None: def __finish_send_msg(self) -> None:
_len = len(self.ifc.write) - self.send_msg_ofs _len = self.ifc.tx_len() - self.send_msg_ofs
struct.pack_into('!l', self.ifc.write.peek(), self.send_msg_ofs, struct.pack_into('!l', self.ifc.tx_peek(), self.send_msg_ofs,
_len-4) _len-4)
def __dispatch_msg(self) -> None: def __dispatch_msg(self) -> None:
@@ -369,7 +372,7 @@ class Talent(Message):
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}') f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
def __flush_recv_msg(self) -> None: def __flush_recv_msg(self) -> None:
self.ifc.read.get(self.header_len+self.data_len) self.ifc.rx_get(self.header_len+self.data_len)
self.header_valid = False self.header_valid = False
''' '''
@@ -379,7 +382,7 @@ class Talent(Message):
if self.ctrl.is_ind(): if self.ctrl.is_ind():
if self.server_side and self.__process_contact_info(): if self.server_side and self.__process_contact_info():
self.__build_header(0x91) self.__build_header(0x91)
self.ifc.write += b'\x01' self.ifc.tx_add(b'\x01')
self.__finish_send_msg() self.__finish_send_msg()
# don't forward this contact info here, we will build one # don't forward this contact info here, we will build one
# when the remote connection is established # when the remote connection is established
@@ -393,7 +396,7 @@ class Talent(Message):
self.forward() self.forward()
def __process_contact_info(self) -> bool: def __process_contact_info(self) -> bool:
buf = self.ifc.read.peek() buf = self.ifc.rx_peek()
result = struct.unpack_from('!B', buf, self.header_len) result = struct.unpack_from('!B', buf, self.header_len)
name_len = result[0] name_len = result[0]
if self.data_len == 1: # this is a response withone status byte if self.data_len == 1: # this is a response withone status byte
@@ -420,16 +423,16 @@ class Talent(Message):
ts = self._timestamp() ts = self._timestamp()
logger.debug(f'time: {ts:08x}') logger.debug(f'time: {ts:08x}')
self.__build_header(0x91) self.__build_header(0x91)
self.ifc.write += struct.pack('!q', ts) self.ifc.tx_add(struct.pack('!q', ts))
self.__finish_send_msg() self.__finish_send_msg()
elif self.data_len >= 8: elif self.data_len >= 8:
ts = self._timestamp() ts = self._timestamp()
result = struct.unpack_from('!q', self.ifc.read.peek(), result = struct.unpack_from('!q', self.ifc.rx_peek(),
self.header_len) self.header_len)
self.ts_offset = result[0]-ts self.ts_offset = result[0]-ts
if self.remote_stream: if self.remote.stream:
self.remote_stream.ts_offset = self.ts_offset self.remote.stream.ts_offset = self.ts_offset
logger.debug(f'tsun-time: {int(result[0]):08x}' logger.debug(f'tsun-time: {int(result[0]):08x}'
f' proxy-time: {ts:08x}' f' proxy-time: {ts:08x}'
f' offset: {self.ts_offset}') f' offset: {self.ts_offset}')
@@ -449,10 +452,10 @@ class Talent(Message):
self.db.set_db_def_value(Register.POLLING_INTERVAL, self.db.set_db_def_value(Register.POLLING_INTERVAL,
self.mb_timeout) self.mb_timeout)
self.__build_header(0x99) self.__build_header(0x99)
self.ifc.write += b'\x02' self.ifc.tx_add(b'\x02')
self.__finish_send_msg() self.__finish_send_msg()
result = struct.unpack_from('!Bq', self.ifc.read.peek(), result = struct.unpack_from('!Bq', self.ifc.rx_peek(),
self.header_len) self.header_len)
resp_code = result[0] resp_code = result[0]
ts = result[1]+self.ts_offset ts = result[1]+self.ts_offset
@@ -460,11 +463,11 @@ class Talent(Message):
f' tsun-time: {ts:08x}' f' tsun-time: {ts:08x}'
f' offset: {self.ts_offset}') f' offset: {self.ts_offset}')
self.__build_header(0x91) self.__build_header(0x91)
self.ifc.write += struct.pack('!Bq', resp_code, ts) self.ifc.tx_add(struct.pack('!Bq', resp_code, ts))
self.forward_snd() self.forward_snd()
return return
elif self.ctrl.is_resp(): elif self.ctrl.is_resp():
result = struct.unpack_from('!B', self.ifc.read.peek(), result = struct.unpack_from('!B', self.ifc.rx_peek(),
self.header_len) self.header_len)
resp_code = result[0] resp_code = result[0]
logging.debug(f'TimeActRespCode: {resp_code}') logging.debug(f'TimeActRespCode: {resp_code}')
@@ -476,7 +479,7 @@ class Talent(Message):
self.forward() self.forward()
def parse_msg_header(self): def parse_msg_header(self):
result = struct.unpack_from('!lB', self.ifc.read.peek(), result = struct.unpack_from('!lB', self.ifc.rx_peek(),
self.header_len) self.header_len)
data_id = result[0] # len of complete message data_id = result[0] # len of complete message
@@ -485,7 +488,7 @@ class Talent(Message):
msg_hdr_len = 5+id_len+9 msg_hdr_len = 5+id_len+9
result = struct.unpack_from(f'!{id_len+1}pBq', self.ifc.read.peek(), result = struct.unpack_from(f'!{id_len+1}pBq', self.ifc.rx_peek(),
self.header_len + 4) self.header_len + 4)
timestamp = result[2] timestamp = result[2]
@@ -498,7 +501,7 @@ class Talent(Message):
def msg_collector_data(self): def msg_collector_data(self):
if self.ctrl.is_ind(): if self.ctrl.is_ind():
self.__build_header(0x99) self.__build_header(0x99)
self.ifc.write += b'\x01' self.ifc.tx_add(b'\x01')
self.__finish_send_msg() self.__finish_send_msg()
self.__process_data() self.__process_data()
@@ -513,7 +516,7 @@ class Talent(Message):
def msg_inverter_data(self): def msg_inverter_data(self):
if self.ctrl.is_ind(): if self.ctrl.is_ind():
self.__build_header(0x99) self.__build_header(0x99)
self.ifc.write += b'\x01' self.ifc.tx_add(b'\x01')
self.__finish_send_msg() self.__finish_send_msg()
self.__process_data() self.__process_data()
self.state = State.up # allow MODBUS cmds self.state = State.up # allow MODBUS cmds
@@ -533,7 +536,7 @@ class Talent(Message):
def __process_data(self): def __process_data(self):
msg_hdr_len, ts = self.parse_msg_header() msg_hdr_len, ts = self.parse_msg_header()
for key, update in self.db.parse(self.ifc.read.peek(), self.header_len for key, update in self.db.parse(self.ifc.rx_peek(), self.header_len
+ msg_hdr_len, self.node_id): + msg_hdr_len, self.node_id):
if update: if update:
self._set_mqtt_timestamp(key, self._utcfromts(ts)) self._set_mqtt_timestamp(key, self._utcfromts(ts))
@@ -553,7 +556,7 @@ class Talent(Message):
msg_hdr_len = 5 msg_hdr_len = 5
result = struct.unpack_from('!lBB', self.ifc.read.peek(), result = struct.unpack_from('!lBB', self.ifc.rx_peek(),
self.header_len) self.header_len)
modbus_len = result[1] modbus_len = result[1]
return msg_hdr_len, modbus_len return msg_hdr_len, modbus_len
@@ -562,7 +565,7 @@ class Talent(Message):
msg_hdr_len = 6 msg_hdr_len = 6
result = struct.unpack_from('!lBBB', self.ifc.read.peek(), result = struct.unpack_from('!lBBB', self.ifc.rx_peek(),
self.header_len) self.header_len)
modbus_len = result[2] modbus_len = result[2]
return msg_hdr_len, modbus_len return msg_hdr_len, modbus_len
@@ -583,12 +586,12 @@ class Talent(Message):
self.__msg_modbus(hdr_len) self.__msg_modbus(hdr_len)
def __msg_modbus(self, hdr_len): def __msg_modbus(self, hdr_len):
data = self.ifc.read.peek()[self.header_len: data = self.ifc.rx_peek()[self.header_len:
self.header_len+self.data_len] self.header_len+self.data_len]
if self.ctrl.is_req(): if self.ctrl.is_req():
if self.remote_stream.mb.recv_req(data[hdr_len:], if self.remote.stream.mb.recv_req(data[hdr_len:],
self.remote_stream. self.remote.stream.
msg_forward): msg_forward):
self.inc_counter('Modbus_Command') self.inc_counter('Modbus_Command')
else: else:

View File

@@ -2,37 +2,18 @@ import logging
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3plus.connection_g3p": if __name__ == "app.src.gen3plus.connection_g3p":
from app.src.async_ifc import AsyncIfc from app.src.async_stream import AsyncStreamServer
from app.src.async_stream import AsyncStream from app.src.async_stream import AsyncStreamClient, StreamPtr
from app.src.gen3plus.solarman_v5 import SolarmanV5 from app.src.gen3plus.solarman_v5 import SolarmanV5
else: # pragma: no cover else: # pragma: no cover
from async_ifc import AsyncIfc from async_stream import AsyncStreamServer
from async_stream import AsyncStream from async_stream import AsyncStreamClient, StreamPtr
from gen3plus.solarman_v5 import SolarmanV5 from gen3plus.solarman_v5 import SolarmanV5
logger = logging.getLogger('conn') logger = logging.getLogger('conn')
class ConnectionG3P(AsyncStream, SolarmanV5): class ConnectionG3P(SolarmanV5):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, remote_stream: 'ConnectionG3P',
server_side: bool,
client_mode: bool) -> None:
self._ifc = AsyncIfc()
AsyncStream.__init__(self, reader, writer, addr, self._ifc)
SolarmanV5.__init__(self, server_side, client_mode, self._ifc)
self.remote_stream: 'ConnectionG3P' = remote_stream
'''
Our puplic methods
'''
def close(self):
AsyncStream.close(self)
SolarmanV5.close(self)
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
pass # virtual interface # pragma: no cover pass # virtual interface # pragma: no cover
@@ -41,10 +22,40 @@ class ConnectionG3P(AsyncStream, SolarmanV5):
def healthy(self) -> bool: def healthy(self) -> bool:
logger.debug('ConnectionG3P healthy()') logger.debug('ConnectionG3P healthy()')
return AsyncStream.healthy(self) return self._ifc.healthy()
''' def close(self):
Our private methods self._ifc.close()
''' SolarmanV5.close(self)
def __del__(self): # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
super().__del__()
class ConnectionG3PServer(ConnectionG3P):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, rstream: 'ConnectionG3PClient',
client_mode: bool) -> None:
server_side = True
self.remote = StreamPtr(rstream)
self._ifc = AsyncStreamServer(reader, writer, addr,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
self.conn_no = self._ifc.get_conn_no()
self.addr = addr
SolarmanV5.__init__(self, server_side, client_mode, self._ifc)
class ConnectionG3PClient(ConnectionG3P):
def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, rstream: 'ConnectionG3PServer') -> None:
server_side = False
client_mode = False
self.remote = StreamPtr(rstream)
self._ifc = AsyncStreamClient(reader, writer, addr, self.remote)
self.conn_no = self._ifc.get_conn_no()
self.addr = addr
SolarmanV5.__init__(self, server_side, client_mode, self._ifc)

View File

@@ -8,19 +8,21 @@ from aiomqtt import MqttCodeError
if __name__ == "app.src.gen3plus.inverter_g3p": if __name__ == "app.src.gen3plus.inverter_g3p":
from app.src.config import Config from app.src.config import Config
from app.src.inverter import Inverter from app.src.inverter import Inverter
from app.src.gen3plus.connection_g3p import ConnectionG3P from app.src.gen3plus.connection_g3p import ConnectionG3PServer
from app.src.gen3plus.connection_g3p import ConnectionG3PClient
from app.src.infos import Infos from app.src.infos import Infos
else: # pragma: no cover else: # pragma: no cover
from config import Config from config import Config
from inverter import Inverter from inverter import Inverter
from gen3plus.connection_g3p import ConnectionG3P from gen3plus.connection_g3p import ConnectionG3PServer
from gen3plus.connection_g3p import ConnectionG3PClient
from infos import Infos from infos import Infos
logger_mqtt = logging.getLogger('mqtt') logger_mqtt = logging.getLogger('mqtt')
class InverterG3P(Inverter, ConnectionG3P): class InverterG3P(Inverter, ConnectionG3PServer):
'''class Inverter is a derivation of an Async_Stream '''class Inverter is a derivation of an Async_Stream
The class has some class method for managing common resources like a The class has some class method for managing common resources like a
@@ -53,8 +55,9 @@ class InverterG3P(Inverter, ConnectionG3P):
def __init__(self, reader: StreamReader, writer: StreamWriter, addr, def __init__(self, reader: StreamReader, writer: StreamWriter, addr,
client_mode: bool = False): client_mode: bool = False):
super().__init__(reader, writer, addr, None, super().__init__(reader, writer, addr, None,
server_side=True, client_mode=client_mode) client_mode=client_mode)
self.__ha_restarts = -1 self.__ha_restarts = -1
self.addr = addr
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
'''Establish a client connection to the TSUN cloud''' '''Establish a client connection to the TSUN cloud'''
@@ -67,13 +70,12 @@ class InverterG3P(Inverter, ConnectionG3P):
logging.info(f'[{self.node_id}] Connect to {addr}') logging.info(f'[{self.node_id}] Connect to {addr}')
connect = asyncio.open_connection(host, port) connect = asyncio.open_connection(host, port)
reader, writer = await connect reader, writer = await connect
self.remote_stream = ConnectionG3P(reader, writer, addr, self, self.remote.stream = ConnectionG3PClient(reader, writer,
server_side=False, addr, self)
client_mode=False) logging.info(f'[{self.remote.stream.node_id}:'
logging.info(f'[{self.remote_stream.node_id}:' f'{self.remote.stream.conn_no}] '
f'{self.remote_stream.conn_no}] '
f'Connected to {addr}') f'Connected to {addr}')
asyncio.create_task(self.client_loop(addr)) asyncio.create_task(self.remote.ifc.client_loop(addr))
except (ConnectionRefusedError, TimeoutError) as error: except (ConnectionRefusedError, TimeoutError) as error:
logging.info(f'{error}') logging.info(f'{error}')
@@ -85,6 +87,9 @@ class InverterG3P(Inverter, ConnectionG3P):
async def async_publ_mqtt(self) -> None: async def async_publ_mqtt(self) -> None:
'''publish data to MQTT broker''' '''publish data to MQTT broker'''
if not self.unique_id:
return
# check if new inverter or collector infos are available or when the # check if new inverter or collector infos are available or when the
# home assistant has changed the status back to online # home assistant has changed the status back to online
try: try:
@@ -99,7 +104,7 @@ class InverterG3P(Inverter, ConnectionG3P):
for key in self.new_data: for key in self.new_data:
await self.__async_publ_mqtt_packet(key) await self.__async_publ_mqtt_packet(key)
for key in Infos.new_stat_data: for key in Infos.new_stat_data:
await self._async_publ_mqtt_proxy_stat(key) await Inverter._async_publ_mqtt_proxy_stat(key)
except MqttCodeError as error: except MqttCodeError as error:
logging.error(f'Mqtt except: {error}') logging.error(f'Mqtt except: {error}')
@@ -131,10 +136,6 @@ class InverterG3P(Inverter, ConnectionG3P):
self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}') self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}')
def close(self) -> None: def close(self) -> None:
logging.debug(f'InverterG3P.close() l{self.l_addr} | r{self.r_addr}') logging.debug(f'InverterG3P.close() {self.addr}')
super().close() # call close handler in the parent class super().close() # call close handler in the parent class
# logger.debug (f'Inverter refs: {gc.get_referrers(self)}') # logger.debug (f'Inverter refs: {gc.get_referrers(self)}')
def __del__(self):
logging.debug("InverterG3P.__del__")
super().__del__()

View File

@@ -64,7 +64,11 @@ class SolarmanV5(Message):
def __init__(self, server_side: bool, client_mode: bool, ifc: "AsyncIfc"): def __init__(self, server_side: bool, client_mode: bool, ifc: "AsyncIfc"):
super().__init__(server_side, self.send_modbus_cb, mb_timeout=8) super().__init__(server_side, self.send_modbus_cb, mb_timeout=8)
ifc.read.reg_trigger(self.read) ifc.rx_set_cb(self.read)
ifc.prot_set_timeout_cb(self._timeout)
ifc.prot_set_init_new_client_conn_cb(self._init_new_client_conn)
ifc.prot_set_update_header_cb(self._update_header)
self.ifc = ifc self.ifc = ifc
self.header_len = 11 # overwrite construcor in class Message self.header_len = 11 # overwrite construcor in class Message
self.control = 0 self.control = 0
@@ -165,7 +169,10 @@ class SolarmanV5(Message):
self.log_lvl.clear() self.log_lvl.clear()
self.state = State.closed self.state = State.closed
self.mb_timer.close() self.mb_timer.close()
self.ifc.read.reg_trigger(None) 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)
super().close() super().close()
async def send_start_cmd(self, snr: int, host: str, async def send_start_cmd(self, snr: int, host: str,
@@ -250,10 +257,10 @@ class SolarmanV5(Message):
self._read() self._read()
while True: while True:
if not self.header_valid: if not self.header_valid:
self.__parse_header(self.ifc.read.peek(), self.__parse_header(self.ifc.rx_peek(),
len(self.ifc.read)) self.ifc.rx_len())
if self.header_valid and len(self.ifc.read) >= \ if self.header_valid and self.ifc.rx_len() >= \
(self.header_len + self.data_len+2): (self.header_len + self.data_len+2):
self.__process_complete_received_msg() self.__process_complete_received_msg()
self.__flush_recv_msg() self.__flush_recv_msg()
@@ -264,10 +271,10 @@ class SolarmanV5(Message):
log_lvl = self.log_lvl.get(self.control, logging.WARNING) log_lvl = self.log_lvl.get(self.control, logging.WARNING)
if callable(log_lvl): if callable(log_lvl):
log_lvl = log_lvl() log_lvl = log_lvl()
self.ifc.read.logging(log_lvl, f'Received from {self.addr}:') self.ifc.rx_log(log_lvl, f'Received from {self.addr}:')
# self._recv_buffer, self.header_len + # self._recv_buffer, self.header_len +
# self.data_len+2) # self.data_len+2)
if self.__trailer_is_ok(self.ifc.read.peek(), self.header_len if self.__trailer_is_ok(self.ifc.rx_peek(), self.header_len
+ self.data_len + 2): + self.data_len + 2):
if self.state == State.init: if self.state == State.init:
self.state = State.received self.state = State.received
@@ -280,9 +287,8 @@ class SolarmanV5(Message):
return return
tsun = Config.get('solarman') tsun = Config.get('solarman')
if tsun['enabled']: if tsun['enabled']:
self._forward_buffer += buffer[:buflen] self.ifc.fwd_add(buffer[:buflen])
hex_dump_memory(logging.DEBUG, 'Store for forwarding:', self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
buffer, buflen)
fnc = self.switch.get(self.control, self.msg_unknown) fnc = self.switch.get(self.control, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'forwrd') + logger.info(self.__flow_str(self.server_side, 'forwrd') +
@@ -338,7 +344,7 @@ class SolarmanV5(Message):
self.inc_counter('Invalid_Msg_Format') self.inc_counter('Invalid_Msg_Format')
# erase broken recv buffer # erase broken recv buffer
self.ifc.read.clear() self.ifc.rx_clear()
return return
self.header_valid = True self.header_valid = True
@@ -350,11 +356,11 @@ class SolarmanV5(Message):
'Drop packet w invalid stop byte from ' 'Drop packet w invalid stop byte from '
f'{self.addr}:', buf, buf_len) f'{self.addr}:', buf, buf_len)
self.inc_counter('Invalid_Msg_Format') self.inc_counter('Invalid_Msg_Format')
if len(self.ifc.read) > (self.data_len+13): if self.ifc.rx_len() > (self.data_len+13):
next_start = buf[self.data_len+13] next_start = buf[self.data_len+13]
if next_start != 0xa5: if next_start != 0xa5:
# erase broken recv buffer # erase broken recv buffer
self.ifc.read.clear() self.ifc.rx_clear()
return False return False
@@ -370,22 +376,22 @@ class SolarmanV5(Message):
def __build_header(self, ctrl) -> None: def __build_header(self, ctrl) -> None:
'''build header for new transmit message''' '''build header for new transmit message'''
self.send_msg_ofs = len(self.ifc.write) self.send_msg_ofs = self.ifc.tx_len()
self.ifc.write += struct.pack( self.ifc.tx_add(struct.pack(
'<BHHHL', 0xA5, 0, ctrl, self.seq.get_send(), self.snr) '<BHHHL', 0xA5, 0, ctrl, self.seq.get_send(), self.snr))
fnc = self.switch.get(ctrl, self.msg_unknown) fnc = self.switch.get(ctrl, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'tx') + logger.info(self.__flow_str(self.server_side, 'tx') +
f' Ctl: {int(ctrl):#04x} Msg: {fnc.__name__!r}') f' Ctl: {int(ctrl):#04x} Msg: {fnc.__name__!r}')
def __finish_send_msg(self) -> None: def __finish_send_msg(self) -> None:
'''finish the transmit message, set lenght and checksum''' '''finish the transmit message, set lenght and checksum'''
_len = len(self.ifc.write) - self.send_msg_ofs _len = self.ifc.tx_len() - self.send_msg_ofs
struct.pack_into('<H', self.ifc.write.peek(), self.send_msg_ofs+1, struct.pack_into('<H', self.ifc.tx_peek(), self.send_msg_ofs+1,
_len-11) _len-11)
check = sum(self.ifc.write.peek()[ check = sum(self.ifc.tx_peek()[
self.send_msg_ofs+1:self.send_msg_ofs + _len]) & 0xff self.send_msg_ofs+1:self.send_msg_ofs + _len]) & 0xff
self.ifc.write += struct.pack('<BB', check, 0x15) # crc & stop self.ifc.tx_add(struct.pack('<BB', check, 0x15)) # crc & stop
def _update_header(self, _forward_buffer): def _update_header(self, _forward_buffer):
'''update header for message before forwarding, '''update header for message before forwarding,
@@ -416,14 +422,14 @@ class SolarmanV5(Message):
f' Msg: {fnc.__name__!r}') f' Msg: {fnc.__name__!r}')
def __flush_recv_msg(self) -> None: def __flush_recv_msg(self) -> None:
self.ifc.read.get(self.header_len + self.data_len+2) self.ifc.rx_get(self.header_len + self.data_len+2)
self.header_valid = False self.header_valid = False
def __send_ack_rsp(self, msgtype, ftype, ack=1): def __send_ack_rsp(self, msgtype, ftype, ack=1):
self.__build_header(msgtype) self.__build_header(msgtype)
self.ifc.write += struct.pack('<BBLL', ftype, ack, self.ifc.tx_add(struct.pack('<BBLL', ftype, ack,
self._timestamp(), self._timestamp(),
self._heartbeat()) self._heartbeat()))
self.__finish_send_msg() self.__finish_send_msg()
def send_modbus_cb(self, pdu: bytearray, log_lvl: int, state: str): def send_modbus_cb(self, pdu: bytearray, log_lvl: int, state: str):
@@ -432,12 +438,12 @@ class SolarmanV5(Message):
' cause the state is not UP anymore') ' cause the state is not UP anymore')
return return
self.__build_header(0x4510) self.__build_header(0x4510)
self.ifc.write += struct.pack('<BHLLL', self.MB_RTU_CMD, self.ifc.tx_add(struct.pack('<BHLLL', self.MB_RTU_CMD,
self.sensor_list, 0, 0, 0) self.sensor_list, 0, 0, 0))
self.ifc.write += pdu self.ifc.tx_add(pdu)
self.__finish_send_msg() self.__finish_send_msg()
self.ifc.write.logging(log_lvl, f'Send Modbus {state}:{self.addr}:') self.ifc.tx_log(log_lvl, f'Send Modbus {state}:{self.addr}:')
self.ifc.write() self.ifc.tx_flush()
def _send_modbus_cmd(self, mb_no, func, addr, val, log_lvl) -> None: def _send_modbus_cmd(self, mb_no, func, addr, val, log_lvl) -> None:
if self.state != State.up: if self.state != State.up:
@@ -493,17 +499,18 @@ class SolarmanV5(Message):
self.forward_at_cmd_resp = False self.forward_at_cmd_resp = False
self.__build_header(0x4510) self.__build_header(0x4510)
self.ifc.write += struct.pack(f'<BHLLL{len(at_cmd)}sc', self.AT_CMD, self.ifc.tx_add(struct.pack(f'<BHLLL{len(at_cmd)}sc', self.AT_CMD,
0x0002, 0, 0, 0, 0x0002, 0, 0, 0,
at_cmd.encode('utf-8'), b'\r') at_cmd.encode('utf-8'), b'\r'))
self.__finish_send_msg() self.__finish_send_msg()
self.ifc.tx_log(logging.INFO, 'Send AT Command:')
try: try:
await self.async_write('Send AT Command:') self.ifc.tx_flush()
except Exception: except Exception:
self.ifc.write.clear() self.ifc.tx_clear()
def __forward_msg(self): def __forward_msg(self):
self.forward(self.ifc.read.peek(), self.header_len+self.data_len+2) self.forward(self.ifc.rx_peek(), self.header_len+self.data_len+2)
def __build_model_name(self): def __build_model_name(self):
db = self.db db = self.db
@@ -524,7 +531,7 @@ class SolarmanV5(Message):
def __process_data(self, ftype, ts): def __process_data(self, ftype, ts):
inv_update = False inv_update = False
msg_type = self.control >> 8 msg_type = self.control >> 8
for key, update in self.db.parse(self.ifc.read.peek(), msg_type, ftype, for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, ftype,
self.node_id): self.node_id):
if update: if update:
if key == 'inverter': if key == 'inverter':
@@ -543,7 +550,7 @@ class SolarmanV5(Message):
self.__forward_msg() self.__forward_msg()
def msg_dev_ind(self): def msg_dev_ind(self):
data = self.ifc.read.peek()[self.header_len:] data = self.ifc.rx_peek()[self.header_len:]
result = struct.unpack_from(self.HDR_FMT, data, 0) result = struct.unpack_from(self.HDR_FMT, data, 0)
ftype = result[0] # always 2 ftype = result[0] # always 2
total = result[1] total = result[1]
@@ -564,7 +571,7 @@ class SolarmanV5(Message):
self.__send_ack_rsp(0x1110, ftype) self.__send_ack_rsp(0x1110, ftype)
def msg_data_ind(self): def msg_data_ind(self):
data = self.ifc.read.peek() data = self.ifc.rx_peek()
result = struct.unpack_from('<BHLLLHL', data, self.header_len) result = struct.unpack_from('<BHLLLHL', data, self.header_len)
ftype = result[0] # 1 or 0x81 ftype = result[0] # 1 or 0x81
sensor = result[1] sensor = result[1]
@@ -592,7 +599,7 @@ class SolarmanV5(Message):
self.new_state_up() self.new_state_up()
def msg_sync_start(self): def msg_sync_start(self):
data = self.ifc.read.peek()[self.header_len:] data = self.ifc.rx_peek()[self.header_len:]
result = struct.unpack_from(self.HDR_FMT, data, 0) result = struct.unpack_from(self.HDR_FMT, data, 0)
ftype = result[0] ftype = result[0]
total = result[1] total = result[1]
@@ -605,7 +612,7 @@ class SolarmanV5(Message):
self.__send_ack_rsp(0x1310, ftype) self.__send_ack_rsp(0x1310, ftype)
def msg_command_req(self): def msg_command_req(self):
data = self.ifc.read.peek()[self.header_len: data = self.ifc.rx_peek()[self.header_len:
self.header_len+self.data_len] self.header_len+self.data_len]
result = struct.unpack_from('<B', data, 0) result = struct.unpack_from('<B', data, 0)
ftype = result[0] ftype = result[0]
@@ -618,8 +625,8 @@ class SolarmanV5(Message):
self.forward_at_cmd_resp = True self.forward_at_cmd_resp = True
elif ftype == self.MB_RTU_CMD: elif ftype == self.MB_RTU_CMD:
if self.remote_stream.mb.recv_req(data[15:], if self.remote.stream.mb.recv_req(data[15:],
self.remote_stream. self.remote.stream.
__forward_msg): __forward_msg):
self.inc_counter('Modbus_Command') self.inc_counter('Modbus_Command')
else: else:
@@ -634,7 +641,7 @@ class SolarmanV5(Message):
self.mqtt.publish(key, data)) self.mqtt.publish(key, data))
def get_cmd_rsp_log_lvl(self) -> int: def get_cmd_rsp_log_lvl(self) -> int:
ftype = self.ifc.read.peek()[self.header_len] ftype = self.ifc.rx_peek()[self.header_len]
if ftype == self.AT_CMD: if ftype == self.AT_CMD:
if self.forward_at_cmd_resp: if self.forward_at_cmd_resp:
return logging.INFO return logging.INFO
@@ -646,7 +653,7 @@ class SolarmanV5(Message):
return logging.WARNING return logging.WARNING
def msg_command_rsp(self): def msg_command_rsp(self):
data = self.ifc.read.peek()[self.header_len: data = self.ifc.rx_peek()[self.header_len:
self.header_len+self.data_len] self.header_len+self.data_len]
ftype = data[0] ftype = data[0]
if ftype == self.AT_CMD: if ftype == self.AT_CMD:
@@ -690,7 +697,7 @@ class SolarmanV5(Message):
self.__build_model_name() self.__build_model_name()
def msg_hbeat_ind(self): def msg_hbeat_ind(self):
data = self.ifc.read.peek()[self.header_len:] data = self.ifc.rx_peek()[self.header_len:]
result = struct.unpack_from('<B', data, 0) result = struct.unpack_from('<B', data, 0)
ftype = result[0] ftype = result[0]
@@ -699,7 +706,7 @@ class SolarmanV5(Message):
self.new_state_up() self.new_state_up()
def msg_sync_end(self): def msg_sync_end(self):
data = self.ifc.read.peek()[self.header_len:] data = self.ifc.rx_peek()[self.header_len:]
result = struct.unpack_from(self.HDR_FMT, data, 0) result = struct.unpack_from(self.HDR_FMT, data, 0)
ftype = result[0] ftype = result[0]
total = result[1] total = result[1]
@@ -712,7 +719,7 @@ class SolarmanV5(Message):
self.__send_ack_rsp(0x1810, ftype) self.__send_ack_rsp(0x1810, ftype)
def msg_response(self): def msg_response(self):
data = self.ifc.read.peek()[self.header_len:] data = self.ifc.rx_peek()[self.header_len:]
result = struct.unpack_from('<BBLL', data, 0) result = struct.unpack_from('<BBLL', data, 0)
ftype = result[0] # always 2 ftype = result[0] # always 2
valid = result[1] == 1 # status valid = result[1] == 1 # status

View File

@@ -383,12 +383,14 @@ class Infos:
'''inc proxy statistic counter''' '''inc proxy statistic counter'''
db_dict = cls.stat['proxy'] db_dict = cls.stat['proxy']
db_dict[counter] += 1 db_dict[counter] += 1
cls.new_stat_data['proxy'] = True
@classmethod @classmethod
def dec_counter(cls, counter: str) -> None: def dec_counter(cls, counter: str) -> None:
'''dec proxy statistic counter''' '''dec proxy statistic counter'''
db_dict = cls.stat['proxy'] db_dict = cls.stat['proxy']
db_dict[counter] -= 1 db_dict[counter] -= 1
cls.new_stat_data['proxy'] = True
def ha_proxy_confs(self, ha_prfx: str, node_id: str, snr: str) \ def ha_proxy_confs(self, ha_prfx: str, node_id: str, snr: str) \
-> Generator[tuple[str, str, str, str], None, None]: -> Generator[tuple[str, str, str, str], None, None]:

View File

@@ -90,6 +90,12 @@ class State(Enum):
class Message(metaclass=IterRegistry): class Message(metaclass=IterRegistry):
_registry = [] _registry = []
MAX_START_TIME = 400
'''maximum time without a received msg in sec'''
MAX_INV_IDLE_TIME = 120
'''maximum time without a received msg from the inverter in sec'''
MAX_DEF_IDLE_TIME = 360
'''maximum default time without a received msg in sec'''
def __init__(self, server_side: bool, send_modbus_cb: def __init__(self, server_side: bool, send_modbus_cb:
Callable[[bytes, int, str], None], mb_timeout: int): Callable[[bytes, int, str], None], mb_timeout: int):
@@ -105,13 +111,21 @@ class Message(metaclass=IterRegistry):
self.header_len = 0 self.header_len = 0
self.data_len = 0 self.data_len = 0
self.unique_id = 0 self.unique_id = 0
self.node_id = '' # will be overwritten in the child class's __init__ self._node_id = ''
self.sug_area = '' self.sug_area = ''
self._forward_buffer = bytearray(0)
self.new_data = {} self.new_data = {}
self.state = State.init self.state = State.init
self.shutdown_started = False self.shutdown_started = False
@property
def node_id(self):
return self._node_id
@node_id.setter
def node_id(self, value):
self._node_id = value
self.ifc.set_node_id(value)
''' '''
Empty methods, that have to be implemented in any child class which Empty methods, that have to be implemented in any child class which
don't use asyncio don't use asyncio
@@ -120,10 +134,6 @@ class Message(metaclass=IterRegistry):
# to our _recv_buffer # to our _recv_buffer
return # pragma: no cover return # pragma: no cover
def _update_header(self, _forward_buffer):
'''callback for updating the header of the forward buffer'''
pass # pragma: no cover
def _set_mqtt_timestamp(self, key, ts: float | None): def _set_mqtt_timestamp(self, key, ts: float | None):
if key not in self.new_data or \ if key not in self.new_data or \
not self.new_data[key]: not self.new_data[key]:
@@ -139,6 +149,16 @@ class Message(metaclass=IterRegistry):
# logger.info(f'update: key: {key} ts:{tstr}' # logger.info(f'update: key: {key} ts:{tstr}'
self.db.set_db_def_value(info_id, round(ts)) self.db.set_db_def_value(info_id, round(ts))
def _timeout(self) -> int:
if self.state == State.init or self.state == State.received:
to = self.MAX_START_TIME
elif self.state == State.up and \
self.server_side and self.modbus_polling:
to = self.MAX_INV_IDLE_TIME
else:
to = self.MAX_DEF_IDLE_TIME
return to
''' '''
Our puplic methods Our puplic methods
''' '''

View File

@@ -5,9 +5,11 @@ import asyncio
if __name__ == "app.src.modbus_tcp": if __name__ == "app.src.modbus_tcp":
from app.src.config import Config from app.src.config import Config
from app.src.gen3plus.inverter_g3p import InverterG3P from app.src.gen3plus.inverter_g3p import InverterG3P
from app.src.infos import Infos
else: # pragma: no cover else: # pragma: no cover
from config import Config from config import Config
from gen3plus.inverter_g3p import InverterG3P from gen3plus.inverter_g3p import InverterG3P
from infos import Infos
logger = logging.getLogger('conn') logger = logging.getLogger('conn')
@@ -27,13 +29,14 @@ class ModbusConn():
client_mode=True) client_mode=True)
logging.info(f'[{self.stream.node_id}:{self.stream.conn_no}] ' logging.info(f'[{self.stream.node_id}:{self.stream.conn_no}] '
f'Connected to {self.addr}') f'Connected to {self.addr}')
self.stream.inc_counter('Inverter_Cnt') Infos.inc_counter('Inverter_Cnt')
await self.stream.publish_outstanding_mqtt() await self.stream._ifc.publish_outstanding_mqtt()
return self.stream return self.stream
async def __aexit__(self, exc_type, exc, tb): async def __aexit__(self, exc_type, exc, tb):
self.stream.dec_counter('Inverter_Cnt') Infos.dec_counter('Inverter_Cnt')
await self.stream.publish_outstanding_mqtt() await self.stream._ifc.publish_outstanding_mqtt()
self.stream.close()
class ModbusTcp(): class ModbusTcp():
@@ -60,7 +63,7 @@ class ModbusTcp():
try: try:
async with ModbusConn(host, port) as stream: async with ModbusConn(host, port) as stream:
await stream.send_start_cmd(snr, host) await stream.send_start_cmd(snr, host)
await stream.loop() await stream._ifc.loop()
logger.info(f'[{stream.node_id}:{stream.conn_no}] ' logger.info(f'[{stream.node_id}:{stream.conn_no}] '
f'Connection closed - Shutdown: ' f'Connection closed - Shutdown: '
f'{stream.shutdown_started}') f'{stream.shutdown_started}')

View File

@@ -1,13 +0,0 @@
class ObjectFactory:
def __init__(self):
self._builders = {}
def register_builder(self, key, builder):
self._builders[key] = builder
def create(self, key, **kwargs):
builder = self._builders.get(key)
if not builder:
raise ValueError(key)
return builder(**kwargs)

View File

@@ -74,14 +74,14 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
'''Handles a new incoming connection and starts an async loop''' '''Handles a new incoming connection and starts an async loop'''
addr = writer.get_extra_info('peername') addr = writer.get_extra_info('peername')
await InverterG3(reader, writer, addr).server_loop(addr) await InverterG3(reader, writer, addr)._ifc.server_loop(addr)
async def handle_client_v2(reader: StreamReader, writer: StreamWriter): async def handle_client_v2(reader: StreamReader, writer: StreamWriter):
'''Handles a new incoming connection and starts an async loop''' '''Handles a new incoming connection and starts an async loop'''
addr = writer.get_extra_info('peername') addr = writer.get_extra_info('peername')
await InverterG3P(reader, writer, addr).server_loop(addr) await InverterG3P(reader, writer, addr)._ifc.server_loop(addr)
async def handle_shutdown(web_task): async def handle_shutdown(web_task):

View File

@@ -2,9 +2,10 @@
import pytest import pytest
import asyncio import asyncio
from itertools import count
from mock import patch from mock import patch
from app.src.async_stream import AsyncStream from app.src.async_stream import AsyncStream, AsyncIfcImpl
from app.src.gen3.connection_g3 import ConnectionG3 from app.src.gen3.connection_g3 import ConnectionG3Server
from app.src.gen3.talent import Talent from app.src.gen3.talent import Talent
@pytest.fixture @pytest.fixture
@@ -60,8 +61,8 @@ class FakeWriter():
def test_method_calls(patch_async_init, patch_talent_init, patch_healthy, patch_async_close, patch_talent_close): def test_method_calls(patch_talent_init, patch_healthy, patch_async_close, patch_talent_close):
spy1 = patch_async_init AsyncIfcImpl._ids = count(5)
spy2 = patch_talent_init spy2 = patch_talent_init
spy3 = patch_healthy spy3 = patch_healthy
spy4 = patch_async_close spy4 = patch_async_close
@@ -70,9 +71,9 @@ def test_method_calls(patch_async_init, patch_talent_init, patch_healthy, patch_
writer = FakeWriter() writer = FakeWriter()
id_str = "id_string" id_str = "id_string"
addr = ('proxy.local', 10000) addr = ('proxy.local', 10000)
conn = ConnectionG3(reader, writer, addr, conn = ConnectionG3Server(reader, writer, addr,
remote_stream= None, server_side=True, id_str=id_str) rstream= None, id_str=id_str)
spy1.assert_called_once_with(conn, reader, writer, addr, conn._ifc) assert 5 == conn._ifc.get_conn_no()
spy2.assert_called_once_with(conn, True, conn._ifc, id_str) spy2.assert_called_once_with(conn, True, conn._ifc, id_str)
conn.healthy() conn.healthy()
@@ -81,4 +82,3 @@ def test_method_calls(patch_async_init, patch_talent_init, patch_healthy, patch_
conn.close() conn.close()
spy4.assert_called_once() spy4.assert_called_once()
spy5.assert_called_once() spy5.assert_called_once()

View File

@@ -2,15 +2,16 @@
import pytest import pytest
import asyncio import asyncio
from itertools import count
from mock import patch from mock import patch
from app.src.singleton import Singleton from app.src.singleton import Singleton
from app.src.async_stream import AsyncStream from app.src.async_stream import AsyncStream, AsyncIfcImpl
from app.src.gen3plus.connection_g3p import ConnectionG3P from app.src.gen3plus.connection_g3p import ConnectionG3PServer
from app.src.gen3plus.solarman_v5 import SolarmanV5 from app.src.gen3plus.solarman_v5 import SolarmanV5
@pytest.fixture @pytest.fixture
def patch_async_init(): def patch_async_init():
with patch.object(AsyncStream, '__init__') as conn: with patch.object(AsyncStream, '__init__', return_value= None) as conn:
yield conn yield conn
@pytest.fixture @pytest.fixture
@@ -66,8 +67,8 @@ class FakeWriter():
def test_method_calls(patch_async_init, patch_solarman_init, patch_healthy, patch_async_close, patch_solarman_close): def test_method_calls(patch_solarman_init, patch_healthy, patch_async_close, patch_solarman_close):
spy1 = patch_async_init AsyncIfcImpl._ids = count(5)
spy2 = patch_solarman_init spy2 = patch_solarman_init
spy3 = patch_healthy spy3 = patch_healthy
spy4 = patch_async_close spy4 = patch_async_close
@@ -75,9 +76,9 @@ def test_method_calls(patch_async_init, patch_solarman_init, patch_healthy, patc
reader = FakeReader() reader = FakeReader()
writer = FakeWriter() writer = FakeWriter()
addr = ('proxy.local', 10000) addr = ('proxy.local', 10000)
conn = ConnectionG3P(reader, writer, addr, conn = ConnectionG3PServer(reader, writer, addr,
remote_stream= None, server_side=True, client_mode=False) rstream= None, client_mode=False)
spy1.assert_called_once_with(conn, reader, writer, addr, conn._ifc) assert 5 == conn._ifc.get_conn_no()
spy2.assert_called_once_with(conn, True, False, conn._ifc) spy2.assert_called_once_with(conn, True, False, conn._ifc)
conn.healthy() conn.healthy()

View File

@@ -18,7 +18,7 @@ pytest_plugins = ('pytest_asyncio',)
@pytest.fixture(scope="module", autouse=True) @pytest.fixture(scope="module", autouse=True)
def module_init(): def module_init():
def new_init(cls, cb_mqtt_is_up): def new_init(cls, cb_mqtt_is_up):
cb_mqtt_is_up() pass # empty test methos
Singleton._instances.clear() Singleton._instances.clear()
with patch.object(Mqtt, '__init__', new_init): with patch.object(Mqtt, '__init__', new_init):
@@ -69,6 +69,7 @@ async def test_inverter_cb(config_conn):
assert 'homeassistant/' == Inverter.discovery_prfx assert 'homeassistant/' == Inverter.discovery_prfx
assert 'tsun/' == Inverter.entity_prfx assert 'tsun/' == Inverter.entity_prfx
assert 'test_1/' == Inverter.proxy_node_id assert 'test_1/' == Inverter.proxy_node_id
await Inverter._cb_mqtt_is_up()
spy.assert_called_once() spy.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@@ -8,7 +8,7 @@ from app.src.infos import Infos
from app.src.config import Config from app.src.config import Config
from app.src.inverter import Inverter from app.src.inverter import Inverter
from app.src.singleton import Singleton from app.src.singleton import Singleton
from app.src.gen3.connection_g3 import ConnectionG3 from app.src.gen3.connection_g3 import ConnectionG3Server
from app.src.gen3.inverter_g3 import InverterG3 from app.src.gen3.inverter_g3 import InverterG3
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
@@ -44,12 +44,12 @@ def module_init():
@pytest.fixture @pytest.fixture
def patch_conn_init(): def patch_conn_init():
with patch.object(ConnectionG3, '__init__', return_value= None) as conn: with patch.object(ConnectionG3Server, '__init__', return_value= None) as conn:
yield conn yield conn
@pytest.fixture @pytest.fixture
def patch_conn_close(): def patch_conn_close():
with patch.object(ConnectionG3, 'close') as conn: with patch.object(ConnectionG3Server, 'close') as conn:
yield conn yield conn
class FakeReader(): class FakeReader():
@@ -115,7 +115,7 @@ def test_method_calls(patch_conn_init, patch_conn_close):
inverter.r_addr = '' inverter.r_addr = ''
spy1.assert_called_once() spy1.assert_called_once()
spy1.assert_called_once_with(reader, writer, addr, None, True) spy1.assert_called_once_with(reader, writer, addr, None)
inverter.close() inverter.close()
spy2.assert_called_once() spy2.assert_called_once()
@@ -132,7 +132,7 @@ async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close)
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream assert inverter.remote.stream
inverter.close() inverter.close()
spy1.assert_called_once() spy1.assert_called_once()
@@ -151,12 +151,12 @@ async def test_remote_except(config_conn, patch_open_connection, patch_conn_clos
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream==None assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT test = TestType.RD_TEST_EXCEPT
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream==None assert inverter.remote.stream==None
inverter.close() inverter.close()
spy1.assert_called_once() spy1.assert_called_once()
@@ -171,6 +171,7 @@ async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close
Inverter.class_init() Inverter.class_init()
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
await inverter.async_publ_mqtt() # check call with invalid unique_id
inverter._Talent__set_serial_no(serial_no= "123344") inverter._Talent__set_serial_no(serial_no= "123344")
inverter.new_data['inverter'] = True inverter.new_data['inverter'] = True

View File

@@ -8,7 +8,7 @@ from app.src.infos import Infos
from app.src.config import Config from app.src.config import Config
from app.src.inverter import Inverter from app.src.inverter import Inverter
from app.src.singleton import Singleton from app.src.singleton import Singleton
from app.src.gen3plus.connection_g3p import ConnectionG3P from app.src.gen3plus.connection_g3p import ConnectionG3PServer
from app.src.gen3plus.inverter_g3p import InverterG3P 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 from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
@@ -45,12 +45,12 @@ def module_init():
@pytest.fixture @pytest.fixture
def patch_conn_init(): def patch_conn_init():
with patch.object(ConnectionG3P, '__init__', return_value= None) as conn: with patch.object(ConnectionG3PServer, '__init__', return_value= None) as conn:
yield conn yield conn
@pytest.fixture @pytest.fixture
def patch_conn_close(): def patch_conn_close():
with patch.object(ConnectionG3P, 'close') as conn: with patch.object(ConnectionG3PServer, 'close') as conn:
yield conn yield conn
class FakeReader(): class FakeReader():
@@ -116,7 +116,7 @@ def test_method_calls(patch_conn_init, patch_conn_close):
inverter.r_addr = '' inverter.r_addr = ''
spy1.assert_called_once() spy1.assert_called_once()
spy1.assert_called_once_with(reader, writer, addr, None, server_side=True, client_mode=False) spy1.assert_called_once_with(reader, writer, addr, None, client_mode=False)
inverter.close() inverter.close()
spy2.assert_called_once() spy2.assert_called_once()
@@ -133,7 +133,7 @@ async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close)
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream assert inverter.remote.stream
inverter.close() inverter.close()
spy1.assert_called_once() spy1.assert_called_once()
@@ -152,12 +152,12 @@ async def test_remote_except(config_conn, patch_open_connection, patch_conn_clos
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream==None assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT test = TestType.RD_TEST_EXCEPT
await inverter.async_create_remote() await inverter.async_create_remote()
await asyncio.sleep(0) await asyncio.sleep(0)
assert inverter.remote_stream==None assert inverter.remote.stream==None
inverter.close() inverter.close()
spy1.assert_called_once() spy1.assert_called_once()
@@ -172,6 +172,7 @@ async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close
Inverter.class_init() Inverter.class_init()
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
await inverter.async_publ_mqtt() # check call with invalid unique_id
inverter._SolarmanV5__set_serial_no(snr= 123344) inverter._SolarmanV5__set_serial_no(snr= 123344)
inverter.new_data['inverter'] = True inverter.new_data['inverter'] = True

View File

@@ -157,8 +157,8 @@ async def test_modbus_conn(patch_open):
async with ModbusConn('test.local', 1234) as stream: async with ModbusConn('test.local', 1234) as stream:
assert stream.node_id == 'G3P' assert stream.node_id == 'G3P'
assert stream.addr == ('test.local', 1234) assert stream.addr == ('test.local', 1234)
assert type(stream._reader) is FakeReader assert type(stream._ifc._reader) is FakeReader
assert type(stream._writer) is FakeWriter assert type(stream._ifc._writer) is FakeWriter
assert Infos.stat['proxy']['Inverter_Cnt'] == 1 assert Infos.stat['proxy']['Inverter_Cnt'] == 1
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
@@ -209,7 +209,7 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
test += 1 test += 1
assert Infos.stat['proxy']['Inverter_Cnt'] == 1 assert Infos.stat['proxy']['Inverter_Cnt'] == 1
m.shutdown_started = True m.shutdown_started = True
m._reader.on_recv.set() m._ifc._reader.on_recv.set()
del m del m
assert 1 == test assert 1 == test
@@ -236,13 +236,13 @@ async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
test += 1 test += 1
if test == 1: if test == 1:
m.shutdown_started = False m.shutdown_started = False
m._reader.on_recv.set() m.ifc._reader.on_recv.set()
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert m.state == State.closed assert m.state == State.closed
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
else: else:
m.shutdown_started = True m.shutdown_started = True
m._reader.on_recv.set() m.ifc._reader.on_recv.set()
del m del m
assert 2 == test assert 2 == test
@@ -269,13 +269,14 @@ async def test_mqtt_err(config_conn, patch_mqtt_err, patch_open):
test += 1 test += 1
if test == 1: if test == 1:
m.shutdown_started = False m.shutdown_started = False
m._reader.on_recv.set() m._ifc._reader.on_recv.set()
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert m.state == State.closed assert m.state == State.closed
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
await asyncio.sleep(0.1)
else: else:
m.shutdown_started = True m.shutdown_started = True
m._reader.on_recv.set() m._ifc._reader.on_recv.set()
del m del m
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
@@ -301,13 +302,13 @@ async def test_mqtt_except(config_conn, patch_mqtt_except, patch_open):
test += 1 test += 1
if test == 1: if test == 1:
m.shutdown_started = False m.shutdown_started = False
m._reader.on_recv.set() m._ifc._reader.on_recv.set()
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert m.state == State.closed assert m.state == State.closed
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
else: else:
m.shutdown_started = True m.shutdown_started = True
m._reader.on_recv.set() m._ifc._reader.on_recv.set()
del m del m
await asyncio.sleep(0.01) await asyncio.sleep(0.01)

View File

@@ -5,7 +5,7 @@ import aiomqtt
import logging import logging
from mock import patch, Mock from mock import patch, Mock
from app.src.async_ifc import AsyncIfc from app.src.async_stream import AsyncIfcImpl
from app.src.singleton import Singleton from app.src.singleton import Singleton
from app.src.mqtt import Mqtt from app.src.mqtt import Mqtt
from app.src.modbus import Modbus from app.src.modbus import Modbus
@@ -45,8 +45,7 @@ def config_no_conn(test_port):
@pytest.fixture @pytest.fixture
def spy_at_cmd(): def spy_at_cmd():
ifc = AsyncIfc() conn = SolarmanV5(server_side=True, client_mode= False, ifc=AsyncIfcImpl())
conn = SolarmanV5(server_side=True, client_mode= False, ifc=ifc)
conn.node_id = 'inv_2/' conn.node_id = 'inv_2/'
with patch.object(conn, 'send_at_cmd', wraps=conn.send_at_cmd) as wrapped_conn: with patch.object(conn, 'send_at_cmd', wraps=conn.send_at_cmd) as wrapped_conn:
yield wrapped_conn yield wrapped_conn
@@ -54,8 +53,7 @@ def spy_at_cmd():
@pytest.fixture @pytest.fixture
def spy_modbus_cmd(): def spy_modbus_cmd():
ifc = AsyncIfc() conn = SolarmanV5(server_side=True, client_mode= False, ifc=AsyncIfcImpl())
conn = SolarmanV5(server_side=True, client_mode= False, ifc=ifc)
conn.node_id = 'inv_1/' conn.node_id = 'inv_1/'
with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn: with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn:
yield wrapped_conn yield wrapped_conn
@@ -63,8 +61,7 @@ def spy_modbus_cmd():
@pytest.fixture @pytest.fixture
def spy_modbus_cmd_client(): def spy_modbus_cmd_client():
ifc = AsyncIfc() conn = SolarmanV5(server_side=False, client_mode= False, ifc=AsyncIfcImpl())
conn = SolarmanV5(server_side=False, client_mode= False, ifc=ifc)
conn.node_id = 'inv_1/' conn.node_id = 'inv_1/'
with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn: with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn:
yield wrapped_conn yield wrapped_conn

View File

@@ -5,7 +5,7 @@ import asyncio
import logging import logging
import random import random
from math import isclose from math import isclose
from app.src.async_ifc import AsyncIfc from app.src.async_stream import AsyncIfcImpl, StreamPtr
from app.src.gen3plus.solarman_v5 import SolarmanV5 from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config from app.src.config import Config
from app.src.infos import Infos, Register from app.src.infos import Infos, Register
@@ -34,14 +34,15 @@ class Mqtt():
class MemoryStream(SolarmanV5): class MemoryStream(SolarmanV5):
def __init__(self, msg, chunks = (0,), server_side: bool = True): def __init__(self, msg, chunks = (0,), server_side: bool = True):
_ifc = AsyncIfc() _ifc = AsyncIfcImpl()
super().__init__(server_side, client_mode=False, ifc=_ifc) super().__init__(server_side, client_mode=False, ifc=_ifc)
if server_side: if server_side:
self.mb.timeout = 0.4 # overwrite for faster testing self.mb.timeout = 0.4 # overwrite for faster testing
self.remote = StreamPtr(None)
self.mb_first_timeout = 0.5 self.mb_first_timeout = 0.5
self.mb_timeout = 0.5 self.mb_timeout = 0.5
self.sent_pdu = b'' self.sent_pdu = b''
self.ifc.write.reg_trigger(self.write_cb) self.ifc.tx_fifo.reg_trigger(self.write_cb)
self.mqtt = Mqtt() self.mqtt = Mqtt()
self.__msg = msg self.__msg = msg
self.__msg_len = len(msg) self.__msg_len = len(msg)
@@ -61,7 +62,9 @@ class MemoryStream(SolarmanV5):
self.msg_recvd = [] self.msg_recvd = []
def write_cb(self): def write_cb(self):
self.sent_pdu = self.ifc.write.get() if self.test_exception_async_write:
raise RuntimeError("Peer closed.")
self.sent_pdu = self.ifc.tx_fifo.get()
def _timestamp(self): def _timestamp(self):
return timestamp return timestamp
@@ -85,25 +88,21 @@ class MemoryStream(SolarmanV5):
chunk_len = self.__chunks[self.__chunk_idx] chunk_len = self.__chunks[self.__chunk_idx]
self.__chunk_idx += 1 self.__chunk_idx += 1
if chunk_len!=0: if chunk_len!=0:
self.ifc.read += self.__msg[self.__offs:chunk_len] self.ifc.rx_fifo += self.__msg[self.__offs:chunk_len]
copied_bytes = chunk_len - self.__offs copied_bytes = chunk_len - self.__offs
self.__offs = chunk_len self.__offs = chunk_len
else: else:
self.ifc.read += self.__msg[self.__offs:] self.ifc.rx_fifo += self.__msg[self.__offs:]
copied_bytes = self.__msg_len - self.__offs copied_bytes = self.__msg_len - self.__offs
self.__offs = self.__msg_len self.__offs = self.__msg_len
except Exception: except Exception:
pass # ignore exceptions here pass # ignore exceptions here
return copied_bytes return copied_bytes
async def async_write(self, headline=''):
if self.test_exception_async_write:
raise RuntimeError("Peer closed.")
def createClientStream(self, msg, chunks = (0,)): def createClientStream(self, msg, chunks = (0,)):
c = MemoryStream(msg, chunks, False) c = MemoryStream(msg, chunks, False)
self.remote_stream = c self.remote.stream = c
c. remote_stream = self c. remote.stream = self
return c return c
def _SolarmanV5__flush_recv_msg(self) -> None: def _SolarmanV5__flush_recv_msg(self) -> None:
@@ -689,9 +688,9 @@ def test_read_message(device_ind_msg):
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:00' assert str(m.seq) == '01:00'
assert m.data_len == 0xd4 assert m.data_len == 0xd4
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -710,9 +709,9 @@ def test_invalid_start_byte(invalid_start_byte, device_ind_msg):
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:00' assert str(m.seq) == '01:00'
assert m.data_len == 0xd4 assert m.data_len == 0xd4
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
m.close() m.close()
@@ -730,9 +729,9 @@ def test_invalid_stop_byte(invalid_stop_byte):
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:00' assert str(m.seq) == '01:00'
assert m.data_len == 0xd4 assert m.data_len == 0xd4
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
m.close() m.close()
@@ -755,9 +754,9 @@ def test_invalid_stop_byte2(invalid_stop_byte, device_ind_msg):
assert m.msg_recvd[1]['data_len']==0xd4 assert m.msg_recvd[1]['data_len']==0xd4
assert m.unique_id == None assert m.unique_id == None
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
m.close() m.close()
@@ -777,9 +776,9 @@ def test_invalid_stop_start_byte(invalid_stop_byte, invalid_start_byte):
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:00' assert str(m.seq) == '01:00'
assert m.data_len == 0xd4 assert m.data_len == 0xd4
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
m.close() m.close()
@@ -801,9 +800,9 @@ def test_invalid_checksum(invalid_checksum, device_ind_msg):
assert m.msg_recvd[1]['control']==0x4110 assert m.msg_recvd[1]['control']==0x4110
assert m.msg_recvd[1]['seq']=='01:00' assert m.msg_recvd[1]['seq']=='01:00'
assert m.msg_recvd[1]['data_len']==0xd4 assert m.msg_recvd[1]['data_len']==0xd4
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
m.close() m.close()
@@ -823,8 +822,8 @@ def test_read_message_twice(config_no_tsun_inv1, device_ind_msg, device_rsp_msg)
assert m.msg_recvd[1]['control']==0x4110 assert m.msg_recvd[1]['control']==0x4110
assert m.msg_recvd[1]['seq']=='01:01' assert m.msg_recvd[1]['seq']=='01:01'
assert m.msg_recvd[1]['data_len']==0xd4 assert m.msg_recvd[1]['data_len']==0xd4
assert m.ifc.write.get()==device_rsp_msg+device_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg+device_rsp_msg
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -895,11 +894,11 @@ def test_read_two_messages(config_tsun_allow_all, device_ind_msg, device_rsp_msg
assert m.msg_recvd[1]['data_len']==0x199 assert m.msg_recvd[1]['data_len']==0x199
assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None) assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None)
assert 0x02b0 == m.sensor_list assert 0x02b0 == m.sensor_list
assert m._forward_buffer==device_ind_msg+inverter_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg+inverter_ind_msg
assert m.ifc.write.get()==device_rsp_msg+inverter_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg+inverter_rsp_msg
m._init_new_client_conn() m._init_new_client_conn()
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
m.close() m.close()
def test_read_two_messages2(config_tsun_allow_all, inverter_ind_msg, inverter_ind_msg_81, inverter_rsp_msg, inverter_rsp_msg_81): def test_read_two_messages2(config_tsun_allow_all, inverter_ind_msg, inverter_ind_msg_81, inverter_rsp_msg, inverter_rsp_msg_81):
@@ -920,11 +919,11 @@ def test_read_two_messages2(config_tsun_allow_all, inverter_ind_msg, inverter_in
assert m.msg_recvd[1]['seq']=='03:03' assert m.msg_recvd[1]['seq']=='03:03'
assert m.msg_recvd[1]['data_len']==0x199 assert m.msg_recvd[1]['data_len']==0x199
assert m.time_ofs == 0x33e447a0 assert m.time_ofs == 0x33e447a0
assert m._forward_buffer==inverter_ind_msg+inverter_ind_msg_81 assert m.ifc.fwd_fifo.get()==inverter_ind_msg+inverter_ind_msg_81
assert m.ifc.write.get()==inverter_rsp_msg+inverter_rsp_msg_81 assert m.ifc.tx_fifo.get()==inverter_rsp_msg+inverter_rsp_msg_81
m._init_new_client_conn() m._init_new_client_conn()
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
m.close() m.close()
def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_msg2, inverter_ind_msg, inverter_rsp_msg): def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_msg2, inverter_ind_msg, inverter_rsp_msg):
@@ -949,11 +948,11 @@ def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_m
assert m.msg_recvd[1]['data_len']==0xd4 assert m.msg_recvd[1]['data_len']==0xd4
assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None) assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None)
assert 0x02b0 == m.sensor_list assert 0x02b0 == m.sensor_list
assert m._forward_buffer==inverter_ind_msg+device_ind_msg2 assert m.ifc.fwd_fifo.get()==inverter_ind_msg+device_ind_msg2
assert m.ifc.write.get()==inverter_rsp_msg+device_rsp_msg2 assert m.ifc.tx_fifo.get()==inverter_rsp_msg+device_rsp_msg2
m._init_new_client_conn() m._init_new_client_conn()
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
m.close() m.close()
def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_msg_81): def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_msg_81):
@@ -968,9 +967,9 @@ def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_m
assert m.control == 0x4210 assert m.control == 0x4210
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.data_len == 0x199 assert m.data_len == 0x199
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==inverter_rsp_msg_81 assert m.ifc.tx_fifo.get()==inverter_rsp_msg_81
assert m._forward_buffer==inverter_ind_msg_81 assert m.ifc.fwd_fifo.get()==inverter_ind_msg_81
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -986,9 +985,9 @@ def test_unkown_message(config_tsun_inv1, unknown_msg):
assert m.control == 0x5110 assert m.control == 0x5110
assert str(m.seq) == '84:10' assert str(m.seq) == '84:10'
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==unknown_msg assert m.ifc.fwd_fifo.get()==unknown_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1004,9 +1003,9 @@ def test_device_rsp(config_tsun_inv1, device_rsp_msg):
assert m.control == 0x1110 assert m.control == 0x1110
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1022,9 +1021,9 @@ def test_inverter_rsp(config_tsun_inv1, inverter_rsp_msg):
assert m.control == 0x1210 assert m.control == 0x1210
assert str(m.seq) == '02:02' assert str(m.seq) == '02:02'
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1039,9 +1038,9 @@ def test_heartbeat_ind(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp_msg):
assert m.control == 0x4710 assert m.control == 0x4710
assert str(m.seq) == '84:11' # value after sending response assert str(m.seq) == '84:11' # value after sending response
assert m.data_len == 0x01 assert m.data_len == 0x01
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==heartbeat_rsp_msg assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
assert m._forward_buffer==heartbeat_ind_msg assert m.ifc.fwd_fifo.get()==heartbeat_ind_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1057,9 +1056,9 @@ def test_heartbeat_ind2(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp_msg):
assert m.control == 0x4710 assert m.control == 0x4710
assert str(m.seq) == '84:11' # value after sending response assert str(m.seq) == '84:11' # value after sending response
assert m.data_len == 0x01 assert m.data_len == 0x01
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==heartbeat_rsp_msg assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1075,9 +1074,9 @@ def test_heartbeat_rsp(config_tsun_inv1, heartbeat_rsp_msg):
assert m.control == 0x1710 assert m.control == 0x1710
assert str(m.seq) == '11:84' # value after sending response assert str(m.seq) == '11:84' # value after sending response
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1092,15 +1091,15 @@ def test_sync_start_ind(config_tsun_inv1, sync_start_ind_msg, sync_start_rsp_msg
assert m.control == 0x4310 assert m.control == 0x4310
assert str(m.seq) == '0d:0d' # value after sending response assert str(m.seq) == '0d:0d' # value after sending response
assert m.data_len == 47 assert m.data_len == 47
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==sync_start_rsp_msg assert m.ifc.tx_fifo.get()==sync_start_rsp_msg
assert m._forward_buffer==sync_start_ind_msg assert m.ifc.fwd_fifo.peek()==sync_start_ind_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.seq.server_side = False # simulate forawding to TSUN cloud m.seq.server_side = False # simulate forawding to TSUN cloud
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert str(m.seq) == '0d:0e' # value after forwarding indication assert str(m.seq) == '0d:0e' # value after forwarding indication
assert m._forward_buffer==sync_start_fwd_msg assert m.ifc.fwd_fifo.get()==sync_start_fwd_msg
m.close() m.close()
@@ -1116,9 +1115,9 @@ def test_sync_start_rsp(config_tsun_inv1, sync_start_rsp_msg):
assert m.control == 0x1310 assert m.control == 0x1310
assert str(m.seq) == '0d:0d' # value after sending response assert str(m.seq) == '0d:0d' # value after sending response
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1133,9 +1132,9 @@ def test_sync_end_ind(config_tsun_inv1, sync_end_ind_msg, sync_end_rsp_msg):
assert m.control == 0x4810 assert m.control == 0x4810
assert str(m.seq) == '07:07' # value after sending response assert str(m.seq) == '07:07' # value after sending response
assert m.data_len == 60 assert m.data_len == 60
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==sync_end_rsp_msg assert m.ifc.tx_fifo.get()==sync_end_rsp_msg
assert m._forward_buffer==sync_end_ind_msg assert m.ifc.fwd_fifo.get()==sync_end_ind_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1151,9 +1150,9 @@ def test_sync_end_rsp(config_tsun_inv1, sync_end_rsp_msg):
assert m.control == 0x1810 assert m.control == 0x1810
assert str(m.seq) == '07:07' # value after sending response assert str(m.seq) == '07:07' # value after sending response
assert m.data_len == 0x0a assert m.data_len == 0x0a
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close() m.close()
@@ -1171,9 +1170,9 @@ def test_build_modell_600(config_tsun_allow_all, inverter_ind_msg):
assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None) assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None)
assert 0 == m.sensor_list # must not been set by an inverter data ind assert 0 == m.sensor_list # must not been set by an inverter data ind
m.ifc.write.clear() # clear send buffer for next test m.ifc.tx_clear() # clear send buffer for next test
m._init_new_client_conn() m._init_new_client_conn()
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
m.close() m.close()
def test_build_modell_1600(config_tsun_allow_all, inverter_ind_msg1600): def test_build_modell_1600(config_tsun_allow_all, inverter_ind_msg1600):
@@ -1237,9 +1236,9 @@ def test_build_logger_modell(config_tsun_allow_all, device_ind_msg):
def test_msg_iterator(): def test_msg_iterator():
Message._registry.clear() Message._registry.clear()
m1 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfc()) m1 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m2 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfc()) m2 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m3 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfc()) m3 = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfcImpl())
m3.close() m3.close()
del m3 del m3
test1 = 0 test1 = 0
@@ -1257,7 +1256,7 @@ def test_msg_iterator():
assert test2 == 1 assert test2 == 1
def test_proxy_counter(): def test_proxy_counter():
m = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfc()) m = SolarmanV5(server_side=True, client_mode=False, ifc=AsyncIfcImpl())
assert m.new_data == {} assert m.new_data == {}
m.db.stat['proxy']['Unknown_Msg'] = 0 m.db.stat['proxy']['Unknown_Msg'] = 0
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
@@ -1281,16 +1280,14 @@ async def test_msg_build_modbus_req(config_tsun_inv1, device_ind_msg, device_rsp
m.read() m.read()
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.ifc.write.get()==device_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg
assert m._forward_buffer==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
m.ifc.write.clear() # clear send buffer for next test
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m._forward_buffer == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.sent_pdu == b'' # modbus command must be ignore, cause connection is still not up assert m.sent_pdu == b'' # modbus command must be ignore, cause connection is still not up
assert m.ifc.write.get() == b'' # modbus command must be ignore, cause connection is still not up assert m.ifc.tx_fifo.get() == b'' # modbus command must be ignore, cause connection is still not up
m.append_msg(inverter_ind_msg) m.append_msg(inverter_ind_msg)
m.read() m.read()
@@ -1300,22 +1297,15 @@ async def test_msg_build_modbus_req(config_tsun_inv1, device_ind_msg, device_rsp
assert m.msg_recvd[0]['seq']=='01:01' assert m.msg_recvd[0]['seq']=='01:01'
assert m.msg_recvd[1]['control']==0x4210 assert m.msg_recvd[1]['control']==0x4210
assert m.msg_recvd[1]['seq']=='02:02' assert m.msg_recvd[1]['seq']=='02:02'
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==inverter_rsp_msg assert m.ifc.tx_fifo.get()==inverter_rsp_msg
assert m._forward_buffer==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m._forward_buffer == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.sent_pdu == msg_modbus_cmd assert m.sent_pdu == msg_modbus_cmd
assert m.ifc.write.get()== b'' assert m.ifc.tx_fifo.get()== b''
m.test_exception_async_write = True
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs
assert m._forward_buffer == b''
assert m.ifc.write.get() == b''
m.close() m.close()
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -1325,13 +1315,13 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
m.read() # read device ind m.read() # read device ind
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.ifc.write.get()==device_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg
assert m._forward_buffer==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_at_cmd('AT+TIME=214028,1,60,120') await m.send_at_cmd('AT+TIME=214028,1,60,120')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.sent_pdu == b''
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.mqtt.key == '' assert m.mqtt.key == ''
assert m.mqtt.data == "" assert m.mqtt.data == ""
@@ -1340,13 +1330,15 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
m.read() # read inverter ind m.read() # read inverter ind
assert m.control == 0x4210 assert m.control == 0x4210
assert str(m.seq) == '02:02' assert str(m.seq) == '02:02'
assert m.ifc.write.get()==inverter_rsp_msg assert m.ifc.tx_fifo.get()==inverter_rsp_msg
assert m._forward_buffer==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_at_cmd('AT+TIME=214028,1,60,120') await m.send_at_cmd('AT+TIME=214028,1,60,120')
assert m.ifc.write.get()==at_command_ind_msg assert m.ifc.fwd_fifo.get() == b''
assert m._forward_buffer==b'' assert m.ifc.tx_fifo.get()== b''
assert m.sent_pdu == at_command_ind_msg
m.sent_pdu = bytearray()
assert str(m.seq) == '02:03' assert str(m.seq) == '02:03'
assert m.mqtt.key == '' assert m.mqtt.key == ''
assert m.mqtt.data == "" assert m.mqtt.data == ""
@@ -1355,17 +1347,20 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
m.read() # read at resp m.read() # read at resp
assert m.control == 0x1510 assert m.control == 0x1510
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.key == 'at_resp' assert m.key == 'at_resp'
assert m.data == "+ok" assert m.data == "+ok"
m.sent_pdu = bytearray()
m.test_exception_async_write = True m.test_exception_async_write = True
await m.send_at_cmd('AT+TIME=214028,1,60,120') await m.send_at_cmd('AT+TIME=214028,1,60,120')
assert m.ifc.read.get()==b'' assert m.sent_pdu == b''
assert m.ifc.write.get()==b'' assert m.ifc.rx_get()==b''
assert m._forward_buffer==b'' assert m.ifc.tx_fifo.get()==b''
assert m.ifc.fwd_fifo.get()==b''
assert m.sent_pdu == b''
assert str(m.seq) == '03:04' assert str(m.seq) == '03:04'
assert m.forward_at_cmd_resp == False assert m.forward_at_cmd_resp == False
assert m.mqtt.key == '' assert m.mqtt.key == ''
@@ -1379,13 +1374,12 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
m.read() m.read()
assert m.control == 0x4110 assert m.control == 0x4110
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.ifc.write.get()==device_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg
assert m._forward_buffer==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_at_cmd('AT+WEBU') await m.send_at_cmd('AT+WEBU')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert str(m.seq) == '01:01' assert str(m.seq) == '01:01'
assert m.mqtt.key == '' assert m.mqtt.key == ''
assert m.mqtt.data == "" assert m.mqtt.data == ""
@@ -1394,15 +1388,14 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
m.read() m.read()
assert m.control == 0x4210 assert m.control == 0x4210
assert str(m.seq) == '02:02' assert str(m.seq) == '02:02'
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==inverter_rsp_msg assert m.ifc.tx_fifo.get()==inverter_rsp_msg
assert m._forward_buffer==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
m._forward_buffer = bytearray(0) # clear send buffer for next test
await m.send_at_cmd('AT+WEBU') await m.send_at_cmd('AT+WEBU')
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert str(m.seq) == '02:02' assert str(m.seq) == '02:02'
assert m.forward_at_cmd_resp == False assert m.forward_at_cmd_resp == False
assert m.mqtt.key == 'at_resp' assert m.mqtt.key == 'at_resp'
@@ -1424,9 +1417,9 @@ def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg):
assert m.control == 0x4510 assert m.control == 0x4510
assert str(m.seq) == '03:02' assert str(m.seq) == '03:02'
assert m.data_len == 39 assert m.data_len == 39
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==at_command_ind_msg assert m.ifc.fwd_fifo.get()==at_command_ind_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
assert m.db.stat['proxy']['AT_Command'] == 1 assert m.db.stat['proxy']['AT_Command'] == 1
assert m.db.stat['proxy']['AT_Command_Blocked'] == 0 assert m.db.stat['proxy']['AT_Command_Blocked'] == 0
@@ -1448,9 +1441,9 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block):
assert m.control == 0x4510 assert m.control == 0x4510
assert str(m.seq) == '03:02' assert str(m.seq) == '03:02'
assert m.data_len == 23 assert m.data_len == 23
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command'] == 0
assert m.db.stat['proxy']['AT_Command_Blocked'] == 1 assert m.db.stat['proxy']['AT_Command_Blocked'] == 1
@@ -1470,8 +1463,8 @@ def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg):
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.header_len==11 assert m.header_len==11
assert m.data_len==17 assert m.data_len==17
assert m._forward_buffer==at_command_rsp_msg assert m.ifc.fwd_fifo.get()==at_command_rsp_msg
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1489,8 +1482,8 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_rsp_msg):
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.header_len==11 assert m.header_len==11
assert m.data_len==17 assert m.data_len==17
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1514,8 +1507,8 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd):
assert str(c.seq) == '03:02' assert str(c.seq) == '03:02'
assert c.header_len==11 assert c.header_len==11
assert c.data_len==23 assert c.data_len==23
assert c._forward_buffer==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m.sent_pdu == msg_modbus_cmd_fwd assert m.sent_pdu == msg_modbus_cmd_fwd
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command'] == 0
@@ -1541,8 +1534,8 @@ def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd_crc_err):
assert str(c.seq) == '03:02' assert str(c.seq) == '03:02'
assert c.header_len==11 assert c.header_len==11
assert c.data_len==23 assert c.data_len==23
assert c._forward_buffer==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m.sent_pdu==b'' assert m.sent_pdu==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command'] == 0
@@ -1564,8 +1557,8 @@ def test_msg_unknown_cmd_req(config_tsun_inv1, msg_unknown_cmd):
assert str(m.seq) == '03:02' assert str(m.seq) == '03:02'
assert m.header_len==11 assert m.header_len==11
assert m.data_len==23 assert m.data_len==23
assert m._forward_buffer==msg_unknown_cmd assert m.ifc.fwd_fifo.get()==msg_unknown_cmd
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
@@ -1585,8 +1578,8 @@ def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.header_len==11 assert m.header_len==11
assert m.data_len==59 assert m.data_len==59
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1609,21 +1602,20 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 0 assert m.mb.err == 0
assert m.msg_count == 1 assert m.msg_count == 1
assert m._forward_buffer==msg_modbus_rsp assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10' assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
assert m.new_data['inverter'] == True assert m.new_data['inverter'] == True
m.new_data['inverter'] = False m.new_data['inverter'] = False
m.mb.req_pend = True m.mb.req_pend = True
m._forward_buffer = bytearray()
m.append_msg(msg_modbus_rsp) m.append_msg(msg_modbus_rsp)
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 0 assert m.mb.err == 0
assert m.msg_count == 2 assert m.msg_count == 2
assert m._forward_buffer==msg_modbus_rsp assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10' assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
assert m.new_data['inverter'] == False assert m.new_data['inverter'] == False
@@ -1647,20 +1639,19 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 0 assert m.mb.err == 0
assert m.msg_count == 1 assert m.msg_count == 1
assert m._forward_buffer==msg_modbus_rsp assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10' assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
assert m.new_data['inverter'] == True assert m.new_data['inverter'] == True
m.new_data['inverter'] = False m.new_data['inverter'] = False
m._forward_buffer = bytearray()
m.append_msg(msg_modbus_rsp) m.append_msg(msg_modbus_rsp)
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 5 assert m.mb.err == 5
assert m.msg_count == 2 assert m.msg_count == 2
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10' assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
assert m.new_data['inverter'] == False assert m.new_data['inverter'] == False
@@ -1678,8 +1669,8 @@ def test_msg_unknown_rsp(config_tsun_inv1, msg_unknown_cmd_rsp):
assert str(m.seq) == '03:03' assert str(m.seq) == '03:03'
assert m.header_len==11 assert m.header_len==11
assert m.data_len==59 assert m.data_len==59
assert m._forward_buffer==msg_unknown_cmd_rsp assert m.ifc.fwd_fifo.get()==msg_unknown_cmd_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1692,8 +1683,8 @@ def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_invalid):
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1 assert m.msg_count == 1
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1715,8 +1706,8 @@ def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp):
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1 assert m.msg_count == 1
assert m._forward_buffer==msg_modbus_rsp assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
assert m.ifc.write.get()== b'' assert m.ifc.tx_fifo.get()== b''
assert m.mb.err == 0 assert m.mb.err == 0
assert m.modbus_elms == 20-1 # register 0x300d is unknown, so one value can't be mapped assert m.modbus_elms == 20-1 # register 0x300d is unknown, so one value can't be mapped
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
@@ -1739,9 +1730,9 @@ async def test_modbus_polling(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp
assert m.control == 0x4710 assert m.control == 0x4710
assert str(m.seq) == '84:11' # value after sending response assert str(m.seq) == '84:11' # value after sending response
assert m.data_len == 0x01 assert m.data_len == 0x01
assert m.ifc.read.get()==b'' assert m.ifc.rx_get()==b''
assert m.ifc.write.get()==heartbeat_rsp_msg assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
assert m._forward_buffer==heartbeat_ind_msg assert m.ifc.fwd_fifo.get()==heartbeat_ind_msg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
assert m.state == State.up assert m.state == State.up
@@ -1750,16 +1741,16 @@ async def test_modbus_polling(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x12\x84!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x30\x00\x000J\xde\x86\x15') assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x12\x84!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x30\x00\x000J\xde\x86\x15')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x13\x84!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x30\x00\x000J\xde\x87\x15') assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x13\x84!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x30\x00\x000J\xde\x87\x15')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
m.state = State.closed m.state = State.closed
m.sent_pdu = bytearray() m.sent_pdu = bytearray()
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==bytearray(b'') assert m.sent_pdu==bytearray(b'')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert next(m.mb_timer.exp_count) == 4 assert next(m.mb_timer.exp_count) == 4
m.close() m.close()
@@ -1781,16 +1772,29 @@ async def test_start_client_mode(config_tsun_inv1, str_test_ip):
assert m.state == State.up assert m.state == State.up
assert m.no_forwarding == True assert m.no_forwarding == True
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert isclose(m.mb_timeout, 0.5) assert isclose(m.mb_timeout, 0.5)
assert next(m.mb_timer.exp_count) == 0 assert next(m.mb_timer.exp_count) == 0
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x02\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf2\x15') assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x02\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf2\x15')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x03\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf3\x15') assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x03\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf3\x15')
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert next(m.mb_timer.exp_count) == 3 assert next(m.mb_timer.exp_count) == 3
m.close() m.close()
def test_timeout(config_tsun_inv1):
_ = config_tsun_inv1
m = MemoryStream(b'')
assert m.state == State.init
assert SolarmanV5.MAX_START_TIME == m._timeout()
m.state = State.up
m.modbus_polling = True
assert SolarmanV5.MAX_INV_IDLE_TIME == m._timeout()
m.modbus_polling = False
assert SolarmanV5.MAX_DEF_IDLE_TIME == m._timeout()
m.state = State.closed
m.close()

View File

@@ -1,7 +1,7 @@
# test_with_pytest.py # test_with_pytest.py
import pytest, logging, asyncio import pytest, logging, asyncio
from math import isclose from math import isclose
from app.src.async_ifc import AsyncIfc from app.src.async_stream import AsyncIfcImpl, StreamPtr
from app.src.gen3.talent import Talent, Control from app.src.gen3.talent import Talent, Control
from app.src.config import Config from app.src.config import Config
from app.src.infos import Infos, Register from app.src.infos import Infos, Register
@@ -19,13 +19,15 @@ tracer = logging.getLogger('tracer')
class MemoryStream(Talent): class MemoryStream(Talent):
def __init__(self, msg, chunks = (0,), server_side: bool = True): def __init__(self, msg, chunks = (0,), server_side: bool = True):
super().__init__(server_side, AsyncIfc()) self.ifc = AsyncIfcImpl()
super().__init__(server_side, self.ifc)
if server_side: if server_side:
self.mb.timeout = 0.4 # overwrite for faster testing self.mb.timeout = 0.4 # overwrite for faster testing
self.remote = StreamPtr(None)
self.mb_first_timeout = 0.5 self.mb_first_timeout = 0.5
self.mb_timeout = 0.5 self.mb_timeout = 0.5
self.sent_pdu = b'' self.sent_pdu = b''
self.ifc.write.reg_trigger(self.write_cb) self.ifc.tx_fifo.reg_trigger(self.write_cb)
self.__msg = msg self.__msg = msg
self.__msg_len = len(msg) self.__msg_len = len(msg)
self.__chunks = chunks self.__chunks = chunks
@@ -34,12 +36,11 @@ class MemoryStream(Talent):
self.msg_count = 0 self.msg_count = 0
self.addr = 'Test: SrvSide' self.addr = 'Test: SrvSide'
self.send_msg_ofs = 0 self.send_msg_ofs = 0
self.test_exception_async_write = False
self.msg_recvd = [] self.msg_recvd = []
self.remote_stream = None self.remote.stream = None
def write_cb(self): def write_cb(self):
self.sent_pdu = self.ifc.write.get() self.sent_pdu = self.ifc.tx_fifo.get()
def append_msg(self, msg): def append_msg(self, msg):
@@ -53,11 +54,11 @@ class MemoryStream(Talent):
chunk_len = self.__chunks[self.__chunk_idx] chunk_len = self.__chunks[self.__chunk_idx]
self.__chunk_idx += 1 self.__chunk_idx += 1
if chunk_len!=0: if chunk_len!=0:
self.ifc.read += self.__msg[self.__offs:chunk_len] self.ifc.rx_fifo += self.__msg[self.__offs:chunk_len]
copied_bytes = chunk_len - self.__offs copied_bytes = chunk_len - self.__offs
self.__offs = chunk_len self.__offs = chunk_len
else: else:
self.ifc.read += self.__msg[self.__offs:] self.ifc.rx_fifo += self.__msg[self.__offs:]
copied_bytes = self.__msg_len - self.__offs copied_bytes = self.__msg_len - self.__offs
self.__offs = self.__msg_len self.__offs = self.__msg_len
except Exception: except Exception:
@@ -72,8 +73,8 @@ class MemoryStream(Talent):
def createClientStream(self, msg, chunks = (0,)): def createClientStream(self, msg, chunks = (0,)):
c = MemoryStream(msg, chunks, False) c = MemoryStream(msg, chunks, False)
self.remote_stream = c self.remote.stream = c
c. remote_stream = self c. remote.stream = self
return c return c
def _Talent__flush_recv_msg(self) -> None: def _Talent__flush_recv_msg(self) -> None:
@@ -90,10 +91,6 @@ class MemoryStream(Talent):
self.msg_count += 1 self.msg_count += 1
async def async_write(self, headline=''):
if self.test_exception_async_write:
raise RuntimeError("Peer closed.")
@pytest.fixture @pytest.fixture
@@ -746,7 +743,9 @@ def test_read_message(msg_contact_info):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==25 assert m.data_len==25
assert m._forward_buffer==b'' assert m.ifc.rx_get()==b''
assert m.ifc.tx_fifo.get()==b''
assert m.ifc.fwd_fifo.get()==b''
m.close() m.close()
def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind): def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind):
@@ -766,7 +765,7 @@ def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind):
assert m.msg_recvd[1]['data_len']==120 assert m.msg_recvd[1]['data_len']==120
assert m.id_str == b"R170000000000001" assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001' assert m.unique_id == 'R170000000000001'
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
m.close() m.close()
def test_read_message_long_id(msg_contact_info_long_id): def test_read_message_long_id(msg_contact_info_long_id):
@@ -851,15 +850,15 @@ def test_read_two_messages(config_tsun_allow_all, msg2_contact_info,msg_contact_
assert m.msg_recvd[1]['msg_id']==0 assert m.msg_recvd[1]['msg_id']==0
assert m.msg_recvd[1]['header_len']==23 assert m.msg_recvd[1]['header_len']==23
assert m.msg_recvd[1]['data_len']==25 assert m.msg_recvd[1]['data_len']==25
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_contact_rsp + msg_contact_rsp2 assert m.ifc.tx_fifo.get()==msg_contact_rsp + msg_contact_rsp2
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.ifc.write.clear() # clear send buffer for next test m.ifc.tx_clear() # clear send buffer for next test
m.contact_name = b'solarhub' m.contact_name = b'solarhub'
m.contact_mail = b'solarhub@123456' m.contact_mail = b'solarhub@123456'
m._init_new_client_conn() m._init_new_client_conn()
assert m.ifc.write.get()==b'\x00\x00\x00,\x10R170000000000002\x91\x00\x08solarhub\x0fsolarhub@123456' assert m.ifc.tx_fifo.get()==b'\x00\x00\x00,\x10R170000000000002\x91\x00\x08solarhub\x0fsolarhub@123456'
m.close() m.close()
def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp): def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
@@ -876,8 +875,8 @@ def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==25 assert m.data_len==25
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_contact_rsp assert m.ifc.tx_fifo.get()==msg_contact_rsp
m.close() m.close()
def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp): def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp):
@@ -894,8 +893,8 @@ def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==23 assert m.data_len==23
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_contact_rsp assert m.ifc.tx_fifo.get()==msg_contact_rsp
m.close() m.close()
def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp): def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
@@ -912,8 +911,8 @@ def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==25 assert m.data_len==25
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_contact_rsp assert m.ifc.tx_fifo.get()==msg_contact_rsp
m.close() m.close()
def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp): def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp):
@@ -930,8 +929,8 @@ def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==23 assert m.data_len==23
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_contact_rsp assert m.ifc.tx_fifo.get()==msg_contact_rsp
m.close() m.close()
def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp): def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
@@ -949,8 +948,8 @@ def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -969,8 +968,8 @@ def test_msg_contact_resp_2(config_tsun_inv1, msg_contact_rsp):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==msg_contact_rsp assert m.ifc.fwd_fifo.get()==msg_contact_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -989,8 +988,8 @@ def test_msg_contact_resp_3(config_tsun_inv1, msg_contact_rsp):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==msg_contact_rsp assert m.ifc.fwd_fifo.get()==msg_contact_rsp
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1007,8 +1006,8 @@ def test_msg_contact_invalid(config_tsun_inv1, msg_contact_invalid):
assert m.msg_id==0 assert m.msg_id==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==msg_contact_invalid assert m.ifc.fwd_fifo.get()==msg_contact_invalid
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1028,8 +1027,8 @@ def test_msg_get_time(config_tsun_inv1, msg_get_time):
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==0 assert m.data_len==0
assert m.state==State.pend assert m.state==State.pend
assert m._forward_buffer==msg_get_time assert m.ifc.fwd_fifo.get()==msg_get_time
assert m.ifc.write.get()==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00' assert m.ifc.tx_fifo.get()==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00'
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1049,8 +1048,8 @@ def test_msg_get_time_autark(config_no_tsun_inv1, msg_get_time):
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==0 assert m.data_len==0
assert m.state==State.received assert m.state==State.received
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==bytearray(b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00') assert m.ifc.tx_fifo.get()==bytearray(b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00')
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1060,7 +1059,7 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
m = MemoryStream(msg_time_rsp, (0,), False) m = MemoryStream(msg_time_rsp, (0,), False)
s = MemoryStream(b'', (0,), True) s = MemoryStream(b'', (0,), True)
assert s.ts_offset==0 assert s.ts_offset==0
m.remote_stream = s m.remote.stream = s
m.db.stat['proxy']['Unknown_Ctrl'] = 0 m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
@@ -1073,10 +1072,10 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
assert m.ts_offset==3600000 assert m.ts_offset==3600000
assert s.ts_offset==3600000 assert s.ts_offset==3600000
assert m.data_len==8 assert m.data_len==8
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.remote_stream = None m.remote.stream = None
s.close() s.close()
m.close() m.close()
@@ -1094,8 +1093,8 @@ def test_msg_time_resp_autark(config_no_tsun_inv1, msg_time_rsp):
assert m.header_len==23 assert m.header_len==23
assert m.ts_offset==3600000 assert m.ts_offset==3600000
assert m.data_len==8 assert m.data_len==8
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1113,8 +1112,8 @@ def test_msg_time_inv_resp(config_tsun_inv1, msg_time_rsp_inv):
assert m.header_len==23 assert m.header_len==23
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==4 assert m.data_len==4
assert m._forward_buffer==msg_time_rsp_inv assert m.ifc.fwd_fifo.get()==msg_time_rsp_inv
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1132,8 +1131,8 @@ def test_msg_time_invalid(config_tsun_inv1, msg_time_invalid):
assert m.header_len==23 assert m.header_len==23
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==0 assert m.data_len==0
assert m._forward_buffer==msg_time_invalid assert m.ifc.fwd_fifo.get()==msg_time_invalid
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1151,8 +1150,8 @@ def test_msg_time_invalid_autark(config_no_tsun_inv1, msg_time_invalid):
assert m.ts_offset==0 assert m.ts_offset==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==0 assert m.data_len==0
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1175,8 +1174,8 @@ def test_msg_act_time(config_no_modbus_poll, msg_act_time, msg_act_time_ack):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==9 assert m.data_len==9
assert m.state == State.up assert m.state == State.up
assert m._forward_buffer==msg_act_time assert m.ifc.fwd_fifo.get()==msg_act_time
assert m.ifc.write.get()==msg_act_time_ack assert m.ifc.tx_fifo.get()==msg_act_time_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert 125 == m.db.get_db_value(Register.POLLING_INTERVAL, 0) assert 125 == m.db.get_db_value(Register.POLLING_INTERVAL, 0)
m.close() m.close()
@@ -1199,8 +1198,8 @@ def test_msg_act_time2(config_tsun_inv1, msg_act_time, msg_act_time_ack):
assert m.ts_offset==0 assert m.ts_offset==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==9 assert m.data_len==9
assert m._forward_buffer==msg_act_time assert m.ifc.fwd_fifo.get()==msg_act_time
assert m.ifc.write.get()==msg_act_time_ack assert m.ifc.tx_fifo.get()==msg_act_time_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert 123 == m.db.get_db_value(Register.POLLING_INTERVAL, 0) assert 123 == m.db.get_db_value(Register.POLLING_INTERVAL, 0)
m.close() m.close()
@@ -1220,8 +1219,8 @@ def test_msg_act_time_ofs(config_tsun_inv1, msg_act_time, msg_act_time_ofs, msg_
assert m.ts_offset==3600 assert m.ts_offset==3600
assert m.header_len==23 assert m.header_len==23
assert m.data_len==9 assert m.data_len==9
assert m._forward_buffer==msg_act_time_ofs assert m.ifc.fwd_fifo.get()==msg_act_time_ofs
assert m.ifc.write.get()==msg_act_time_ack assert m.ifc.tx_fifo.get()==msg_act_time_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1240,8 +1239,8 @@ def test_msg_act_time_ofs2(config_tsun_inv1, msg_act_time, msg_act_time_ofs, msg
assert m.ts_offset==-3600 assert m.ts_offset==-3600
assert m.header_len==23 assert m.header_len==23
assert m.data_len==9 assert m.data_len==9
assert m._forward_buffer==msg_act_time assert m.ifc.fwd_fifo.get()==msg_act_time
assert m.ifc.write.get()==msg_act_time_ack assert m.ifc.tx_fifo.get()==msg_act_time_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1260,8 +1259,8 @@ def test_msg_act_time_autark(config_no_tsun_inv1, msg_act_time, msg_act_time_ack
assert m.ts_offset==0 assert m.ts_offset==0
assert m.header_len==23 assert m.header_len==23
assert m.data_len==9 assert m.data_len==9
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==msg_act_time_ack assert m.ifc.tx_fifo.get()==msg_act_time_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1278,8 +1277,8 @@ def test_msg_act_time_ack(config_tsun_inv1, msg_act_time_ack):
assert m.msg_id==153 assert m.msg_id==153
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1296,8 +1295,8 @@ def test_msg_act_time_cmd(config_tsun_inv1, msg_act_time_cmd):
assert m.msg_id==153 assert m.msg_id==153
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==msg_act_time_cmd assert m.ifc.fwd_fifo.get()==msg_act_time_cmd
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1314,8 +1313,8 @@ def test_msg_act_time_inv(config_tsun_inv1, msg_act_time_inv):
assert m.msg_id==153 assert m.msg_id==153
assert m.header_len==23 assert m.header_len==23
assert m.data_len==8 assert m.data_len==8
assert m._forward_buffer==msg_act_time_inv assert m.ifc.fwd_fifo.get()==msg_act_time_inv
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1333,12 +1332,12 @@ def test_msg_cntrl_ind(config_tsun_inv1, msg_controller_ind, msg_controller_ind_
assert m.header_len==23 assert m.header_len==23
assert m.data_len==284 assert m.data_len==284
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_controller_ind assert m.ifc.fwd_fifo.peek()==msg_controller_ind
m.ts_offset = -4096 m.ts_offset = -4096
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_controller_ind_ts_offs assert m.ifc.fwd_fifo.get()==msg_controller_ind_ts_offs
assert m.ifc.write.get()==msg_controller_ack assert m.ifc.tx_fifo.get()==msg_controller_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1355,8 +1354,8 @@ def test_msg_cntrl_ack(config_tsun_inv1, msg_controller_ack):
assert m.msg_id==113 assert m.msg_id==113
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1374,12 +1373,12 @@ def test_msg_cntrl_invalid(config_tsun_inv1, msg_controller_invalid):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_controller_invalid assert m.ifc.fwd_fifo.peek()==msg_controller_invalid
m.ts_offset = -4096 m.ts_offset = -4096
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_controller_invalid assert m.ifc.fwd_fifo.get()==msg_controller_invalid
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1398,12 +1397,12 @@ def test_msg_inv_ind(config_tsun_inv1, msg_inverter_ind, msg_inverter_ind_ts_off
assert m.header_len==23 assert m.header_len==23
assert m.data_len==120 assert m.data_len==120
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_ind assert m.ifc.fwd_fifo.peek()==msg_inverter_ind
m.ts_offset = +256 m.ts_offset = +256
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_ind_ts_offs assert m.ifc.fwd_fifo.get()==msg_inverter_ind_ts_offs
assert m.ifc.write.get()==msg_inverter_ack assert m.ifc.tx_fifo.get()==msg_inverter_ack
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1425,9 +1424,9 @@ def test_msg_inv_ind1(config_tsun_inv1, msg_inverter_ind2, msg_inverter_ind_ts_o
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1263 assert m.data_len==1263
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_ind2 assert m.ifc.fwd_fifo.get()==msg_inverter_ind2
assert m.ifc.write.get()==msg_inverter_ack assert m.ifc.tx_fifo.get()==msg_inverter_ack
assert m.db.get_db_value(Register.TS_GRID) == 1691243349 assert m.db.get_db_value(Register.TS_GRID) == 1691243349
m.close() m.close()
@@ -1449,9 +1448,9 @@ def test_msg_inv_ind2(config_tsun_inv1, msg_inverter_ind_new, msg_inverter_ind_t
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1165 assert m.data_len==1165
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_ind_new assert m.ifc.fwd_fifo.get()==msg_inverter_ind_new
assert m.ifc.write.get()==msg_inverter_ack assert m.ifc.tx_fifo.get()==msg_inverter_ack
assert m.db.get_db_value(Register.INVERTER_STATUS) == None assert m.db.get_db_value(Register.INVERTER_STATUS) == None
assert m.db.get_db_value(Register.TS_GRID) == None assert m.db.get_db_value(Register.TS_GRID) == None
m.db.db['grid'] = {'Output_Power': 100} m.db.db['grid'] = {'Output_Power': 100}
@@ -1477,9 +1476,9 @@ def test_msg_inv_ind3(config_tsun_inv1, msg_inverter_ind_0w, msg_inverter_ack):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1263 assert m.data_len==1263
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_ind_0w assert m.ifc.fwd_fifo.get()==msg_inverter_ind_0w
assert m.ifc.write.get()==msg_inverter_ack assert m.ifc.tx_fifo.get()==msg_inverter_ack
assert m.db.get_db_value(Register.INVERTER_STATUS) == 1 assert m.db.get_db_value(Register.INVERTER_STATUS) == 1
assert isclose(m.db.db['grid']['Output_Power'], 0.5) assert isclose(m.db.db['grid']['Output_Power'], 0.5)
m.close() m.close()
@@ -1501,8 +1500,8 @@ def test_msg_inv_ack(config_tsun_inv1, msg_inverter_ack):
assert m.msg_id==4 assert m.msg_id==4
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -1520,12 +1519,12 @@ def test_msg_inv_invalid(config_tsun_inv1, msg_inverter_invalid):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_invalid assert m.ifc.fwd_fifo.peek()==msg_inverter_invalid
m.ts_offset = 256 m.ts_offset = 256
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_inverter_invalid assert m.ifc.fwd_fifo.get()==msg_inverter_invalid
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
m.close() m.close()
@@ -1544,12 +1543,12 @@ def test_msg_ota_req(config_tsun_inv1, msg_ota_req):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==259 assert m.data_len==259
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_ota_req assert m.ifc.fwd_fifo.peek()==msg_ota_req
m.ts_offset = 4096 m.ts_offset = 4096
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_ota_req assert m.ifc.fwd_fifo.get()==msg_ota_req
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['OTA_Start_Msg'] == 1 assert m.db.stat['proxy']['OTA_Start_Msg'] == 1
m.close() m.close()
@@ -1571,12 +1570,12 @@ def test_msg_ota_ack(config_tsun_inv1, msg_ota_ack):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_ota_ack assert m.ifc.fwd_fifo.peek()==msg_ota_ack
m.ts_offset = 256 m.ts_offset = 256
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_ota_ack assert m.ifc.fwd_fifo.get()==msg_ota_ack
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['OTA_Start_Msg'] == 0 assert m.db.stat['proxy']['OTA_Start_Msg'] == 0
m.close() m.close()
@@ -1596,12 +1595,12 @@ def test_msg_ota_invalid(config_tsun_inv1, msg_ota_invalid):
assert m.header_len==23 assert m.header_len==23
assert m.data_len==1 assert m.data_len==1
m.ts_offset = 0 m.ts_offset = 0
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m._forward_buffer==msg_ota_invalid assert m.ifc.fwd_fifo.peek()==msg_ota_invalid
m.ts_offset = 4096 m.ts_offset = 4096
assert m._forward_buffer==msg_ota_invalid assert m.ifc.fwd_fifo.get()==msg_ota_invalid
m._update_header(m._forward_buffer) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
assert m.db.stat['proxy']['OTA_Start_Msg'] == 0 assert m.db.stat['proxy']['OTA_Start_Msg'] == 0
m.close() m.close()
@@ -1619,8 +1618,8 @@ def test_msg_unknown(config_tsun_inv1, msg_unknown):
assert m.msg_id==23 assert m.msg_id==23
assert m.header_len==23 assert m.header_len==23
assert m.data_len==4 assert m.data_len==4
assert m._forward_buffer==msg_unknown assert m.ifc.fwd_fifo.get()==msg_unknown
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert 1 == m.db.stat['proxy']['Unknown_Msg'] assert 1 == m.db.stat['proxy']['Unknown_Msg']
m.close() m.close()
@@ -1640,9 +1639,9 @@ def test_ctrl_byte():
def test_msg_iterator(): def test_msg_iterator():
m1 = Talent(server_side=True, ifc=AsyncIfc()) m1 = Talent(server_side=True, ifc=AsyncIfcImpl())
m2 = Talent(server_side=True, ifc=AsyncIfc()) m2 = Talent(server_side=True, ifc=AsyncIfcImpl())
m3 = Talent(server_side=True, ifc=AsyncIfc()) m3 = Talent(server_side=True, ifc=AsyncIfcImpl())
m3.close() m3.close()
del m3 del m3
test1 = 0 test1 = 0
@@ -1744,11 +1743,11 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd):
assert c.msg_id==119 assert c.msg_id==119
assert c.header_len==23 assert c.header_len==23
assert c.data_len==13 assert c.data_len==13
assert c._forward_buffer==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m.id_str == b"R170000000000001" assert m.id_str == b"R170000000000001"
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.sent_pdu == msg_modbus_cmd assert m.sent_pdu == msg_modbus_cmd
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 1 assert m.db.stat['proxy']['Modbus_Command'] == 1
@@ -1774,11 +1773,11 @@ def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd):
assert c.msg_id==119 assert c.msg_id==119
assert c.header_len==23 assert c.header_len==23
assert c.data_len==13 assert c.data_len==13
assert c._forward_buffer==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m.id_str == b"R170000000000001" assert m.id_str == b"R170000000000001"
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.sent_pdu == b'' assert m.sent_pdu == b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 1 assert m.db.stat['proxy']['Modbus_Command'] == 1
@@ -1803,10 +1802,10 @@ def test_msg_modbus_req3(config_tsun_inv1, msg_modbus_cmd_crc_err):
assert c.msg_id==119 assert c.msg_id==119
assert c.header_len==23 assert c.header_len==23
assert c.data_len==13 assert c.data_len==13
assert c._forward_buffer==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.sent_pdu ==b'' assert m.sent_pdu ==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
@@ -1828,8 +1827,8 @@ def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
assert m.msg_id==119 assert m.msg_id==119
assert m.header_len==23 assert m.header_len==23
assert m.data_len==13 assert m.data_len==13
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1850,8 +1849,8 @@ def test_msg_modbus_cloud_rsp(config_tsun_inv1, msg_modbus_rsp):
assert m.msg_id==119 assert m.msg_id==119
assert m.header_len==23 assert m.header_len==23
assert m.data_len==13 assert m.data_len==13
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Msg'] == 1 assert m.db.stat['proxy']['Unknown_Msg'] == 1
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
@@ -1878,8 +1877,8 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp20):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 5 assert m.mb.err == 5
assert m.msg_count == 2 assert m.msg_count == 2
assert m._forward_buffer==msg_modbus_rsp20 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}} assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.09' assert m.db.get_db_value(Register.VERSION) == 'V5.1.09'
assert m.db.get_db_value(Register.TS_GRID) == m._utc() assert m.db.get_db_value(Register.TS_GRID) == m._utc()
@@ -1908,8 +1907,8 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp21):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 5 assert m.mb.err == 5
assert m.msg_count == 2 assert m.msg_count == 2
assert m._forward_buffer==msg_modbus_rsp21 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}} assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E' assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
assert m.db.get_db_value(Register.TS_GRID) == m._utc() assert m.db.get_db_value(Register.TS_GRID) == m._utc()
@@ -1937,9 +1936,9 @@ def test_msg_modbus_rsp4(config_tsun_inv1, msg_modbus_rsp21):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 0 assert m.mb.err == 0
assert m.msg_count == 1 assert m.msg_count == 1
assert m._forward_buffer==msg_modbus_rsp21 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21
assert m.modbus_elms == 19 assert m.modbus_elms == 19
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.db == db_values assert m.db.db == db_values
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E' assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
assert m.db.get_db_value(Register.TS_GRID) == m._utc() assert m.db.get_db_value(Register.TS_GRID) == m._utc()
@@ -1962,8 +1961,8 @@ def test_msg_modbus_rsp_new(config_tsun_inv1, msg_modbus_rsp20_new):
assert m.msg_id==135 assert m.msg_id==135
assert m.header_len==23 assert m.header_len==23
assert m.data_len==107 assert m.data_len==107
assert m._forward_buffer==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -1982,8 +1981,8 @@ def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_inv):
assert m.msg_id==119 assert m.msg_id==119
assert m.header_len==23 assert m.header_len==23
assert m.data_len==13 assert m.data_len==13
assert m._forward_buffer==msg_modbus_inv assert m.ifc.fwd_fifo.get()==msg_modbus_inv
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1 assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0
m.close() m.close()
@@ -2011,8 +2010,8 @@ def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp20):
assert m.msg_id == 119 assert m.msg_id == 119
assert m.header_len == 23 assert m.header_len == 23
assert m.data_len == 50 assert m.data_len == 50
assert m._forward_buffer==msg_modbus_rsp20 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20
assert m.ifc.write.get() == b'' assert m.ifc.tx_fifo.get() == b''
assert m.mb.err == 0 assert m.mb.err == 0
assert m.modbus_elms == 20-1 # register 0x300d is unknown, so one value can't be mapped assert m.modbus_elms == 20-1 # register 0x300d is unknown, so one value can't be mapped
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
@@ -2026,24 +2025,16 @@ async def test_msg_build_modbus_req(config_tsun_inv1, msg_modbus_cmd):
m.id_str = b"R170000000000001" m.id_str = b"R170000000000001"
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m._forward_buffer == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.ifc.write.get() == b'' assert m.ifc.tx_fifo.get() == b''
assert m.sent_pdu == b'' assert m.sent_pdu == b''
m.state = State.up m.state = State.up
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m._forward_buffer == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.ifc.write.get() == b'' assert m.ifc.tx_fifo.get() == b''
assert m.sent_pdu == msg_modbus_cmd assert m.sent_pdu == msg_modbus_cmd
m.sent_pdu = bytearray(0) # clear send buffer for next test
m.test_exception_async_write = True
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs
assert m._forward_buffer == b''
assert m.ifc.write.get() == b''
assert m.sent_pdu == b''
m.close() m.close()
def test_modbus_no_polling(config_no_modbus_poll, msg_get_time): def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
@@ -2061,8 +2052,8 @@ def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
assert m.header_len==23 assert m.header_len==23
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==0 assert m.data_len==0
assert m._forward_buffer==msg_get_time assert m.ifc.fwd_fifo.get()==msg_get_time
assert m.ifc.write.get()==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00' assert m.ifc.tx_fifo.get()==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00'
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close() m.close()
@@ -2085,25 +2076,25 @@ async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
assert m.header_len==23 assert m.header_len==23
assert m.ts_offset==0 assert m.ts_offset==0
assert m.data_len==120 assert m.data_len==120
assert m._forward_buffer==msg_inverter_ind assert m.ifc.fwd_fifo.get()==msg_inverter_ind
assert m.ifc.write.get()==b'\x00\x00\x00\x14\x10R170000000000001\x99\x04\x01' assert m.ifc.tx_fifo.get()==b'\x00\x00\x00\x14\x10R170000000000001\x99\x04\x01'
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0 assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.ifc.write.clear() # clear send buffer for next test m.ifc.tx_clear() # clear send buffer for next test
assert isclose(m.mb_timeout, 0.5) assert isclose(m.mb_timeout, 0.5)
assert next(m.mb_timer.exp_count) == 0 assert next(m.mb_timer.exp_count) == 0
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde' assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde' assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x03\x20\x00\x00`N"' assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x03\x20\x00\x00`N"'
assert m.ifc.write.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert next(m.mb_timer.exp_count) == 4 assert next(m.mb_timer.exp_count) == 4
m.close() m.close()
@@ -2146,3 +2137,15 @@ def test_multiiple_recv_buf(config_tsun_allow_all, multiple_recv_buf):
assert m.db.stat['proxy']['Invalid_Data_Type'] == 1 assert m.db.stat['proxy']['Invalid_Data_Type'] == 1
m.close() m.close()
def test_timeout(config_tsun_inv1):
_ = config_tsun_inv1
m = MemoryStream(b'')
assert m.state == State.init
assert Talent.MAX_START_TIME == m._timeout()
m.state = State.up
m.modbus_polling = True
assert Talent.MAX_INV_IDLE_TIME == m._timeout()
m.modbus_polling = False
assert Talent.MAX_DEF_IDLE_TIME == m._timeout()
m.close()

11
tsun.code-workspace Normal file
View File

@@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../wiki"
}
],
"settings": {}
}